@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
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The same Details dialog as `process-details-linux.ts`, for macOS.
|
|
3
|
+
*
|
|
4
|
+
* It exists because the dialog was *lying* there: the service answered `null`
|
|
5
|
+
* for every non-Linux platform and the route turned that into a 404 reading
|
|
6
|
+
* "PID <n> is no longer running" — a sentence that is correct on Linux, where a
|
|
7
|
+
* vanished `/proc/<pid>` really does mean the process exited, and simply untrue
|
|
8
|
+
* anywhere else. A process sitting in the table at 4.2% CPU was reported dead.
|
|
9
|
+
*
|
|
10
|
+
* macOS has no `/proc`, but `ps` answers ten of the eleven fields, and two of
|
|
11
|
+
* them more directly than Linux does: `lstart` is a real timestamp rather than
|
|
12
|
+
* jiffies to be added to `btime`, and `user` is the login name already, so no
|
|
13
|
+
* `/etc/passwd` lookup is needed and it works across users. Only `cgroup` has no
|
|
14
|
+
* equivalent — macOS has no such concept — and it stays null, which the page
|
|
15
|
+
* draws as an em dash: the honest answer rather than an invented one.
|
|
16
|
+
*
|
|
17
|
+
* Measured on an M1 Max: `ps` for one pid 29.7 ms, `ps -M` 32.5 ms, `lsof` 37.5
|
|
18
|
+
* ms. The last three run concurrently, so a dialog costs about two spawns' wall
|
|
19
|
+
* time. That is affordable only because this is on-demand — it must never be
|
|
20
|
+
* moved onto the tick, which is the same rule the Linux module states.
|
|
21
|
+
*/
|
|
22
|
+
import type { ProcessDetails } from "../../types/system-metrics.ts";
|
|
23
|
+
import { redactSecrets } from "../redact-secrets.ts";
|
|
24
|
+
import { defaultRunner, type Runner } from "../host-info/spawn-runner.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* BSD's state letters, which are NOT Linux's. The one that catches people is
|
|
28
|
+
* **U**: uninterruptible wait, spelled `D` on Linux. `I` is also real here —
|
|
29
|
+
* macOS marks a process idle once it has slept more than 20 seconds — and `Z`
|
|
30
|
+
* is far from rare: this host had 126 zombies against 645 sleepers.
|
|
31
|
+
*/
|
|
32
|
+
export const DARWIN_PROCESS_STATES: Record<string, string> = {
|
|
33
|
+
R: "Running", S: "Sleeping", I: "Idle", T: "Stopped",
|
|
34
|
+
U: "Uninterruptible sleep", Z: "Zombie",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export interface PsFields {
|
|
38
|
+
ppid: number;
|
|
39
|
+
state: string | null;
|
|
40
|
+
nice: number | null;
|
|
41
|
+
/** Epoch ms, or 0 when `lstart` did not parse — the UI draws 0 as an em dash. */
|
|
42
|
+
startedAt: number;
|
|
43
|
+
user: string | null;
|
|
44
|
+
exe: string | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One line of `ps -o ppid=,stat=,nice=,lstart=,user=,comm=`, e.g.
|
|
49
|
+
* ` 1 S 0 Tue Sep 8 10:05:03 2026 thawng /System/…/Finder`
|
|
50
|
+
*
|
|
51
|
+
* Split by position, not by a delimiter, because `ps` on macOS offers none —
|
|
52
|
+
* and two fields contain spaces. `lstart` is **always five tokens** (verified
|
|
53
|
+
* across all 740 processes on a live host: a single-digit day pads with two
|
|
54
|
+
* spaces, which collapses on a `\s+` split rather than dropping a token), so
|
|
55
|
+
* every field up to `user` is at a fixed index and `comm` is whatever is left.
|
|
56
|
+
* It has to be last for that reason: a path like
|
|
57
|
+
* `/Applications/Google Chrome.app/…/Google Chrome Helper (Renderer)` holds six
|
|
58
|
+
* spaces, so anything after it could not be found again.
|
|
59
|
+
*/
|
|
60
|
+
export function parsePsLine(text: string | null | undefined): PsFields | null {
|
|
61
|
+
const line = (text ?? "").trim();
|
|
62
|
+
if (!line) return null;
|
|
63
|
+
const t = line.split(/\s+/);
|
|
64
|
+
// ppid, stat, nice, five for lstart, user — comm may legitimately be empty.
|
|
65
|
+
if (t.length < 9) return null;
|
|
66
|
+
|
|
67
|
+
const ppid = Number(t[0]);
|
|
68
|
+
if (!Number.isInteger(ppid) || ppid < 0) return null;
|
|
69
|
+
|
|
70
|
+
const nice = Number(t[2]);
|
|
71
|
+
const started = Date.parse(t.slice(3, 8).join(" "));
|
|
72
|
+
const exe = t.slice(9).join(" ");
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
ppid,
|
|
76
|
+
state: DARWIN_PROCESS_STATES[t[1]![0] ?? ""] ?? null,
|
|
77
|
+
nice: Number.isFinite(nice) ? nice : null,
|
|
78
|
+
startedAt: Number.isFinite(started) ? started : 0,
|
|
79
|
+
user: t[8] || null,
|
|
80
|
+
exe: exe || null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Thread count from a bare `ps -M -p <pid>` dump: one header line, then one line
|
|
86
|
+
* per thread.
|
|
87
|
+
*
|
|
88
|
+
* `-M` may not be combined with `-o` — asking for both prints the chosen columns
|
|
89
|
+
* AND leftovers of the thread layout on the same line, which parses as nonsense
|
|
90
|
+
* rather than failing, so this is deliberately a second call against the default
|
|
91
|
+
* format rather than a column added to the first.
|
|
92
|
+
*/
|
|
93
|
+
export function parseThreadCount(text: string | null | undefined): number | null {
|
|
94
|
+
const lines = (text ?? "").split("\n").filter((l) => l.trim().length > 0);
|
|
95
|
+
return lines.length > 1 ? lines.length - 1 : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* `lsof -a -p <pid> -d cwd -Fn` in its machine-readable form: one field per
|
|
100
|
+
* line, prefixed by its type, `n` being the name.
|
|
101
|
+
*
|
|
102
|
+
* Another user's working directory comes back as exit 0 with **no output at
|
|
103
|
+
* all** rather than an error, which is the normal case on any machine with
|
|
104
|
+
* daemons on it — the same "absent, not failed" reading the Linux module gives
|
|
105
|
+
* an EACCES on `/proc/<pid>/cwd`.
|
|
106
|
+
*/
|
|
107
|
+
export function parseLsofCwd(text: string | null | undefined): string | null {
|
|
108
|
+
for (const line of (text ?? "").split("\n")) {
|
|
109
|
+
if (line.startsWith("n")) return line.slice(1).trim() || null;
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The last path segment, for the `name` field — macOS has no `comm` truncated
|
|
115
|
+
* to 15 characters to take it from, only the executable's full path. */
|
|
116
|
+
function baseName(exe: string | null): string | null {
|
|
117
|
+
if (!exe) return null;
|
|
118
|
+
const cut = exe.lastIndexOf("/");
|
|
119
|
+
return (cut >= 0 ? exe.slice(cut + 1) : exe) || null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Null when the pid is gone, which `ps` reports as a non-zero exit with nothing
|
|
124
|
+
* on stdout — so the route's "no longer running" is finally true when it is said.
|
|
125
|
+
*/
|
|
126
|
+
export async function readProcessDetailsDarwin(
|
|
127
|
+
pid: number,
|
|
128
|
+
run: Runner = defaultRunner,
|
|
129
|
+
): Promise<ProcessDetails | null> {
|
|
130
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
131
|
+
const arg = String(pid);
|
|
132
|
+
|
|
133
|
+
const ps = await run(["ps", "-o", "ppid=,stat=,nice=,lstart=,user=,comm=", "-p", arg]);
|
|
134
|
+
if (ps.code !== 0) return null;
|
|
135
|
+
const fields = parsePsLine(ps.stdout);
|
|
136
|
+
if (!fields) return null;
|
|
137
|
+
|
|
138
|
+
// Concurrently: three independent spawns, and every one of them is allowed to
|
|
139
|
+
// fail on its own. A process whose command line or thread count cannot be read
|
|
140
|
+
// is still a process whose parent, user and state were just read successfully.
|
|
141
|
+
const [cmd, threads, cwd] = await Promise.all([
|
|
142
|
+
run(["ps", "-o", "command=", "-p", arg]),
|
|
143
|
+
run(["ps", "-M", "-p", arg]),
|
|
144
|
+
run(["lsof", "-a", "-p", arg, "-d", "cwd", "-Fn"]),
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const command = cmd.code === 0 ? cmd.stdout.trim() : "";
|
|
148
|
+
return {
|
|
149
|
+
pid,
|
|
150
|
+
ppid: fields.ppid,
|
|
151
|
+
name: baseName(fields.exe) ?? arg,
|
|
152
|
+
startedAt: fields.startedAt,
|
|
153
|
+
// Redacted but NOT truncated, exactly as on Linux: showing the whole line is
|
|
154
|
+
// what this dialog is for.
|
|
155
|
+
command: command ? redactSecrets(command) : null,
|
|
156
|
+
exe: fields.exe,
|
|
157
|
+
cwd: cwd.code === 0 ? parseLsofCwd(cwd.stdout) : null,
|
|
158
|
+
user: fields.user,
|
|
159
|
+
state: fields.state,
|
|
160
|
+
threads: threads.code === 0 ? parseThreadCount(threads.stdout) : null,
|
|
161
|
+
nice: fields.nice,
|
|
162
|
+
// macOS has no cgroups. Null is the honest answer and renders as an em dash;
|
|
163
|
+
// inventing a value here would be the same mistake this file exists to undo.
|
|
164
|
+
cgroup: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand facts for the process Details dialog — Mission Center's process
|
|
3
|
+
* properties. Fetched when the dialog opens, never per tick: it is a handful of
|
|
4
|
+
* reads per process and nobody needs `cwd` at 0.5 Hz.
|
|
5
|
+
*
|
|
6
|
+
* Every field is null when this pid's permissions do not expose it. Another
|
|
7
|
+
* user's `exe` and `cwd` are EACCES for an unprivileged PPM, which is the normal
|
|
8
|
+
* case on a shared machine and not an error.
|
|
9
|
+
*/
|
|
10
|
+
import type { ProcessDetails } from "../../types/system-metrics.ts";
|
|
11
|
+
import { redactSecrets } from "../redact-secrets.ts";
|
|
12
|
+
import { readAttr, realLinuxFs, type LinuxFs } from "./linux-fs.ts";
|
|
13
|
+
|
|
14
|
+
const PROC = "/proc";
|
|
15
|
+
/** Same assumption `proc-table-linux.ts` documents: 100 on every mainstream Linux. */
|
|
16
|
+
const CLOCK_TICKS_PER_SEC = 100;
|
|
17
|
+
|
|
18
|
+
/** The kernel's one-letter state, spelled out the way `ps` and Mission Center do. */
|
|
19
|
+
export const PROCESS_STATES: Record<string, string> = {
|
|
20
|
+
R: "Running", S: "Sleeping", D: "Uninterruptible sleep", Z: "Zombie",
|
|
21
|
+
T: "Stopped", t: "Tracing stop", X: "Dead", I: "Idle", W: "Paging", K: "Wakekill",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface StatFields {
|
|
25
|
+
state: string;
|
|
26
|
+
ppid: number;
|
|
27
|
+
nice: number;
|
|
28
|
+
threads: number;
|
|
29
|
+
/** Ticks since boot. */
|
|
30
|
+
startTicks: number;
|
|
31
|
+
comm: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* `/proc/<pid>/stat`, anchored on the LAST `)`.
|
|
36
|
+
*
|
|
37
|
+
* The second field is the executable name in parentheses and it is not escaped,
|
|
38
|
+
* so a process called `foo) 0 (bar` shifts every field after it for any parser
|
|
39
|
+
* that splits on whitespace. Anchoring on the last `)` is the only correct
|
|
40
|
+
* reading, and it is what `readProcTable` does too.
|
|
41
|
+
*/
|
|
42
|
+
export function parseStatFields(text: string): StatFields | null {
|
|
43
|
+
const close = text.lastIndexOf(")");
|
|
44
|
+
const open = text.indexOf("(");
|
|
45
|
+
if (close < 0 || open < 0 || close < open) return null;
|
|
46
|
+
const rest = text.slice(close + 1).trim().split(/\s+/);
|
|
47
|
+
// `rest[0]` is field 3 (state), so field N is at rest[N - 3].
|
|
48
|
+
const at = (field: number) => Number(rest[field - 3]);
|
|
49
|
+
const ppid = at(4);
|
|
50
|
+
if (!Number.isFinite(ppid)) return null;
|
|
51
|
+
return {
|
|
52
|
+
comm: text.slice(open + 1, close),
|
|
53
|
+
state: rest[0] ?? "",
|
|
54
|
+
ppid,
|
|
55
|
+
nice: at(19),
|
|
56
|
+
threads: at(20),
|
|
57
|
+
startTicks: at(22),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** `/proc/stat`'s `btime` line: boot time as epoch SECONDS. */
|
|
62
|
+
export function parseBootTimeSec(procStat: string | null): number | null {
|
|
63
|
+
const m = /^btime\s+(\d+)/m.exec(procStat ?? "");
|
|
64
|
+
const n = m ? Number(m[1]) : NaN;
|
|
65
|
+
return Number.isFinite(n) ? n : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The real uid from `/proc/<pid>/status`'s `Uid: real effective saved fs`. */
|
|
69
|
+
export function parseUid(status: string | null): number | null {
|
|
70
|
+
const m = /^Uid:\s+(\d+)/m.exec(status ?? "");
|
|
71
|
+
const n = m ? Number(m[1]) : NaN;
|
|
72
|
+
return Number.isFinite(n) ? n : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** uid → login name from `/etc/passwd`, which is world-readable by design. */
|
|
76
|
+
export function lookupUser(uid: number, passwd: string | null): string | null {
|
|
77
|
+
for (const line of (passwd ?? "").split("\n")) {
|
|
78
|
+
const f = line.split(":");
|
|
79
|
+
if (f.length > 2 && Number(f[2]) === uid) return f[0] ?? null;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** cgroup v2 writes one `0::<path>` line; v1 writes several, and the systemd
|
|
85
|
+
* hierarchy is the one that names the unit. */
|
|
86
|
+
export function parseCgroup(text: string | null): string | null {
|
|
87
|
+
const lines = (text ?? "").split("\n").filter(Boolean);
|
|
88
|
+
const v2 = lines.find((l) => l.startsWith("0::"));
|
|
89
|
+
if (v2) return v2.slice(3) || null;
|
|
90
|
+
const systemd = lines.find((l) => l.includes(":name=systemd:"));
|
|
91
|
+
const chosen = systemd ?? lines[0];
|
|
92
|
+
if (!chosen) return null;
|
|
93
|
+
const idx = chosen.indexOf(":", chosen.indexOf(":") + 1);
|
|
94
|
+
return idx >= 0 ? chosen.slice(idx + 1) || null : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ProcessDetailsOptions {
|
|
98
|
+
fs?: LinuxFs;
|
|
99
|
+
/** Injected so the lookup is testable and read once per request, not per line. */
|
|
100
|
+
passwd?: () => string | null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Null when the pid is gone (or /proc is unreadable) — the dialog then says so. */
|
|
104
|
+
export function readProcessDetails(pid: number, opts: ProcessDetailsOptions = {}): ProcessDetails | null {
|
|
105
|
+
const fs = opts.fs ?? realLinuxFs;
|
|
106
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
107
|
+
const stat = fs.read(`${PROC}/${pid}/stat`);
|
|
108
|
+
const fields = stat ? parseStatFields(stat) : null;
|
|
109
|
+
if (!fields) return null;
|
|
110
|
+
|
|
111
|
+
const status = fs.read(`${PROC}/${pid}/status`);
|
|
112
|
+
const uid = parseUid(status);
|
|
113
|
+
const bootSec = parseBootTimeSec(fs.read(`${PROC}/stat`));
|
|
114
|
+
const rawCmdline = fs.read(`${PROC}/${pid}/cmdline`) ?? "";
|
|
115
|
+
// NUL-separated argv. A kernel thread has an empty cmdline, which is not a
|
|
116
|
+
// command line of "" — it has none at all.
|
|
117
|
+
const argv = rawCmdline.split("\0").filter(Boolean).join(" ");
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
pid,
|
|
121
|
+
ppid: fields.ppid,
|
|
122
|
+
name: nameOf(status) ?? fields.comm,
|
|
123
|
+
startedAt: bootSec !== null && Number.isFinite(fields.startTicks)
|
|
124
|
+
? Math.round((bootSec + fields.startTicks / CLOCK_TICKS_PER_SEC) * 1000)
|
|
125
|
+
: 0,
|
|
126
|
+
// Redacted but NOT truncated: the dialog exists to show the whole line, and
|
|
127
|
+
// the 160-char cut on a table row is a bandwidth decision, not a privacy one.
|
|
128
|
+
command: argv ? redactSecrets(argv) : null,
|
|
129
|
+
exe: fs.readlink(`${PROC}/${pid}/exe`),
|
|
130
|
+
cwd: fs.readlink(`${PROC}/${pid}/cwd`),
|
|
131
|
+
user: uid === null ? null : lookupUser(uid, (opts.passwd ?? (() => fs.read("/etc/passwd")))()),
|
|
132
|
+
state: describeState(fields.state),
|
|
133
|
+
threads: Number.isFinite(fields.threads) ? fields.threads : null,
|
|
134
|
+
nice: Number.isFinite(fields.nice) ? fields.nice : null,
|
|
135
|
+
cgroup: parseCgroup(fs.read(`${PROC}/${pid}/cgroup`)),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** `/proc/<pid>/status`'s `Name:` is the same truncated comm, but without the
|
|
140
|
+
* parenthesis quoting — preferred when present because it needs no unwrapping. */
|
|
141
|
+
function nameOf(status: string | null): string | null {
|
|
142
|
+
const m = /^Name:\s*(.+)$/m.exec(status ?? "");
|
|
143
|
+
return m?.[1]?.trim() || null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function describeState(code: string): string | null {
|
|
147
|
+
if (!code) return null;
|
|
148
|
+
const word = PROCESS_STATES[code];
|
|
149
|
+
return word ? `${code} (${word})` : code;
|
|
150
|
+
}
|
|
@@ -16,7 +16,7 @@ const POSIX_BOUNDARY_NAMES: ReadonlySet<string> = new Set(["launchd", "systemd",
|
|
|
16
16
|
const round1 = (n: number): number => Math.round(n * 10) / 10;
|
|
17
17
|
|
|
18
18
|
/** The optional per-process columns, which roll up by summation. */
|
|
19
|
-
type OptionalMetric = "diskReadBps" | "diskWriteBps" | "gpuPct" | "gpuMemMB" | "netInBps" | "netOutBps";
|
|
19
|
+
type OptionalMetric = "swapMB" | "diskReadBps" | "diskWriteBps" | "gpuPct" | "gpuMemMB" | "netInBps" | "netOutBps";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Sum over the members that HAVE a value; `undefined` when none does, so a
|
|
@@ -129,6 +129,7 @@ export function groupProcesses(
|
|
|
129
129
|
count: g.procs.length,
|
|
130
130
|
ppm: g.procs.some((p) => p.ppm),
|
|
131
131
|
pids: g.procs.map((p) => p.pid),
|
|
132
|
+
swapMB: sumOptional(g.procs, "swapMB"),
|
|
132
133
|
diskReadBps: sumOptional(g.procs, "diskReadBps"),
|
|
133
134
|
diskWriteBps: sumOptional(g.procs, "diskWriteBps"),
|
|
134
135
|
gpuPct: sumOptional(g.procs, "gpuPct"),
|
|
@@ -93,6 +93,8 @@ export function buildProcessRows(input: BuildRowsInput): BuiltRows {
|
|
|
93
93
|
command: sanitizeCommand(r.command, r.name),
|
|
94
94
|
cpu: percentByKey.get(key) ?? 0,
|
|
95
95
|
ramMB: Math.round(r.ramMB * 10) / 10,
|
|
96
|
+
...(r.swapMB === undefined ? {} : { swapMB: Math.round(r.swapMB * 10) / 10 }),
|
|
97
|
+
...(r.unitKey === undefined ? {} : { unitKey: r.unitKey }),
|
|
96
98
|
startedAt: r.startedAt,
|
|
97
99
|
ppm: ppmPids.has(r.pid),
|
|
98
100
|
protected: !checkKillAllowed({ pid: r.pid, name: r.name }, false, guardCtx).allowed,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-process swap — Mission Center's Swap column, on the process table and on
|
|
3
|
+
* every Services row.
|
|
4
|
+
*
|
|
5
|
+
* `VmSwap` in `/proc/<pid>/status` is the only cheap source: `smaps_rollup` also
|
|
6
|
+
* carries it but walks every mapping, and on a 560-process desktop that is the
|
|
7
|
+
* difference between a few milliseconds and a tick that misses its deadline.
|
|
8
|
+
* Measured here: the whole table costs 3.2 ms, beside the 1.7 ms `/proc/<pid>/io`
|
|
9
|
+
* already pays every tick.
|
|
10
|
+
*
|
|
11
|
+
* A kernel thread has no address space and so no `VmSwap` line at all. That is
|
|
12
|
+
* 0 swapped bytes, not an unmeasurable one — the distinction matters because the
|
|
13
|
+
* table renders `undefined` as an em dash, and 335 of this host's 561 rows would
|
|
14
|
+
* otherwise be dashes on a column that really does read zero for them.
|
|
15
|
+
*/
|
|
16
|
+
import { readFileSync } from "node:fs";
|
|
17
|
+
|
|
18
|
+
const KB_PER_MB = 1024;
|
|
19
|
+
|
|
20
|
+
/** `VmSwap` in kB from a `/proc/<pid>/status` dump. A process with an address
|
|
21
|
+
* space always has the line; one without is a kernel thread, i.e. 0. */
|
|
22
|
+
export function parseVmSwapKB(status: string): number {
|
|
23
|
+
const m = /^VmSwap:\s+(\d+)\s*kB/m.exec(status);
|
|
24
|
+
if (!m) return 0;
|
|
25
|
+
const kb = Number(m[1]);
|
|
26
|
+
return Number.isFinite(kb) ? kb : 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Swapped MB for one pid, or null when the process is gone or unreadable —
|
|
30
|
+
* another user's process is expected to refuse, and that row simply has none. */
|
|
31
|
+
export function readProcSwapMB(pid: number): number | null {
|
|
32
|
+
try {
|
|
33
|
+
return parseVmSwapKB(readFileSync(`/proc/${pid}/status`, "utf-8")) / KB_PER_MB;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivers ONE signal to a process, or to its whole collected tree — Mission
|
|
3
|
+
* Center's "Send Signal" submenu. Separate from `executeKill` on purpose: a kill
|
|
4
|
+
* escalates SIGTERM to SIGKILL after a grace period, which is right for "end this
|
|
5
|
+
* process" and wrong for every other signal. Asking for SIGSTOP must send SIGSTOP
|
|
6
|
+
* and nothing else.
|
|
7
|
+
*
|
|
8
|
+
* Pids are validated integers passed to `process.kill` or as argv elements —
|
|
9
|
+
* never interpolated into a shell string.
|
|
10
|
+
*/
|
|
11
|
+
import type { ProcessSignal, SignalProcessResult } from "../../types/system-metrics.ts";
|
|
12
|
+
import type { Runner } from "../host-info/spawn-runner.ts";
|
|
13
|
+
import { defaultRunner } from "../host-info/spawn-runner.ts";
|
|
14
|
+
import { collectProcessTree } from "../windows-process-tree.ts";
|
|
15
|
+
|
|
16
|
+
/** Windows has no signals. `taskkill` is the only delivery there and it can only
|
|
17
|
+
* mean "end it", so those two map and the rest are refused with a reason. */
|
|
18
|
+
export const WINDOWS_SUPPORTED_SIGNALS: ReadonlySet<ProcessSignal> = new Set<ProcessSignal>(["TERM", "KILL"]);
|
|
19
|
+
|
|
20
|
+
export interface SignalExecutorDeps {
|
|
21
|
+
platform: NodeJS.Platform;
|
|
22
|
+
run: Runner;
|
|
23
|
+
signal: (pid: number, sig: NodeJS.Signals) => void;
|
|
24
|
+
collectTree: (pid: number) => number[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const defaultSignalExecutorDeps: SignalExecutorDeps = {
|
|
28
|
+
platform: process.platform,
|
|
29
|
+
run: defaultRunner,
|
|
30
|
+
signal: (pid, sig) => { process.kill(pid, sig); },
|
|
31
|
+
collectTree: collectProcessTree,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Which signals this host can actually deliver, for the client to build its menu. */
|
|
35
|
+
export function supportedSignals(platform: NodeJS.Platform = process.platform): ProcessSignal[] {
|
|
36
|
+
return platform === "win32"
|
|
37
|
+
? [...WINDOWS_SUPPORTED_SIGNALS]
|
|
38
|
+
: ["TERM", "KILL", "STOP", "CONT", "HUP", "INT", "USR1", "USR2"];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Throws on executor failure; the handler maps that to 500. */
|
|
42
|
+
export async function executeSignal(
|
|
43
|
+
pid: number,
|
|
44
|
+
signal: ProcessSignal,
|
|
45
|
+
tree: boolean,
|
|
46
|
+
deps: SignalExecutorDeps = defaultSignalExecutorDeps,
|
|
47
|
+
): Promise<SignalProcessResult> {
|
|
48
|
+
if (!Number.isInteger(pid) || pid <= 0) throw new Error(`Invalid PID ${pid}`);
|
|
49
|
+
|
|
50
|
+
if (deps.platform === "win32") {
|
|
51
|
+
if (!WINDOWS_SUPPORTED_SIGNALS.has(signal)) {
|
|
52
|
+
throw new Error(`Windows cannot deliver SIG${signal}`);
|
|
53
|
+
}
|
|
54
|
+
const argv = tree
|
|
55
|
+
? ["taskkill", "/PID", String(pid), "/T", "/F"]
|
|
56
|
+
: ["taskkill", "/PID", String(pid), "/F"];
|
|
57
|
+
const r = await deps.run(argv, 5000);
|
|
58
|
+
if (r.code !== 0 || r.timedOut) {
|
|
59
|
+
throw new Error((r.stderr || r.stdout || `taskkill exited ${r.timedOut ? "on timeout" : r.code}`).trim());
|
|
60
|
+
}
|
|
61
|
+
return { pid, signal, tree, method: "taskkill", signalled: [pid] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const posix = `SIG${signal}` as NodeJS.Signals;
|
|
65
|
+
if (!tree) {
|
|
66
|
+
deps.signal(pid, posix);
|
|
67
|
+
return { pid, signal, tree: false, method: "signal", signalled: [pid] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Enumerate BEFORE signalling: SIGKILL on the root reparents its children to
|
|
71
|
+
// init, and the tree can no longer be recovered afterwards. Children are
|
|
72
|
+
// signalled first so a parent cannot fork a replacement mid-sweep.
|
|
73
|
+
const pids = deps.collectTree(pid);
|
|
74
|
+
const signalled: number[] = [];
|
|
75
|
+
for (const target of pids) {
|
|
76
|
+
// A process exiting between the walk and the signal is the normal case here,
|
|
77
|
+
// not a failure of the request — it is simply not in the result.
|
|
78
|
+
try {
|
|
79
|
+
deps.signal(target, posix);
|
|
80
|
+
signalled.push(target);
|
|
81
|
+
} catch { /* already gone */ }
|
|
82
|
+
}
|
|
83
|
+
return { pid, signal, tree: true, method: "signal", signalled };
|
|
84
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signal decision chain, transport-free like the kill one and deliberately
|
|
3
|
+
* built from the SAME parts: parse → live re-query → identity check → guard on
|
|
4
|
+
* the FRESH name → execute.
|
|
5
|
+
*
|
|
6
|
+
* The guard is `checkKillAllowed` unchanged. A SIGSTOP on systemd wedges the
|
|
7
|
+
* machine as thoroughly as a SIGKILL would, and SIGSTOP on PPM's own supervisor
|
|
8
|
+
* hangs every session — "can this process be signalled" and "can this process be
|
|
9
|
+
* ended" are the same question, so they must not be two lists that can drift.
|
|
10
|
+
*/
|
|
11
|
+
import type {
|
|
12
|
+
MetricsPlatform, ProcessSignal, SignalProcessRequest, SignalProcessResult,
|
|
13
|
+
} from "../../types/system-metrics.ts";
|
|
14
|
+
import { PROCESS_SIGNALS } from "../../types/system-metrics.ts";
|
|
15
|
+
import { ok, err, type ApiResponse } from "../../types/api.ts";
|
|
16
|
+
import type { ProcessCollector } from "./process-collector-types.ts";
|
|
17
|
+
import { identityMatches, resolveLiveProcess } from "./kill-identity-resolver.ts";
|
|
18
|
+
import { checkKillAllowed } from "./kill-guard.ts";
|
|
19
|
+
import type { ProtectedPids } from "./ppm-protected-pids.ts";
|
|
20
|
+
|
|
21
|
+
export interface SignalHandlerDeps {
|
|
22
|
+
platform: MetricsPlatform;
|
|
23
|
+
collector: ProcessCollector;
|
|
24
|
+
resolveProtected: (isAlive: (pid: number) => boolean, nameOf: (pid: number) => string | undefined) => ProtectedPids;
|
|
25
|
+
execute: (pid: number, signal: ProcessSignal, tree: boolean) => Promise<SignalProcessResult>;
|
|
26
|
+
/** Signals this host can deliver; anything else is a 400 before any re-query. */
|
|
27
|
+
supported: readonly ProcessSignal[];
|
|
28
|
+
/** Audit line: pid + name + signal + result ONLY. `~/.ppm/ppm.log`'s tail is
|
|
29
|
+
* served unauthenticated by `/api/logs/recent`, so no command line here. */
|
|
30
|
+
log: (line: string) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type SignalStatus = 200 | 400 | 403 | 404 | 409 | 500;
|
|
34
|
+
|
|
35
|
+
export interface SignalOutcome {
|
|
36
|
+
status: SignalStatus;
|
|
37
|
+
body: ApiResponse<SignalProcessResult>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function parseSignalRequest(body: unknown): SignalProcessRequest | null {
|
|
41
|
+
if (!body || typeof body !== "object") return null;
|
|
42
|
+
const b = body as Record<string, unknown>;
|
|
43
|
+
if (typeof b.pid !== "number" || !Number.isInteger(b.pid) || b.pid <= 0) return null;
|
|
44
|
+
if (typeof b.startedAt !== "number" || !Number.isFinite(b.startedAt) || b.startedAt < 0) return null;
|
|
45
|
+
if (b.tree !== undefined && typeof b.tree !== "boolean") return null;
|
|
46
|
+
if (typeof b.signal !== "string" || !PROCESS_SIGNALS.includes(b.signal as ProcessSignal)) return null;
|
|
47
|
+
return { pid: b.pid, startedAt: b.startedAt, signal: b.signal as ProcessSignal, tree: b.tree === true };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function handleSignalRequest(body: unknown, deps: SignalHandlerDeps): Promise<SignalOutcome> {
|
|
51
|
+
const req = parseSignalRequest(body);
|
|
52
|
+
if (!req) {
|
|
53
|
+
return { status: 400, body: err(`Body must be {pid, startedAt, signal: one of ${PROCESS_SIGNALS.join("|")}, tree?}`) };
|
|
54
|
+
}
|
|
55
|
+
if (!deps.supported.includes(req.signal)) {
|
|
56
|
+
return { status: 400, body: err(`This host cannot deliver SIG${req.signal}`) };
|
|
57
|
+
}
|
|
58
|
+
const tree = req.tree === true;
|
|
59
|
+
|
|
60
|
+
const { live, maps } = await resolveLiveProcess(req.pid, deps.collector);
|
|
61
|
+
if (!live) return { status: 404, body: err(`PID ${req.pid} is no longer running`) };
|
|
62
|
+
if (!identityMatches(req.startedAt, live.startedAt)) {
|
|
63
|
+
return { status: 409, body: err(`PID ${req.pid} was recycled — refresh and try again`) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const protectedPids = deps.resolveProtected(
|
|
67
|
+
(pid) => maps.byPid.has(pid),
|
|
68
|
+
(pid) => maps.byPid.get(pid)?.name.toLowerCase(),
|
|
69
|
+
);
|
|
70
|
+
const verdict = checkKillAllowed({ pid: live.pid, name: live.name }, tree, {
|
|
71
|
+
platform: deps.platform,
|
|
72
|
+
protectedPids: protectedPids.pids,
|
|
73
|
+
ppidOf: maps.ppidOf,
|
|
74
|
+
startedAtOf: maps.startedAtOf,
|
|
75
|
+
});
|
|
76
|
+
const prefix = `[SystemMetrics] signal=${req.signal} pid=${live.pid} name=${live.name} tree=${tree}`;
|
|
77
|
+
if (!verdict.allowed) {
|
|
78
|
+
deps.log(`${prefix} → refused: ${verdict.reason}`);
|
|
79
|
+
return { status: 403, body: err(verdict.reason ?? "Refused") };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
deps.log(`${prefix} → allowed`);
|
|
83
|
+
try {
|
|
84
|
+
const result = await deps.execute(live.pid, req.signal, tree);
|
|
85
|
+
deps.log(`${prefix} → done (${result.signalled.length} signalled)`);
|
|
86
|
+
return { status: 200, body: ok(result) };
|
|
87
|
+
} catch (e) {
|
|
88
|
+
const message = (e as Error)?.message ?? String(e);
|
|
89
|
+
deps.log(`${prefix} → failed: ${message}`);
|
|
90
|
+
return { status: 500, body: err(`Failed to signal PID ${live.pid}: ${message}`) };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/** Per-OS collector wiring for the snapshot service. Nothing here spawns until
|
|
2
2
|
* a full-tier tick actually calls a collector. */
|
|
3
3
|
import type { MetricsPlatform } from "../../types/system-metrics.ts";
|
|
4
|
+
import type { AppCollector } from "../system-services/apps-linux.ts";
|
|
5
|
+
import { createLinuxAppCollector } from "../system-services/apps-linux.ts";
|
|
4
6
|
import type { ProcessCollector } from "./process-collector-types.ts";
|
|
5
7
|
import { EMPTY_PROCESS_COLLECTOR } from "./process-collector-types.ts";
|
|
6
8
|
import type { DiskNetCounters } from "./disk-net-collector-linux.ts";
|
|
9
|
+
import type { DeviceCollector } from "./device-collector-types.ts";
|
|
10
|
+
import { collectLinuxDevices } from "./device-collector-linux.ts";
|
|
11
|
+
import { createDrmGpuCollector } from "./gpu-fdinfo-linux.ts";
|
|
7
12
|
import { collectLinuxDiskNet } from "./disk-net-collector-linux.ts";
|
|
8
13
|
import { collectDarwinDiskNet } from "./disk-net-collector-darwin.ts";
|
|
9
14
|
import { createNvidiaGpuCollector, type GpuCollector } from "./gpu-collector-nvidia.ts";
|
|
@@ -20,6 +25,12 @@ export interface PlatformCollectors {
|
|
|
20
25
|
/** Null on win32: the counters ride along in the process round trip. */
|
|
21
26
|
diskNet: (() => Promise<DiskNetCounters>) | null;
|
|
22
27
|
gpus: GpuCollector;
|
|
28
|
+
/** Per-drive and per-interface figures. Null where the host has no source for
|
|
29
|
+
* them, which the client reads as "this machine lists no devices". */
|
|
30
|
+
devices: DeviceCollector | null;
|
|
31
|
+
/** Desktop applications with a live process. Null off Linux, which has neither
|
|
32
|
+
* .desktop entries in the XDG sense nor app cgroups to read them from. */
|
|
33
|
+
apps: AppCollector | null;
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
export function toMetricsPlatform(p: NodeJS.Platform = process.platform): MetricsPlatform {
|
|
@@ -30,22 +41,37 @@ export function createPlatformCollectors(platform: MetricsPlatform = toMetricsPl
|
|
|
30
41
|
const gpus = createNvidiaGpuCollector();
|
|
31
42
|
switch (platform) {
|
|
32
43
|
case "win32":
|
|
33
|
-
return { platform, processes: createWindowsProcessCollector(), diskNet: null, gpus };
|
|
44
|
+
return { platform, processes: createWindowsProcessCollector(), diskNet: null, gpus, devices: null, apps: null };
|
|
34
45
|
case "darwin":
|
|
35
46
|
return {
|
|
36
47
|
platform,
|
|
37
48
|
processes: createDarwinProcessCollector(undefined, undefined, { net: createDarwinProcessNetCollector() }),
|
|
38
49
|
diskNet: () => collectDarwinDiskNet(),
|
|
39
50
|
gpus,
|
|
51
|
+
devices: null,
|
|
52
|
+
apps: null,
|
|
40
53
|
};
|
|
41
|
-
case "linux":
|
|
54
|
+
case "linux": {
|
|
55
|
+
// ONE collector for both: the whole-GPU figures and the process rows are
|
|
56
|
+
// built from the same `/proc` walk, so they can never disagree, and the
|
|
57
|
+
// walk is paid for once per tick rather than twice.
|
|
58
|
+
const drm = createDrmGpuCollector();
|
|
59
|
+
// Its desktop-entry scan is lazy, so a host where nobody opens the Apps
|
|
60
|
+
// page never pays for it.
|
|
61
|
+
const apps = createLinuxAppCollector();
|
|
42
62
|
return {
|
|
43
63
|
platform,
|
|
44
|
-
processes: createLinuxProcessCollector(readProcTable, {
|
|
64
|
+
processes: createLinuxProcessCollector(readProcTable, {
|
|
65
|
+
gpuMemory: createNvidiaProcessMemoryCollector(),
|
|
66
|
+
drm,
|
|
67
|
+
}),
|
|
45
68
|
diskNet: async () => collectLinuxDiskNet(),
|
|
46
69
|
gpus,
|
|
70
|
+
devices: (prev) => collectLinuxDevices(prev, drm),
|
|
71
|
+
apps: (processes) => apps.collect(processes),
|
|
47
72
|
};
|
|
73
|
+
}
|
|
48
74
|
default:
|
|
49
|
-
return { platform, processes: EMPTY_PROCESS_COLLECTOR, diskNet: null, gpus };
|
|
75
|
+
return { platform, processes: EMPTY_PROCESS_COLLECTOR, diskNet: null, gpus, devices: null, apps: null };
|
|
50
76
|
}
|
|
51
77
|
}
|