@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,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-process GPU accounting from DRM fdinfo — the same source nvtop and Mission
|
|
3
|
+
* Center use, and the only one that works for Intel and AMD without root.
|
|
4
|
+
*
|
|
5
|
+
* `/proc/<pid>/fdinfo/<fd>` of a DRM file carries cumulative engine busy time in
|
|
6
|
+
* nanoseconds. Two things make it trickier than a counter read:
|
|
7
|
+
*
|
|
8
|
+
* - walking every fd of every process costs ~140 ms on a desktop with 3000
|
|
9
|
+
* threads, which a 2 s tick cannot pay. Only a handful of processes ever hold
|
|
10
|
+
* a DRM fd (15 of 558 here), so the full scan runs on a slow cadence and every
|
|
11
|
+
* other tick re-reads just those — measured at ~2 ms, a 70x saving.
|
|
12
|
+
* - a client's fds are SHARED across its threads and often dup'd, so the same
|
|
13
|
+
* counters appear many times; `drm-client-id` identifies the real client and
|
|
14
|
+
* is what deduplicates them. Summing without that multiplies a browser's GPU
|
|
15
|
+
* use by its thread count.
|
|
16
|
+
* - the counter is per engine, so "GPU %" is engine busy over WALL time, not
|
|
17
|
+
* over some total — an engine can only ever be busy for the time that passed.
|
|
18
|
+
*/
|
|
19
|
+
import { realLinuxFs, type LinuxFs } from "./linux-fs.ts";
|
|
20
|
+
|
|
21
|
+
const PROC = "/proc";
|
|
22
|
+
const DRI_PREFIX = "/dev/dri/";
|
|
23
|
+
|
|
24
|
+
export interface DrmClient {
|
|
25
|
+
/** PCI address the client is rendering on, e.g. "0000:00:02.0". */
|
|
26
|
+
pdev: string;
|
|
27
|
+
/** Unique per open client on that device; the deduplication key with `pdev`. */
|
|
28
|
+
clientId: string;
|
|
29
|
+
driver: string;
|
|
30
|
+
pid: number;
|
|
31
|
+
/** Engine name ("render", "copy", "video", "video-enhance", "compute") → ns. */
|
|
32
|
+
engines: Map<string, number>;
|
|
33
|
+
/** Dedicated (VRAM) bytes this client holds; 0 on an integrated GPU. */
|
|
34
|
+
vramBytes: number;
|
|
35
|
+
/** System/GTT bytes this client holds. */
|
|
36
|
+
sharedBytes: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One fdinfo file. Null when it is not a DRM fd (the overwhelming majority). */
|
|
40
|
+
export function parseDrmFdinfo(text: string, pid: number): DrmClient | null {
|
|
41
|
+
let pdev = "";
|
|
42
|
+
let clientId = "";
|
|
43
|
+
let driver = "";
|
|
44
|
+
let vramBytes = 0;
|
|
45
|
+
let sharedBytes = 0;
|
|
46
|
+
const engines = new Map<string, number>();
|
|
47
|
+
|
|
48
|
+
for (const line of text.split("\n")) {
|
|
49
|
+
if (!line.startsWith("drm-")) continue;
|
|
50
|
+
const sep = line.indexOf(":");
|
|
51
|
+
if (sep < 0) continue;
|
|
52
|
+
const key = line.slice(0, sep);
|
|
53
|
+
const value = line.slice(sep + 1).trim();
|
|
54
|
+
|
|
55
|
+
if (key === "drm-pdev") pdev = value;
|
|
56
|
+
else if (key === "drm-client-id") clientId = value;
|
|
57
|
+
else if (key === "drm-driver") driver = value;
|
|
58
|
+
else if (key.startsWith("drm-engine-")) {
|
|
59
|
+
const ns = Number(value.split(/\s+/)[0]);
|
|
60
|
+
if (Number.isFinite(ns)) engines.set(key.slice("drm-engine-".length), ns);
|
|
61
|
+
} else if (key.startsWith("drm-total-")) {
|
|
62
|
+
// `drm-total-vram0`/`-vram` is dedicated; `-system0`/`-gtt` is shared.
|
|
63
|
+
const region = key.slice("drm-total-".length);
|
|
64
|
+
const bytes = parseMemoryValue(value);
|
|
65
|
+
if (region.startsWith("vram")) vramBytes += bytes;
|
|
66
|
+
else if (region.startsWith("system") || region.startsWith("gtt")) sharedBytes += bytes;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!pdev || !clientId) return null;
|
|
71
|
+
return { pdev, clientId, driver, pid, engines, vramBytes, sharedBytes };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** fdinfo writes memory as "74920 KiB" (i915) or a bare byte count (amdgpu). */
|
|
75
|
+
export function parseMemoryValue(value: string): number {
|
|
76
|
+
const [raw, unit] = value.split(/\s+/);
|
|
77
|
+
const n = Number(raw);
|
|
78
|
+
if (!Number.isFinite(n)) return 0;
|
|
79
|
+
switch (unit) {
|
|
80
|
+
case "KiB": return n * 1024;
|
|
81
|
+
case "MiB": return n * 1024 ** 2;
|
|
82
|
+
case "GiB": return n * 1024 ** 3;
|
|
83
|
+
default: return n;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Every distinct DRM client on the machine, deduplicated by (pdev, client-id).
|
|
89
|
+
*
|
|
90
|
+
* Only fds whose `/proc/<pid>/fd/<n>` link points into `/dev/dri` are read: one
|
|
91
|
+
* `readlink` beats an `open`+`read`+`close` on every fd of every process, and on
|
|
92
|
+
* a desktop with ~3000 threads that is the difference between a few ms and a
|
|
93
|
+
* tick that misses its deadline.
|
|
94
|
+
*/
|
|
95
|
+
export function collectDrmClients(
|
|
96
|
+
fs: Pick<LinuxFs, "list" | "read" | "readlink"> = realLinuxFs,
|
|
97
|
+
onlyPids?: readonly number[],
|
|
98
|
+
): DrmClient[] {
|
|
99
|
+
const byKey = new Map<string, DrmClient>();
|
|
100
|
+
const candidates = onlyPids ?? (fs.list(PROC) ?? []).map(Number);
|
|
101
|
+
for (const entry of candidates) {
|
|
102
|
+
const pid = Number(entry);
|
|
103
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
104
|
+
for (const fd of fs.list(`${PROC}/${pid}/fd`) ?? []) {
|
|
105
|
+
if (!fs.readlink(`${PROC}/${pid}/fd/${fd}`)?.startsWith(DRI_PREFIX)) continue;
|
|
106
|
+
const text = fs.read(`${PROC}/${pid}/fdinfo/${fd}`);
|
|
107
|
+
if (!text) continue;
|
|
108
|
+
const client = parseDrmFdinfo(text, pid);
|
|
109
|
+
if (!client) continue;
|
|
110
|
+
const key = `${client.pdev}/${client.clientId}`;
|
|
111
|
+
// The lowest pid holding a client is its owner; a dup in a child would
|
|
112
|
+
// otherwise attribute the parent's rendering to whichever fd came last.
|
|
113
|
+
const existing = byKey.get(key);
|
|
114
|
+
if (!existing || client.pid < existing.pid) byKey.set(key, client);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return [...byKey.values()];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Cumulative engine ns per client, plus the clock they were read at. */
|
|
121
|
+
export interface DrmEngineState {
|
|
122
|
+
atSec: number;
|
|
123
|
+
/** "<pdev>/<clientId>" → engine → ns. */
|
|
124
|
+
byClient: Map<string, Map<string, number>>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface DrmUsage {
|
|
128
|
+
/** pdev → engine → busy percent over the interval (0-100 per engine). */
|
|
129
|
+
perDevice: Map<string, Map<string, number>>;
|
|
130
|
+
/** pid → summed engine percent (clamped 0-100) and memory in MB. */
|
|
131
|
+
perProcess: Map<number, { gpuPct: number; vramMB: number; sharedMB: number }>;
|
|
132
|
+
/**
|
|
133
|
+
* pdev → system/GTT memory its clients hold, MB. This is the ONLY whole-device
|
|
134
|
+
* memory figure an integrated GPU has: i915 publishes no `mem_info_*` sysfs
|
|
135
|
+
* attribute, so without it the page can say nothing at all.
|
|
136
|
+
*
|
|
137
|
+
* A buffer shared between two clients is counted twice, because fdinfo's
|
|
138
|
+
* `drm-total-*` is per client and the `drm-shared-*` line does not say WHO it
|
|
139
|
+
* is shared with. Mission Center sums the same way — measured against it live,
|
|
140
|
+
* 2.54 GiB here against its 2.52 GiB.
|
|
141
|
+
*/
|
|
142
|
+
sharedByDevice: Map<string, number>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function toEngineState(clients: readonly DrmClient[], atSec: number): DrmEngineState {
|
|
146
|
+
const byClient = new Map<string, Map<string, number>>();
|
|
147
|
+
for (const c of clients) byClient.set(`${c.pdev}/${c.clientId}`, c.engines);
|
|
148
|
+
return { atSec, byClient };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Engine busy over the wall interval. A client that did not exist on the previous
|
|
153
|
+
* sample contributes nothing this tick — measuring its whole lifetime against one
|
|
154
|
+
* interval is how a freshly launched window reads as 4000 % GPU.
|
|
155
|
+
*/
|
|
156
|
+
export function computeDrmUsage(
|
|
157
|
+
prev: DrmEngineState | null,
|
|
158
|
+
clients: readonly DrmClient[],
|
|
159
|
+
atSec: number,
|
|
160
|
+
): DrmUsage {
|
|
161
|
+
const perDevice = new Map<string, Map<string, number>>();
|
|
162
|
+
const perProcess = new Map<number, { gpuPct: number; vramMB: number; sharedMB: number }>();
|
|
163
|
+
const sharedByDevice = new Map<string, number>();
|
|
164
|
+
const dtNs = prev ? (atSec - prev.atSec) * 1e9 : 0;
|
|
165
|
+
|
|
166
|
+
for (const c of clients) {
|
|
167
|
+
const MB = 1024 ** 2;
|
|
168
|
+
const row = perProcess.get(c.pid) ?? { gpuPct: 0, vramMB: 0, sharedMB: 0 };
|
|
169
|
+
row.vramMB += c.vramBytes / MB;
|
|
170
|
+
row.sharedMB += c.sharedBytes / MB;
|
|
171
|
+
// Every device a client was seen on gets an entry, even at 0 bytes: "this
|
|
172
|
+
// GPU's clients hold nothing" is a reading, and no entry at all is not.
|
|
173
|
+
sharedByDevice.set(c.pdev, (sharedByDevice.get(c.pdev) ?? 0) + c.sharedBytes / MB);
|
|
174
|
+
|
|
175
|
+
if (dtNs > 0) {
|
|
176
|
+
const before = prev!.byClient.get(`${c.pdev}/${c.clientId}`);
|
|
177
|
+
if (before) {
|
|
178
|
+
const engines = perDevice.get(c.pdev) ?? new Map<string, number>();
|
|
179
|
+
for (const [engine, ns] of c.engines) {
|
|
180
|
+
const delta = ns - (before.get(engine) ?? 0);
|
|
181
|
+
if (delta <= 0) continue;
|
|
182
|
+
const pct = Math.min(100, delta / dtNs * 100);
|
|
183
|
+
engines.set(engine, (engines.get(engine) ?? 0) + pct);
|
|
184
|
+
row.gpuPct += pct;
|
|
185
|
+
}
|
|
186
|
+
perDevice.set(c.pdev, engines);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
row.gpuPct = Math.min(100, row.gpuPct);
|
|
190
|
+
perProcess.set(c.pid, row);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// A device's engine can only be busy for the interval that passed.
|
|
194
|
+
for (const engines of perDevice.values()) {
|
|
195
|
+
for (const [engine, pct] of engines) engines.set(engine, Math.min(100, round1(pct)));
|
|
196
|
+
}
|
|
197
|
+
for (const row of perProcess.values()) {
|
|
198
|
+
row.gpuPct = round1(row.gpuPct);
|
|
199
|
+
row.vramMB = round1(row.vramMB);
|
|
200
|
+
row.sharedMB = round1(row.sharedMB);
|
|
201
|
+
}
|
|
202
|
+
for (const [pdev, mb] of sharedByDevice) sharedByDevice.set(pdev, round1(mb));
|
|
203
|
+
return { perDevice, perProcess, sharedByDevice };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const round1 = (n: number) => Math.round(n * 10) / 10;
|
|
207
|
+
|
|
208
|
+
// ------------------------------------------------------- stateful collector
|
|
209
|
+
|
|
210
|
+
/** How long one walk's result is reused. Both the whole-GPU figures and the
|
|
211
|
+
* process rows are built from the same tick, so whichever asks first pays for
|
|
212
|
+
* the walk and the other reads the same numbers — they can never disagree. */
|
|
213
|
+
export const DRM_MEMO_MS = 1000;
|
|
214
|
+
/** Full `/proc` rescans are this far apart; in between, only the pids already
|
|
215
|
+
* known to hold a DRM fd are re-read. A window opened now shows GPU use within
|
|
216
|
+
* one rescan rather than immediately, which is the cost of not paying 140 ms
|
|
217
|
+
* every 2 s forever. */
|
|
218
|
+
export const DRM_RESCAN_MS = 10_000;
|
|
219
|
+
|
|
220
|
+
export interface DrmGpuCollector {
|
|
221
|
+
/** This tick's usage, walking `/proc` at most once per `DRM_MEMO_MS`. */
|
|
222
|
+
usage(): DrmUsage;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function createDrmGpuCollector(
|
|
226
|
+
fs: Pick<LinuxFs, "list" | "read" | "readlink"> = realLinuxFs,
|
|
227
|
+
now: () => number = Date.now,
|
|
228
|
+
): DrmGpuCollector {
|
|
229
|
+
let state: DrmEngineState | null = null;
|
|
230
|
+
let knownPids: number[] = [];
|
|
231
|
+
let lastFullScanAt = 0;
|
|
232
|
+
let memo: { at: number; usage: DrmUsage } | null = null;
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
usage(): DrmUsage {
|
|
236
|
+
const at = now();
|
|
237
|
+
if (memo && at - memo.at < DRM_MEMO_MS) return memo.usage;
|
|
238
|
+
|
|
239
|
+
const full = knownPids.length === 0 || at - lastFullScanAt >= DRM_RESCAN_MS;
|
|
240
|
+
const clients = collectDrmClients(fs, full ? undefined : knownPids);
|
|
241
|
+
if (full) {
|
|
242
|
+
lastFullScanAt = at;
|
|
243
|
+
knownPids = [...new Set(clients.map((c) => c.pid))];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const atSec = at / 1000;
|
|
247
|
+
const usage = computeDrmUsage(state, clients, atSec);
|
|
248
|
+
state = toEngineState(clients, atSec);
|
|
249
|
+
memo = { at, usage };
|
|
250
|
+
return usage;
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static hardware facts, served by `GET /api/system/hardware` instead of riding
|
|
3
|
+
* every 2 s snapshot: a drive's model and capacity do not change, and sending
|
|
4
|
+
* them 1800 times an hour over a tunnel would be the largest part of the frame.
|
|
5
|
+
*
|
|
6
|
+
* Async only because the OpenGL/Vulkan versions come from a one-shot tool; that
|
|
7
|
+
* result is cached for the process lifetime, so every call after the first is
|
|
8
|
+
* a handful of small sysfs and udev reads with no cache and no subprocess — a
|
|
9
|
+
* drive plugged in is visible on the client's next refetch rather than after a TTL.
|
|
10
|
+
*/
|
|
11
|
+
import type { HardwareInventory } from "../../types/system-hardware.ts";
|
|
12
|
+
import type { MetricsPlatform } from "../../types/system-metrics.ts";
|
|
13
|
+
import { toMetricsPlatform } from "./system-metrics-platform.ts";
|
|
14
|
+
import { readDiskInventory } from "./disk-inventory-linux.ts";
|
|
15
|
+
import { attachPartitionUsage } from "./partitions-linux.ts";
|
|
16
|
+
import { readNicInventory } from "./net-inventory-linux.ts";
|
|
17
|
+
import { readCpuInfo } from "./cpu-details-linux.ts";
|
|
18
|
+
import { readMemoryInfo } from "./memory-linux.ts";
|
|
19
|
+
import { listGpuCards, readGpuInfo } from "./gpu-devices-linux.ts";
|
|
20
|
+
import { createGpuApiVersionReader, type GpuApiVersionReader } from "./gpu-api-versions.ts";
|
|
21
|
+
import { realLinuxFs } from "./linux-fs.ts";
|
|
22
|
+
|
|
23
|
+
const apiVersions = createGpuApiVersionReader();
|
|
24
|
+
|
|
25
|
+
/** Windows and macOS report no devices yet; the client hides the sections rather
|
|
26
|
+
* than showing empty ones, exactly as it does for a host with no GPU. */
|
|
27
|
+
export async function readHardwareInventory(
|
|
28
|
+
platform: MetricsPlatform = toMetricsPlatform(),
|
|
29
|
+
now: () => number = Date.now,
|
|
30
|
+
api: GpuApiVersionReader = apiVersions,
|
|
31
|
+
): Promise<HardwareInventory> {
|
|
32
|
+
const base = { platform, ts: now(), disks: [], nics: [], gpus: [] } satisfies HardwareInventory;
|
|
33
|
+
if (platform !== "linux") return base;
|
|
34
|
+
|
|
35
|
+
const cpu = readCpuInfo();
|
|
36
|
+
const versions = await api.read();
|
|
37
|
+
// statfs per mount, off the event loop and in parallel — see partitions-linux.
|
|
38
|
+
const disks = readDiskInventory();
|
|
39
|
+
await attachPartitionUsage(disks);
|
|
40
|
+
return {
|
|
41
|
+
...base,
|
|
42
|
+
disks,
|
|
43
|
+
nics: readNicInventory(),
|
|
44
|
+
memory: readMemoryInfo(),
|
|
45
|
+
// NVIDIA is deliberately included here: the static facts (name, PCIe link)
|
|
46
|
+
// come from sysfs for every driver, even where the live figures do not.
|
|
47
|
+
gpus: listGpuCards(realLinuxFs).map((card) => readGpuInfo(card, versions, realLinuxFs)),
|
|
48
|
+
...(cpu ? { cpu } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable filesystem access for the /proc and /sys collectors, so every parser
|
|
3
|
+
* is fixture-tested without a real host. Each call returns null instead of
|
|
4
|
+
* throwing: a device or a process vanishing between a directory listing and the
|
|
5
|
+
* read is the normal case here, not an error worth a stack trace per tick.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readdirSync, readFileSync, readlinkSync, realpathSync } from "node:fs";
|
|
8
|
+
|
|
9
|
+
export type FileReader = (path: string) => string | null;
|
|
10
|
+
export type DirLister = (path: string) => string[] | null;
|
|
11
|
+
export type LinkReader = (path: string) => string | null;
|
|
12
|
+
|
|
13
|
+
export interface LinuxFs {
|
|
14
|
+
read: FileReader;
|
|
15
|
+
list: DirLister;
|
|
16
|
+
/** The link's own target, as written (often relative: `../../devices/...`). */
|
|
17
|
+
readlink: LinkReader;
|
|
18
|
+
/** Fully resolved absolute path, or null when it does not resolve. */
|
|
19
|
+
realpath: LinkReader;
|
|
20
|
+
exists: (path: string) => boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const realLinuxFs: LinuxFs = {
|
|
24
|
+
read: (path) => { try { return readFileSync(path, "utf-8"); } catch { return null; } },
|
|
25
|
+
list: (path) => { try { return readdirSync(path); } catch { return null; } },
|
|
26
|
+
readlink: (path) => { try { return readlinkSync(path); } catch { return null; } },
|
|
27
|
+
realpath: (path) => { try { return realpathSync(path); } catch { return null; } },
|
|
28
|
+
exists: (path) => existsSync(path),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** A sysfs attribute as a trimmed string, or undefined when absent or blank. */
|
|
32
|
+
export function readAttr(fs: Pick<LinuxFs, "read">, path: string): string | undefined {
|
|
33
|
+
const v = fs.read(path)?.trim();
|
|
34
|
+
return v ? v : undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A sysfs attribute as a finite number, or undefined. */
|
|
38
|
+
export function readNumber(fs: Pick<LinuxFs, "read">, path: string): number | undefined {
|
|
39
|
+
const v = readAttr(fs, path);
|
|
40
|
+
if (v === undefined) return undefined;
|
|
41
|
+
const n = Number(v);
|
|
42
|
+
return Number.isFinite(n) ? n : undefined;
|
|
43
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS swap, which is the one memory figure the platform publishes and `node:os`
|
|
3
|
+
* does not.
|
|
4
|
+
*
|
|
5
|
+
* Off Linux every swap field was absent, and the memory page renders an absent
|
|
6
|
+
* field as an em dash — i.e. as the claim "this host's swap cannot be measured".
|
|
7
|
+
* On a Mac it can: `vm.swapusage` is a single sysctl. On the host this was found
|
|
8
|
+
* on it read **8047 MB of 9216 MB in use**, which is the most consequential thing
|
|
9
|
+
* that page could have been saying about the machine and was saying nothing about.
|
|
10
|
+
*
|
|
11
|
+
* `vm_stat` is deliberately not used here. It carries page counts but no swap at
|
|
12
|
+
* all, and the page counts are not needed: `os.freemem()` on darwin agrees with
|
|
13
|
+
* what `top` calls unused (75 MB against PPM's 124 MB on the same host, sampled
|
|
14
|
+
* seconds apart), so the cross-platform figure is already the one Activity
|
|
15
|
+
* Monitor shows. Do not "fix" it against `memory_pressure`, whose free
|
|
16
|
+
* percentage measures what could be reclaimed under duress and read 56% on a
|
|
17
|
+
* machine `top` called 31G used.
|
|
18
|
+
*
|
|
19
|
+
* The per-tick cost was measured rather than assumed, because this runs on every
|
|
20
|
+
* tick: **1.33 ms/call**, against the 1.7 ms `/proc/<pid>/io` already costs per
|
|
21
|
+
* tick on Linux. It stays `spawnSync` because `collectMemory` is synchronous and
|
|
22
|
+
* making the whole memory step async for one number is the larger change.
|
|
23
|
+
*/
|
|
24
|
+
import type { MemoryMetrics } from "../../types/system-metrics.ts";
|
|
25
|
+
|
|
26
|
+
type SwapFields = Pick<MemoryMetrics, "swapTotalMB" | "swapUsedMB">;
|
|
27
|
+
|
|
28
|
+
/** `total = 9216.00M used = 8047.00M free = 1169.00M (encrypted)` */
|
|
29
|
+
const SWAP_RE = /total\s*=\s*([\d.]+)([KMGT])?\b[\s\S]*?used\s*=\s*([\d.]+)([KMGT])?\b/i;
|
|
30
|
+
|
|
31
|
+
const MB_PER_UNIT: Record<string, number> = { K: 1 / 1024, M: 1, G: 1024, T: 1024 * 1024 };
|
|
32
|
+
|
|
33
|
+
const round1 = (n: number): number => Math.round(n * 10) / 10;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Pure half, so the format can be tested without a Mac.
|
|
37
|
+
*
|
|
38
|
+
* `undefined` rather than zeroes when the text does not parse: a shape this code
|
|
39
|
+
* does not recognise is a host it did not measure, and 0 would be a claim that
|
|
40
|
+
* the machine has no swap.
|
|
41
|
+
*/
|
|
42
|
+
export function parseSwapUsage(text: string | null | undefined): SwapFields | undefined {
|
|
43
|
+
if (!text) return undefined;
|
|
44
|
+
const m = SWAP_RE.exec(text);
|
|
45
|
+
if (!m) return undefined;
|
|
46
|
+
|
|
47
|
+
const toMB = (value: string, unit: string | undefined): number | null => {
|
|
48
|
+
const n = Number(value);
|
|
49
|
+
if (!Number.isFinite(n) || n < 0) return null;
|
|
50
|
+
// A bare number is bytes. The format normally carries a suffix, but a swap
|
|
51
|
+
// file of zero prints as `0.00M` on some releases and `0` on others, and
|
|
52
|
+
// reading that second form as megabytes would invent 0 MB either way — it
|
|
53
|
+
// is only correct here because the value is zero.
|
|
54
|
+
return unit ? n * (MB_PER_UNIT[unit.toUpperCase()] ?? 1) : n / (1024 * 1024);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const total = toMB(m[1]!, m[2]);
|
|
58
|
+
const used = toMB(m[3]!, m[4]);
|
|
59
|
+
if (total === null || used === null) return undefined;
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
swapTotalMB: round1(total),
|
|
63
|
+
// Clamped, because a used figure above the total is not a bigger swap, it is
|
|
64
|
+
// a sample taken while the file was being resized.
|
|
65
|
+
swapUsedMB: round1(Math.min(Math.max(used, 0), total)),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* `null` on every platform but darwin, so nothing is spawned where the figure
|
|
71
|
+
* already comes from `/proc/meminfo` or from CIM.
|
|
72
|
+
*/
|
|
73
|
+
export function readSwapUsage(): string | null {
|
|
74
|
+
if (process.platform !== "darwin") return null;
|
|
75
|
+
try {
|
|
76
|
+
const result = Bun.spawnSync(["sysctl", "-n", "vm.swapusage"], {
|
|
77
|
+
stdout: "pipe",
|
|
78
|
+
stderr: "ignore",
|
|
79
|
+
stdin: "ignore",
|
|
80
|
+
});
|
|
81
|
+
if (result.exitCode !== 0) return null;
|
|
82
|
+
return new TextDecoder().decode(result.stdout);
|
|
83
|
+
} catch {
|
|
84
|
+
// `Bun.spawn` raises `Executable not found in $PATH` *synchronously* for a
|
|
85
|
+
// missing binary rather than reporting it through an exit code, and
|
|
86
|
+
// `spawnSync` is no different — the same trap `spawn-runner.ts` carries a
|
|
87
|
+
// comment about. A stripped-down host without `sysctl` gets an em dash, not
|
|
88
|
+
// a collector that throws mid-tick.
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission Center's memory page: the composition bar and the figures under it,
|
|
3
|
+
* all from `/proc/meminfo`, plus the populated slots from the SMBIOS table udev
|
|
4
|
+
* already decoded into its database (the raw table needs root; this does not).
|
|
5
|
+
*/
|
|
6
|
+
import type { MemoryMetrics } from "../../types/system-metrics.ts";
|
|
7
|
+
import type { MemoryDeviceInfo, MemoryInfo } from "../../types/system-hardware.ts";
|
|
8
|
+
import { realLinuxFs, type LinuxFs } from "./linux-fs.ts";
|
|
9
|
+
import { readUdevProperties, udevValue } from "./udev-db.ts";
|
|
10
|
+
|
|
11
|
+
/** udev's own name for the SMBIOS record. */
|
|
12
|
+
export const DMI_UDEV_DEVICE = "+dmi:id";
|
|
13
|
+
const MB = 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
/** Every `Key: N kB` line as BYTES. Values without a unit (the HugePages counts)
|
|
16
|
+
* are kept verbatim, which is what they mean. */
|
|
17
|
+
export function parseMeminfo(text: string): Map<string, number> {
|
|
18
|
+
const out = new Map<string, number>();
|
|
19
|
+
for (const line of text.split("\n")) {
|
|
20
|
+
const sep = line.indexOf(":");
|
|
21
|
+
if (sep < 0) continue;
|
|
22
|
+
const rest = line.slice(sep + 1).trim();
|
|
23
|
+
const [rawValue, unit] = rest.split(/\s+/);
|
|
24
|
+
const n = Number(rawValue);
|
|
25
|
+
if (!Number.isFinite(n)) continue;
|
|
26
|
+
out.set(line.slice(0, sep), unit === "kB" ? n * 1024 : n);
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Adds the composition and the swap/commit figures to the cross-platform base.
|
|
33
|
+
*
|
|
34
|
+
* The four parts add up to the total EXACTLY and "in use" is exactly the headline
|
|
35
|
+
* `usedMB` (total - MemAvailable), so the bar and the figure beside it can never
|
|
36
|
+
* disagree. Standby is the rest of what the kernel would hand back on demand
|
|
37
|
+
* (MemAvailable - MemFree), and modified is carved out of it rather than added —
|
|
38
|
+
* a dirty page is also a cached page, so adding the two would overflow the bar.
|
|
39
|
+
*/
|
|
40
|
+
export function enrichMemory(
|
|
41
|
+
base: MemoryMetrics,
|
|
42
|
+
text: string | null,
|
|
43
|
+
zram: ZramTotals | undefined = undefined,
|
|
44
|
+
): MemoryMetrics {
|
|
45
|
+
if (!text) return base;
|
|
46
|
+
const m = parseMeminfo(text);
|
|
47
|
+
const total = m.get("MemTotal");
|
|
48
|
+
if (total === undefined || total <= 0) return base;
|
|
49
|
+
|
|
50
|
+
const get = (k: string) => m.get(k) ?? 0;
|
|
51
|
+
const free = clamp(get("MemFree"), 0, total);
|
|
52
|
+
const available = clamp(m.get("MemAvailable") ?? free, free, total);
|
|
53
|
+
const reclaimable = available - free;
|
|
54
|
+
const modified = clamp(get("Dirty") + get("Writeback"), 0, reclaimable);
|
|
55
|
+
const swapTotal = get("SwapTotal");
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
...base,
|
|
59
|
+
inUseBytes: total - available,
|
|
60
|
+
modifiedBytes: modified,
|
|
61
|
+
standbyBytes: reclaimable - modified,
|
|
62
|
+
freeBytes: free,
|
|
63
|
+
cachedMB: round1(get("Cached") / MB),
|
|
64
|
+
committedMB: round1(get("Committed_AS") / MB),
|
|
65
|
+
commitLimitMB: round1(get("CommitLimit") / MB),
|
|
66
|
+
swapTotalMB: round1(swapTotal / MB),
|
|
67
|
+
swapUsedMB: round1(Math.max(swapTotal - get("SwapFree"), 0) / MB),
|
|
68
|
+
...(zram
|
|
69
|
+
? {
|
|
70
|
+
zramCompressedMB: round1(zram.compressedBytes / MB),
|
|
71
|
+
zramSavingsMB: round1(zram.savingsBytes / MB),
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface ZramTotals {
|
|
78
|
+
compressedBytes: number;
|
|
79
|
+
savingsBytes: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* `/sys/block/zram<N>/mm_stat`, summed over every device — a host can have one
|
|
84
|
+
* per CPU. Undefined when there is no zram at all, which is a different answer
|
|
85
|
+
* from a zram holding nothing: the Memory page hides the rows in the first case
|
|
86
|
+
* and shows zeroes in the second.
|
|
87
|
+
*
|
|
88
|
+
* Field order (Documentation/admin-guide/blockdev/zram.rst): orig_data_size,
|
|
89
|
+
* compr_data_size, mem_used_total, mem_limit, mem_used_max, same_pages,
|
|
90
|
+
* pages_compacted, huge_pages. Savings is orig minus compr, which is what
|
|
91
|
+
* Mission Center shows — NOT orig minus mem_used_total, which also subtracts
|
|
92
|
+
* zram's own allocator overhead and reads several percent low.
|
|
93
|
+
*/
|
|
94
|
+
export function readZramTotals(fs: LinuxFs = realLinuxFs): ZramTotals | undefined {
|
|
95
|
+
const devices = (fs.list("/sys/block") ?? []).filter((n) => /^zram\d+$/.test(n));
|
|
96
|
+
if (devices.length === 0) return undefined;
|
|
97
|
+
let compressedBytes = 0;
|
|
98
|
+
let savingsBytes = 0;
|
|
99
|
+
let any = false;
|
|
100
|
+
for (const dev of devices) {
|
|
101
|
+
const stat = fs.read(`/sys/block/${dev}/mm_stat`)?.trim().split(/\s+/);
|
|
102
|
+
if (!stat || stat.length < 2) continue;
|
|
103
|
+
const orig = Number(stat[0]);
|
|
104
|
+
const compr = Number(stat[1]);
|
|
105
|
+
if (!Number.isFinite(orig) || !Number.isFinite(compr)) continue;
|
|
106
|
+
any = true;
|
|
107
|
+
compressedBytes += compr;
|
|
108
|
+
savingsBytes += Math.max(0, orig - compr);
|
|
109
|
+
}
|
|
110
|
+
return any ? { compressedBytes, savingsBytes } : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const clamp = (v: number, lo: number, hi: number) => Math.min(Math.max(v, lo), hi);
|
|
114
|
+
|
|
115
|
+
/** The populated slots, in board order. `MEMORY_DEVICE_<n>_*` is udev's decode of
|
|
116
|
+
* SMBIOS type 17; an empty slot has no `SIZE`, so it simply does not appear. */
|
|
117
|
+
export function readMemoryInfo(fs: Pick<LinuxFs, "read"> = realLinuxFs): MemoryInfo {
|
|
118
|
+
const props = readUdevProperties(DMI_UDEV_DEVICE, fs.read);
|
|
119
|
+
const devices: MemoryDeviceInfo[] = [];
|
|
120
|
+
|
|
121
|
+
for (let i = 0; ; i++) {
|
|
122
|
+
const prefix = `MEMORY_DEVICE_${i}_`;
|
|
123
|
+
const locator = udevValue(props, `${prefix}LOCATOR`);
|
|
124
|
+
const size = Number(udevValue(props, `${prefix}SIZE`));
|
|
125
|
+
// udev numbers the slots contiguously, so the first gap is the end of the list.
|
|
126
|
+
if (!locator && !Number.isFinite(size)) break;
|
|
127
|
+
if (i > 64) break;
|
|
128
|
+
if (!locator || !Number.isFinite(size) || size <= 0) continue;
|
|
129
|
+
devices.push({
|
|
130
|
+
locator,
|
|
131
|
+
sizeBytes: size,
|
|
132
|
+
...defined("bankLocator", udevValue(props, `${prefix}BANK_LOCATOR`)),
|
|
133
|
+
...defined("formFactor", udevValue(props, `${prefix}FORM_FACTOR`)),
|
|
134
|
+
...defined("ramType", udevValue(props, `${prefix}TYPE`)),
|
|
135
|
+
...defined("speedMts", positive(udevValue(props, `${prefix}SPEED_MTS`))),
|
|
136
|
+
...defined("manufacturer", udevValue(props, `${prefix}MANUFACTURER`)),
|
|
137
|
+
...defined("rank", positive(udevValue(props, `${prefix}RANK`))),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
devices,
|
|
143
|
+
...defined("maxCapacityBytes", positive(udevValue(props, "MEMORY_ARRAY_MAX_CAPACITY"))),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const round1 = (n: number) => Math.round(n * 10) / 10;
|
|
148
|
+
|
|
149
|
+
function positive(raw: string | undefined): number | undefined {
|
|
150
|
+
const n = Number(raw);
|
|
151
|
+
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function defined<K extends string, V>(key: K, value: V | undefined): Partial<Record<K, V>> {
|
|
155
|
+
return value === undefined ? {} : ({ [key]: value } as Record<K, V>);
|
|
156
|
+
}
|