@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
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
* can then never leave a half-advanced baseline behind.
|
|
6
6
|
*/
|
|
7
7
|
import type {
|
|
8
|
-
MetricsPlatform, MetricsSnapshot, MetricsTier, MemoryMetrics, ProcessColumnAvailability, SystemMetrics,
|
|
8
|
+
MetricsPlatform, MetricsSnapshot, MetricsTier, MemoryMetrics, ProcessColumnAvailability, ProcessSignal, SystemMetrics,
|
|
9
9
|
} from "../../types/system-metrics.ts";
|
|
10
10
|
import { METRICS_INTERVAL_MS, METRICS_LIGHT_INTERVAL_MS } from "../../types/system-metrics.ts";
|
|
11
|
-
import { computeCpuFromSamples, type CpuTimesSample } from "./cpu-memory-collector.ts";
|
|
11
|
+
import { computeCpuFromSamples, hostUptimeSec, type CpuTimesSample } from "./cpu-memory-collector.ts";
|
|
12
12
|
import { toRate, UNAVAILABLE_RATE, type CounterSample } from "./rate-delta.ts";
|
|
13
13
|
import type { DiskNetCounters } from "./disk-net-collector-linux.ts";
|
|
14
|
+
import type { AppInfo } from "../../types/system-metrics.ts";
|
|
15
|
+
import type { AppCollector } from "../system-services/apps-linux.ts";
|
|
16
|
+
import type { DeviceCollector, DeviceSampleState } from "./device-collector-types.ts";
|
|
17
|
+
import { EMPTY_DEVICE_STATE } from "./device-collector-types.ts";
|
|
14
18
|
import type { GpuCollector } from "./gpu-collector-nvidia.ts";
|
|
15
19
|
import type { ProcessCollection, ProcessCollector } from "./process-collector-types.ts";
|
|
16
20
|
import { NO_PROCESS_COLUMNS } from "./process-collector-types.ts";
|
|
@@ -26,9 +30,13 @@ export interface TickDeltaState {
|
|
|
26
30
|
net: CounterSample | null;
|
|
27
31
|
procCpu: CpuDeltaState | null;
|
|
28
32
|
procIo: ProcIoDeltaState | null;
|
|
33
|
+
/** Per-device counters, keyed by device id — one baseline each. */
|
|
34
|
+
devices: DeviceSampleState;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
export const EMPTY_DELTA_STATE: TickDeltaState = {
|
|
37
|
+
export const EMPTY_DELTA_STATE: TickDeltaState = {
|
|
38
|
+
cpu: null, disk: null, net: null, procCpu: null, procIo: null, devices: EMPTY_DEVICE_STATE,
|
|
39
|
+
};
|
|
32
40
|
|
|
33
41
|
export interface TickDeps {
|
|
34
42
|
platform: MetricsPlatform;
|
|
@@ -37,6 +45,14 @@ export interface TickDeps {
|
|
|
37
45
|
/** Null on win32 — the counters ride along in the process round trip. */
|
|
38
46
|
diskNet: (() => Promise<DiskNetCounters>) | null;
|
|
39
47
|
gpus: GpuCollector;
|
|
48
|
+
/** Null where this host lists no drives or interfaces of its own. */
|
|
49
|
+
devices: DeviceCollector | null;
|
|
50
|
+
/** Signals this host can actually deliver, published so the client builds its
|
|
51
|
+
* Send-Signal menu from the host instead of guessing from the platform name.
|
|
52
|
+
* Omitted → published as absent, which an older client already tolerates. */
|
|
53
|
+
signals?: readonly ProcessSignal[];
|
|
54
|
+
/** Desktop applications. Omitted or null on a host that lists none. */
|
|
55
|
+
apps?: AppCollector | null;
|
|
40
56
|
resolveProtected: BuildRowsInput["resolveProtected"];
|
|
41
57
|
now: () => number;
|
|
42
58
|
sampleCpu: (now: number) => CpuTimesSample;
|
|
@@ -65,6 +81,9 @@ export async function assembleTick(
|
|
|
65
81
|
const now = deps.now();
|
|
66
82
|
const cpuSample = deps.sampleCpu(now);
|
|
67
83
|
const cpu = computeCpuFromSamples(state.cpu, cpuSample);
|
|
84
|
+
// Set before `collectDevices`, which merges Linux's own `/proc/uptime` over it.
|
|
85
|
+
const uptimeSec = hostUptimeSec();
|
|
86
|
+
if (uptimeSec !== undefined) cpu.uptimeSec = uptimeSec;
|
|
68
87
|
const mem = deps.memory();
|
|
69
88
|
const warnings: string[] = [];
|
|
70
89
|
|
|
@@ -75,6 +94,9 @@ export async function assembleTick(
|
|
|
75
94
|
let groups: MetricsSnapshot["groups"] = [];
|
|
76
95
|
let processes: MetricsSnapshot["processes"] = [];
|
|
77
96
|
let processColumns: ProcessColumnAvailability = NO_PROCESS_COLUMNS;
|
|
97
|
+
// Undefined means "this server does not list apps"; an empty array means "it
|
|
98
|
+
// does, and none are running". The UI hides the page only for the first.
|
|
99
|
+
let apps: AppInfo[] | undefined;
|
|
78
100
|
let guardCtx: KillGuardContext | null = null;
|
|
79
101
|
|
|
80
102
|
if (tier === "full") {
|
|
@@ -89,6 +111,7 @@ export async function assembleTick(
|
|
|
89
111
|
nextState.net = counters.net;
|
|
90
112
|
}
|
|
91
113
|
system.gpus = await deps.gpus.collect();
|
|
114
|
+
collectDevices(system, nextState, deps, warnings);
|
|
92
115
|
|
|
93
116
|
if (collection) {
|
|
94
117
|
const built = buildProcessRows({
|
|
@@ -113,6 +136,14 @@ export async function assembleTick(
|
|
|
113
136
|
// Re-published rows keep the columns they were measured with.
|
|
114
137
|
processColumns = previousFull.processColumns;
|
|
115
138
|
}
|
|
139
|
+
if (collection && deps.apps) {
|
|
140
|
+
try {
|
|
141
|
+
apps = deps.apps(collection.rows);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
// One unreadable cgroup must cost the app list, never the snapshot.
|
|
144
|
+
warnings.push(`App list unavailable this tick: ${(e as Error)?.message ?? String(e)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
116
147
|
system.processCount = processes.length;
|
|
117
148
|
}
|
|
118
149
|
|
|
@@ -127,6 +158,10 @@ export async function assembleTick(
|
|
|
127
158
|
processColumns,
|
|
128
159
|
total: { cpu: cpu.total, ramMB: mem.usedMB, processCount: system.processCount },
|
|
129
160
|
warnings,
|
|
161
|
+
// Only in the tier that has process rows to act on, and only when this host
|
|
162
|
+
// can deliver something — an empty menu is the same information as no menu.
|
|
163
|
+
signals: tier === "full" && deps.signals?.length ? [...deps.signals] : undefined,
|
|
164
|
+
apps,
|
|
130
165
|
};
|
|
131
166
|
return { snapshot, nextState, guardCtx };
|
|
132
167
|
}
|
|
@@ -136,14 +171,48 @@ export function projectLight(full: MetricsSnapshot): MetricsSnapshot {
|
|
|
136
171
|
return {
|
|
137
172
|
...full,
|
|
138
173
|
tier: "light",
|
|
139
|
-
system: {
|
|
174
|
+
system: {
|
|
175
|
+
...full.system,
|
|
176
|
+
disk: UNAVAILABLE_RATE, net: UNAVAILABLE_RATE, gpus: [], processCount: 0,
|
|
177
|
+
disks: undefined, nics: undefined, fans: undefined,
|
|
178
|
+
},
|
|
140
179
|
groups: [],
|
|
141
180
|
processes: [],
|
|
142
181
|
processColumns: NO_PROCESS_COLUMNS,
|
|
182
|
+
// Everything that only means something beside a process row goes with them.
|
|
183
|
+
signals: undefined,
|
|
184
|
+
apps: undefined,
|
|
143
185
|
total: { ...full.total, processCount: 0 },
|
|
144
186
|
};
|
|
145
187
|
}
|
|
146
188
|
|
|
189
|
+
/** Per-device figures are best-effort: a `/sys` read failing must cost the drive
|
|
190
|
+
* and interface lists, never the whole snapshot. The baseline is only advanced
|
|
191
|
+
* when the call returned, so a failed tick re-measures from the last good one. */
|
|
192
|
+
function collectDevices(
|
|
193
|
+
system: SystemMetrics,
|
|
194
|
+
nextState: TickDeltaState,
|
|
195
|
+
deps: TickDeps,
|
|
196
|
+
warnings: string[],
|
|
197
|
+
): void {
|
|
198
|
+
if (!deps.devices) return;
|
|
199
|
+
try {
|
|
200
|
+
const collected = deps.devices(nextState.devices);
|
|
201
|
+
system.disks = collected.disks;
|
|
202
|
+
system.nics = collected.nics;
|
|
203
|
+
if (collected.fans.length > 0) system.fans = collected.fans;
|
|
204
|
+
// Appended, not replaced: nvidia-smi covers the one driver sysfs cannot, and
|
|
205
|
+
// `collectLinuxGpus` deliberately skips those cards so neither is listed twice.
|
|
206
|
+
if (collected.gpus.length > 0) system.gpus = [...system.gpus, ...collected.gpus];
|
|
207
|
+
// Merged, never assigned: `cpu` already holds the cross-platform busy figures
|
|
208
|
+
// computed from the os.cpus() delta, which this collector does not measure.
|
|
209
|
+
Object.assign(system.cpu, collected.cpu);
|
|
210
|
+
nextState.devices = collected.next;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
warnings.push(`Per-device figures unavailable this tick: ${(e as Error)?.message ?? String(e)}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
147
216
|
/** Null means the collection FAILED (as opposed to a legitimately empty table). */
|
|
148
217
|
async function collectProcessesSafely(collector: ProcessCollector, warnings: string[]): Promise<ProcessCollection | null> {
|
|
149
218
|
try {
|
|
@@ -9,16 +9,23 @@
|
|
|
9
9
|
* queries run only while ≥1 full subscriber exists, and the child is torn down
|
|
10
10
|
* 60 s after the last one leaves (a window reopen then skips the bootstrap).
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
KillProcessResult, MetricsPlatform, MetricsSnapshot, MetricsTier, ProcessDetails, ProcessSignal, SignalProcessResult,
|
|
14
|
+
} from "../../types/system-metrics.ts";
|
|
13
15
|
import { METRICS_INTERVAL_MS, METRICS_LEASE_TIMEOUT_MS, METRICS_LIGHT_INTERVAL_MS } from "../../types/system-metrics.ts";
|
|
14
16
|
import { collectMemory, sampleCpuTimes } from "./cpu-memory-collector.ts";
|
|
15
17
|
import { assembleTick, EMPTY_DELTA_STATE, projectLight, type TickDeltaState, type TickDeps } from "./system-metrics-tick.ts";
|
|
18
|
+
import { EMPTY_DEVICE_STATE } from "./device-collector-types.ts";
|
|
16
19
|
import { createPlatformCollectors, type PlatformCollectors } from "./system-metrics-platform.ts";
|
|
17
20
|
import { resolveProtectedPidsLive } from "./ppm-protected-pids.ts";
|
|
18
21
|
import { SubscriberRegistry, type MetricsSubscriber } from "./metrics-subscriber-registry.ts";
|
|
19
22
|
import { PollScheduler, installProcessExitHooks } from "./metrics-poll-scheduler.ts";
|
|
20
23
|
import { handleKillRequest, type KillOutcome } from "./kill-request-handler.ts";
|
|
21
24
|
import { executeKill } from "./kill-executor.ts";
|
|
25
|
+
import { handleSignalRequest, type SignalOutcome } from "./signal-request-handler.ts";
|
|
26
|
+
import { executeSignal, supportedSignals } from "./signal-executor.ts";
|
|
27
|
+
import { readProcessDetails } from "./process-details-linux.ts";
|
|
28
|
+
import { readProcessDetailsDarwin } from "./process-details-darwin.ts";
|
|
22
29
|
import { CollectorLock } from "./metrics-collector-lock.ts";
|
|
23
30
|
|
|
24
31
|
/** Over a proxy a closed window's lease lives until the reap; 8 leaves room for
|
|
@@ -35,11 +42,27 @@ export interface SystemMetricsServiceOptions {
|
|
|
35
42
|
now?: () => number;
|
|
36
43
|
resolveProtected?: TickDeps["resolveProtected"];
|
|
37
44
|
execute?: (pid: number, tree: boolean) => Promise<KillProcessResult>;
|
|
45
|
+
executeSignal?: (pid: number, signal: ProcessSignal, tree: boolean) => Promise<SignalProcessResult>;
|
|
46
|
+
/** Injected in tests so a unit test never reads the real /proc. Explicit
|
|
47
|
+
* `null` models a host with no reader for this at all. */
|
|
48
|
+
details?: DetailsReader | null;
|
|
38
49
|
log?: (line: string) => void;
|
|
39
50
|
/** Register process exit/signal teardown of children (off in unit tests). */
|
|
40
51
|
exitHooks?: boolean;
|
|
41
52
|
}
|
|
42
53
|
|
|
54
|
+
/** A reader may answer synchronously (Linux reads files) or not (macOS shells
|
|
55
|
+
* out), so the service awaits whatever it gets. */
|
|
56
|
+
export type DetailsReader = (pid: number) => ProcessDetails | null | Promise<ProcessDetails | null>;
|
|
57
|
+
|
|
58
|
+
/** Null where PPM has no reader yet — currently win32, whose Details dialog says
|
|
59
|
+
* so rather than reporting every process as exited. */
|
|
60
|
+
function detailsReaderFor(platform: MetricsPlatform): DetailsReader | null {
|
|
61
|
+
if (platform === "linux") return (pid) => readProcessDetails(pid);
|
|
62
|
+
if (platform === "darwin") return (pid) => readProcessDetailsDarwin(pid);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
export type SubscribeInit = Pick<MetricsSubscriber, "tier" | "deliver" | "close">;
|
|
44
67
|
export interface SubscribeResult { sid: string; tier: MetricsTier; intervalMs: number }
|
|
45
68
|
|
|
@@ -52,6 +75,8 @@ export class SystemMetricsService {
|
|
|
52
75
|
private readonly now: () => number;
|
|
53
76
|
private readonly tickDeps: TickDeps;
|
|
54
77
|
private readonly execute: NonNullable<SystemMetricsServiceOptions["execute"]>;
|
|
78
|
+
private readonly signalExecutor: NonNullable<SystemMetricsServiceOptions["executeSignal"]>;
|
|
79
|
+
private readonly details: DetailsReader | null;
|
|
55
80
|
private readonly log: (line: string) => void;
|
|
56
81
|
private readonly exitHooks: boolean;
|
|
57
82
|
|
|
@@ -66,6 +91,8 @@ export class SystemMetricsService {
|
|
|
66
91
|
this.leaseTimeoutMs = opts.leaseTimeoutMs ?? METRICS_LEASE_TIMEOUT_MS;
|
|
67
92
|
this.now = opts.now ?? Date.now;
|
|
68
93
|
this.execute = opts.execute ?? executeKill;
|
|
94
|
+
this.signalExecutor = opts.executeSignal ?? executeSignal;
|
|
95
|
+
this.details = opts.details !== undefined ? opts.details : detailsReaderFor(this.collectors.platform);
|
|
69
96
|
this.log = opts.log ?? ((line) => console.log(line));
|
|
70
97
|
this.exitHooks = opts.exitHooks ?? true;
|
|
71
98
|
this.tickDeps = {
|
|
@@ -74,6 +101,9 @@ export class SystemMetricsService {
|
|
|
74
101
|
processes: this.collectors.processes,
|
|
75
102
|
diskNet: this.collectors.diskNet,
|
|
76
103
|
gpus: this.collectors.gpus,
|
|
104
|
+
devices: this.collectors.devices,
|
|
105
|
+
signals: this.signalMenu(),
|
|
106
|
+
apps: this.collectors.apps,
|
|
77
107
|
resolveProtected: opts.resolveProtected ?? resolveProtectedPidsLive,
|
|
78
108
|
now: this.now,
|
|
79
109
|
sampleCpu: sampleCpuTimes,
|
|
@@ -152,6 +182,59 @@ export class SystemMetricsService {
|
|
|
152
182
|
}
|
|
153
183
|
}
|
|
154
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Delivers one signal, through exactly the chain a kill uses — including the
|
|
187
|
+
* collector lock, because it re-collects the process table to authorise
|
|
188
|
+
* against a FRESH name and the PowerShell session takes one request at a time.
|
|
189
|
+
*/
|
|
190
|
+
async signal(body: unknown): Promise<SignalOutcome> {
|
|
191
|
+
this.ensureExitHooks();
|
|
192
|
+
try {
|
|
193
|
+
return await this.collectorLock.runExclusive(() => handleSignalRequest(body, {
|
|
194
|
+
platform: this.collectors.platform,
|
|
195
|
+
collector: this.collectors.processes,
|
|
196
|
+
resolveProtected: this.tickDeps.resolveProtected,
|
|
197
|
+
execute: this.signalExecutor,
|
|
198
|
+
supported: this.signalMenu(),
|
|
199
|
+
log: this.log,
|
|
200
|
+
}));
|
|
201
|
+
} finally {
|
|
202
|
+
// The re-query may have started the PowerShell child with no window open.
|
|
203
|
+
if (this.registry.count("full") === 0) this.scheduler.armIdle();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Facts for the Details dialog. Deliberately NOT behind the collector lock: it
|
|
209
|
+
* is a handful of /proc reads sharing nothing with a tick or a kill, and
|
|
210
|
+
* queueing it there would make the dialog wait out a 174 ms Windows tick for
|
|
211
|
+
* data that tick does not even produce.
|
|
212
|
+
*/
|
|
213
|
+
async processDetails(pid: number): Promise<ProcessDetails | null> {
|
|
214
|
+
return this.details ? await this.details(pid) : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Whether this host has any reader for that dialog at all.
|
|
219
|
+
*
|
|
220
|
+
* The caller needs it because `null` from `processDetails` would otherwise
|
|
221
|
+
* carry two unrelated meanings: "that pid has exited" and "PPM cannot read
|
|
222
|
+
* process details here". Collapsing them is what made the dialog tell a macOS
|
|
223
|
+
* user that a process consuming 4% of the CPU was no longer running.
|
|
224
|
+
*/
|
|
225
|
+
supportsProcessDetails(): boolean {
|
|
226
|
+
return this.details !== null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** What this host can deliver. Read through a plain string because the platform
|
|
230
|
+
* union says which COLLECTOR runs, not whether signals exist. */
|
|
231
|
+
private signalMenu(): ProcessSignal[] {
|
|
232
|
+
const p: string = this.collectors.platform;
|
|
233
|
+
if (p === "linux" || p === "darwin") return supportedSignals("linux");
|
|
234
|
+
if (p === "win32") return supportedSignals("win32");
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
|
|
155
238
|
/** One poll: assemble for the active tier, commit the baseline, publish. */
|
|
156
239
|
async runTick(): Promise<void> {
|
|
157
240
|
// A Windows tick is ~174 ms and can slip, and a kill re-collects through the
|
|
@@ -196,7 +279,9 @@ export class SystemMetricsService {
|
|
|
196
279
|
const changed = this.scheduler.reconcile(this.registry.count("full"), this.registry.count("light"));
|
|
197
280
|
// Leaving the full tier: its baselines would be stale by the time it resumes.
|
|
198
281
|
if (changed && this.scheduler.activeTier() !== "full") {
|
|
199
|
-
this.state = {
|
|
282
|
+
this.state = {
|
|
283
|
+
...this.state, procCpu: null, procIo: null, disk: null, net: null, devices: EMPTY_DEVICE_STATE,
|
|
284
|
+
};
|
|
200
285
|
}
|
|
201
286
|
}
|
|
202
287
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* systemd-udevd's device database, `/run/udev/data/<device>`: one `E:KEY=VALUE`
|
|
3
|
+
* line per property, world-readable. It already holds what `lspci`, `lsblk` and
|
|
4
|
+
* `dmidecode` would be spawned for — adapter names from the PCI/USB hwdb, drive
|
|
5
|
+
* models and serials, and DIMM facts from the SMBIOS table, whose raw file
|
|
6
|
+
* (/sys/firmware/dmi/tables/DMI) is root-only. Reading it costs one small file
|
|
7
|
+
* read and needs neither root nor a subprocess.
|
|
8
|
+
*
|
|
9
|
+
* Device file names: `+pci:<address>`, `b<major>:<minor>` for a block device,
|
|
10
|
+
* `n<ifindex>` for a network interface, `+dmi:id` for the SMBIOS table.
|
|
11
|
+
*/
|
|
12
|
+
import type { FileReader } from "./linux-fs.ts";
|
|
13
|
+
|
|
14
|
+
export const UDEV_DATA_DIR = "/run/udev/data";
|
|
15
|
+
|
|
16
|
+
/** `E:` lines only; values are taken verbatim (udev already decoded `\x20`-style
|
|
17
|
+
* escapes into the plain keys, the `_ENC` variants keep them). */
|
|
18
|
+
export function parseUdevProperties(text: string): Map<string, string> {
|
|
19
|
+
const props = new Map<string, string>();
|
|
20
|
+
for (const line of text.split("\n")) {
|
|
21
|
+
if (!line.startsWith("E:")) continue;
|
|
22
|
+
const eq = line.indexOf("=", 2);
|
|
23
|
+
if (eq < 0) continue;
|
|
24
|
+
props.set(line.slice(2, eq), line.slice(eq + 1));
|
|
25
|
+
}
|
|
26
|
+
return props;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Properties of one device, or an empty map when udev has no record of it (no
|
|
30
|
+
* udevd in a container, a device that appeared before udev started). */
|
|
31
|
+
export function readUdevProperties(device: string, read: FileReader): Map<string, string> {
|
|
32
|
+
const text = read(`${UDEV_DATA_DIR}/${device}`);
|
|
33
|
+
return text ? parseUdevProperties(text) : new Map();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A non-empty trimmed value, or undefined — hwdb fields are sometimes padded. */
|
|
37
|
+
export function udevValue(props: ReadonlyMap<string, string>, key: string): string | undefined {
|
|
38
|
+
const v = props.get(key)?.trim();
|
|
39
|
+
return v ? v : undefined;
|
|
40
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which desktop app a pid belongs to, read from its systemd app cgroup.
|
|
3
|
+
*
|
|
4
|
+
* This is why PPM's Apps page needs no heuristics: a graphical session started by
|
|
5
|
+
* systemd puts every app in its own app scope or app service, and the unit name
|
|
6
|
+
* states the desktop-entry id rather than leaving it to be inferred from an
|
|
7
|
+
* executable path. Measured on a 560-process desktop, reading every
|
|
8
|
+
* `/proc/<pid>/cgroup` costs 2.8 ms, which the 2 s tick can afford.
|
|
9
|
+
*
|
|
10
|
+
* Pure. The caller supplies the file text.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** systemd escapes a character it cannot put in a unit name as a hex escape. */
|
|
14
|
+
export function unescapeSystemd(value: string): string {
|
|
15
|
+
return value.replace(/\\x([0-9a-fA-F]{2})/g, (_match, hex: string) =>
|
|
16
|
+
String.fromCharCode(Number.parseInt(hex, 16)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The desktop-entry id owning this pid, or null when it is not in an app scope -
|
|
21
|
+
* a daemon, a kernel thread, anything started before the session.
|
|
22
|
+
*
|
|
23
|
+
* Four shapes appear on a real KDE/systemd desktop and all are handled here: a
|
|
24
|
+
* scope with the launching pid appended, a scope whose id contains dots, a
|
|
25
|
+
* service with a launch hash after an at-sign, and an autostart service whose id
|
|
26
|
+
* was hex-escaped.
|
|
27
|
+
*
|
|
28
|
+
* The OUTERMOST app unit wins: an app service can contain further scopes, and the
|
|
29
|
+
* wrapper is the one naming the app.
|
|
30
|
+
*/
|
|
31
|
+
export function appIdFromCgroup(cgroup: string): string | null {
|
|
32
|
+
const match = /(?:^|\/)app-([^/]+?)\.(?:scope|service)(?:\/|$)/m.exec(cgroup);
|
|
33
|
+
const raw = match?.[1];
|
|
34
|
+
if (!raw) return null;
|
|
35
|
+
const at = raw.indexOf("@");
|
|
36
|
+
// The at-sign introduces systemd's instance part (a launch hash, or the word
|
|
37
|
+
// autostart); without one the launcher appends the pid instead. Never both.
|
|
38
|
+
const trimmed = at >= 0 ? raw.slice(0, at) : raw.replace(/-\d+$/, "");
|
|
39
|
+
const id = unescapeSystemd(trimmed).trim();
|
|
40
|
+
return id || null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Unit suffixes that can own processes. `.slice` is deliberately absent: a slice
|
|
45
|
+
* is a grouping, and attributing a pid to `user.slice` would roll every user
|
|
46
|
+
* process into one row that names nothing anybody can act on.
|
|
47
|
+
*/
|
|
48
|
+
const UNIT_SUFFIXES = [".service", ".scope", ".socket", ".mount", ".swap"] as const;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The systemd unit owning this pid, suffix included, or null when it is in no
|
|
52
|
+
* unit at all (the root slice, a kernel thread).
|
|
53
|
+
*
|
|
54
|
+
* The INNERMOST unit wins, which is `systemd-cgls`'s own answer: a user service
|
|
55
|
+
* nests under `user@1000.service`, and reporting the wrapper would put every
|
|
56
|
+
* desktop process on one row.
|
|
57
|
+
*
|
|
58
|
+
* The name is taken verbatim, escapes included — `systemctl list-units` reports
|
|
59
|
+
* `dev-disk-by\x2duuid-….swap` the same way, and a name unescaped here would
|
|
60
|
+
* match no row on the Services page.
|
|
61
|
+
*/
|
|
62
|
+
export function unitFromCgroup(cgroup: string): string | null {
|
|
63
|
+
const path = systemdHierarchy(cgroup);
|
|
64
|
+
if (!path) return null;
|
|
65
|
+
const segments = path.split("/").filter(Boolean);
|
|
66
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
67
|
+
const segment = segments[i]!;
|
|
68
|
+
if (UNIT_SUFFIXES.some((suffix) => segment.endsWith(suffix))) return segment;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The cgroup path systemd tracks units in. Under v2 that is the single `0::`
|
|
75
|
+
* line; under v1 it is whichever controller line is `name=systemd`, and reading
|
|
76
|
+
* any other one (`cpu`, `memory`) gives a hierarchy that need not agree.
|
|
77
|
+
*/
|
|
78
|
+
export function systemdHierarchy(cgroup: string): string | null {
|
|
79
|
+
for (const line of cgroup.split("\n")) {
|
|
80
|
+
const first = line.indexOf(":");
|
|
81
|
+
if (first < 0) continue;
|
|
82
|
+
const second = line.indexOf(":", first + 1);
|
|
83
|
+
if (second < 0) continue;
|
|
84
|
+
const controllers = line.slice(first + 1, second);
|
|
85
|
+
if (controllers === "" || controllers === "name=systemd") return line.slice(second + 1).trim() || null;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* `"<scope>:<unit>"` — the key the Services page rows are identified by, or null
|
|
92
|
+
* when this pid belongs to no row it can show.
|
|
93
|
+
*
|
|
94
|
+
* The scope is not decoration. `dbus-broker.service` exists in BOTH scopes on
|
|
95
|
+
* this dev host, so a roll-up keyed by the unit name alone would add a user
|
|
96
|
+
* session's processes to the system unit's row and show one figure for two
|
|
97
|
+
* different units.
|
|
98
|
+
*
|
|
99
|
+
* A user unit belonging to somebody ELSE returns null rather than that user's
|
|
100
|
+
* unit name: PPM reads `systemctl --user` as its own user, so another uid's
|
|
101
|
+
* units are not on the page at all, and attributing their processes to the
|
|
102
|
+
* same-named row of ours would be a wrong reading rather than a missing one.
|
|
103
|
+
*
|
|
104
|
+
* Being under `/user.slice/` is NOT what makes a unit a user one — `session-2.scope`
|
|
105
|
+
* and `user@1000.service` itself both live there and both belong to the SYSTEM
|
|
106
|
+
* manager, which is where `systemctl list-units` reports them. Only a path
|
|
107
|
+
* passing THROUGH `user@<uid>.service` is inside that user's own manager.
|
|
108
|
+
*/
|
|
109
|
+
export function serviceKeyFromCgroup(cgroup: string, selfUid: number): string | null {
|
|
110
|
+
const unit = unitFromCgroup(cgroup);
|
|
111
|
+
if (!unit) return null;
|
|
112
|
+
const owner = /\/user@(\d+)\.service\//.exec(systemdHierarchy(cgroup) ?? "");
|
|
113
|
+
if (!owner) return `system:${unit}`;
|
|
114
|
+
return Number(owner[1]) === selfUid ? `user:${unit}` : null;
|
|
115
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App id to an icon file on disk, for the icon route.
|
|
3
|
+
*
|
|
4
|
+
* The security property lives here rather than in the route: the caller supplies
|
|
5
|
+
* an APP ID, this looks up that id's desktop entry, and only the path that
|
|
6
|
+
* entry's own Icon resolved to is ever returned. There is no request shape that
|
|
7
|
+
* names a file, so the route cannot become an arbitrary file read.
|
|
8
|
+
*
|
|
9
|
+
* Both caches are lazy and built once, so a host where nobody opens the Apps page
|
|
10
|
+
* pays nothing: the entry scan measured 9 ms and the icon index 100 ms here.
|
|
11
|
+
*/
|
|
12
|
+
import { realLinuxFs, type LinuxFs } from "../system-metrics/linux-fs.ts";
|
|
13
|
+
import { createLinuxAppCollector } from "./apps-linux.ts";
|
|
14
|
+
import { createIconResolver } from "./app-icons-linux.ts";
|
|
15
|
+
|
|
16
|
+
export interface AppIconService {
|
|
17
|
+
/** Absolute path to the icon file, or null when the app or its icon is unknown. */
|
|
18
|
+
path(appId: string): string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createAppIconService(fs: LinuxFs = realLinuxFs): AppIconService {
|
|
22
|
+
const apps = createLinuxAppCollector(fs);
|
|
23
|
+
const icons = createIconResolver(fs);
|
|
24
|
+
return {
|
|
25
|
+
path(appId: string): string | null {
|
|
26
|
+
if (!appId || appId.includes("/") || appId.includes("..")) return null;
|
|
27
|
+
const entry = apps.entries().get(appId);
|
|
28
|
+
return entry ? icons.resolve(entry.icon) : null;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolving a desktop entry's Icon to a file PPM can serve.
|
|
3
|
+
*
|
|
4
|
+
* An Icon is either an absolute path or a theme icon NAME, and the name is the
|
|
5
|
+
* common case: on this host `code.desktop` says `Icon=vscode`, which exists only
|
|
6
|
+
* under /usr/share/pixmaps, while `org.kde.dolphin` is an SVG under hicolor and
|
|
7
|
+
* `claude-desktop` is a PNG under hicolor's sized directories. A resolver that
|
|
8
|
+
* searches only the current theme finds none of the three.
|
|
9
|
+
*
|
|
10
|
+
* So the whole search path is indexed once, name to path: measured here, 22022
|
|
11
|
+
* names in 29 ms. That is far cheaper than walking per lookup and it is built
|
|
12
|
+
* lazily, on the first icon anyone actually asks for.
|
|
13
|
+
*
|
|
14
|
+
* The index is also the security boundary. The HTTP route never takes a path - it
|
|
15
|
+
* takes an app id, looks the entry up, and serves only what this module resolved,
|
|
16
|
+
* so there is no request shape that can name an arbitrary file.
|
|
17
|
+
*/
|
|
18
|
+
import type { LinuxFs } from "../system-metrics/linux-fs.ts";
|
|
19
|
+
import { realLinuxFs } from "../system-metrics/linux-fs.ts";
|
|
20
|
+
|
|
21
|
+
/** Preference order: a vector icon scales to any row height the UI uses. */
|
|
22
|
+
export const ICON_EXTENSIONS: readonly string[] = [".svg", ".png", ".xpm"];
|
|
23
|
+
|
|
24
|
+
/** How deep a theme nests: theme / context / size / name, plus a little slack. */
|
|
25
|
+
const MAX_DEPTH = 5;
|
|
26
|
+
|
|
27
|
+
export const ICON_MIME: Record<string, string> = {
|
|
28
|
+
".svg": "image/svg+xml",
|
|
29
|
+
".png": "image/png",
|
|
30
|
+
".xpm": "image/x-xpixmap",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function iconRoots(env: Record<string, string | undefined>, home: string): string[] {
|
|
34
|
+
const dataHome = env.XDG_DATA_HOME || `${home}/.local/share`;
|
|
35
|
+
const dataDirs = (env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
|
|
36
|
+
const roots: string[] = [`${home}/.icons`, `${dataHome}/icons`];
|
|
37
|
+
for (const base of dataDirs) roots.push(`${base}/icons`);
|
|
38
|
+
// pixmaps is not a theme and is easy to forget; it is also the ONLY place some
|
|
39
|
+
// very common icons exist, so leaving it out loses them entirely.
|
|
40
|
+
for (const base of dataDirs) roots.push(`${base}/pixmaps`);
|
|
41
|
+
return [...new Set(roots)];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* name to path. An earlier root wins over a later one, and within a root a better
|
|
46
|
+
* extension replaces a worse one - so a theme's SVG beats the same theme's PNG
|
|
47
|
+
* without needing the walk to be ordered by size.
|
|
48
|
+
*/
|
|
49
|
+
export function buildIconIndex(roots: readonly string[], fs: LinuxFs): Map<string, string> {
|
|
50
|
+
const index = new Map<string, string>();
|
|
51
|
+
const rank = new Map<string, number>();
|
|
52
|
+
const walk = (dir: string, depth: number, rootOrder: number): void => {
|
|
53
|
+
if (depth > MAX_DEPTH) return;
|
|
54
|
+
const entries = fs.list(dir);
|
|
55
|
+
if (!entries) return;
|
|
56
|
+
for (const name of entries) {
|
|
57
|
+
const path = `${dir}/${name}`;
|
|
58
|
+
const dot = name.lastIndexOf(".");
|
|
59
|
+
const ext = dot > 0 ? name.slice(dot) : "";
|
|
60
|
+
const extRank = ICON_EXTENSIONS.indexOf(ext);
|
|
61
|
+
if (extRank < 0) {
|
|
62
|
+
// No known extension: treat it as a directory and descend. A plain file
|
|
63
|
+
// simply lists as nothing and costs one failed readdir.
|
|
64
|
+
walk(path, depth + 1, rootOrder);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const base = name.slice(0, dot);
|
|
68
|
+
const score = rootOrder * 10 + extRank;
|
|
69
|
+
const previous = rank.get(base);
|
|
70
|
+
if (previous === undefined || score < previous) {
|
|
71
|
+
rank.set(base, score);
|
|
72
|
+
index.set(base, path);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
roots.forEach((root, order) => walk(root, 0, order));
|
|
77
|
+
return index;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The file for one Icon value, or null when nothing resolves.
|
|
82
|
+
*
|
|
83
|
+
* An absolute path is taken from the entry itself, which is host-owned content -
|
|
84
|
+
* but it is still required to be an image extension, so a malformed entry cannot
|
|
85
|
+
* turn the icon route into a reader of arbitrary files.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveIconPath(
|
|
88
|
+
icon: string | null,
|
|
89
|
+
index: Map<string, string>,
|
|
90
|
+
fs: LinuxFs,
|
|
91
|
+
): string | null {
|
|
92
|
+
if (!icon) return null;
|
|
93
|
+
if (icon.startsWith("/")) {
|
|
94
|
+
return hasImageExtension(icon) && fs.exists(icon) ? icon : null;
|
|
95
|
+
}
|
|
96
|
+
// Some entries write the file name rather than the icon name ("foo.png").
|
|
97
|
+
const dot = icon.lastIndexOf(".");
|
|
98
|
+
const stem = dot > 0 && ICON_EXTENSIONS.includes(icon.slice(dot)) ? icon.slice(0, dot) : icon;
|
|
99
|
+
return index.get(stem) ?? index.get(icon) ?? null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function hasImageExtension(path: string): boolean {
|
|
103
|
+
const dot = path.lastIndexOf(".");
|
|
104
|
+
return dot > 0 && ICON_EXTENSIONS.includes(path.slice(dot).toLowerCase());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function iconMimeType(path: string): string {
|
|
108
|
+
const dot = path.lastIndexOf(".");
|
|
109
|
+
return ICON_MIME[dot > 0 ? path.slice(dot).toLowerCase() : ""] ?? "application/octet-stream";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Production wiring: one lazily-built index for the process's lifetime. */
|
|
113
|
+
export function createIconResolver(fs: LinuxFs = realLinuxFs) {
|
|
114
|
+
const roots = iconRoots(process.env, process.env.HOME ?? "/root");
|
|
115
|
+
let index: Map<string, string> | null = null;
|
|
116
|
+
return {
|
|
117
|
+
resolve(icon: string | null): string | null {
|
|
118
|
+
index ??= buildIconIndex(roots, fs);
|
|
119
|
+
return resolveIconPath(icon, index, fs);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type IconResolver = ReturnType<typeof createIconResolver>;
|