@hienlh/ppm 0.23.6 → 0.23.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (701) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/CLAUDE.md +21 -1
  3. package/Dockerfile.test +8 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +4 -4
  6. package/assets/skills/ppm/references/http-api.md +29 -1
  7. package/dist/web/assets/accounts-settings-section-BYWnY9hH.js +1 -0
  8. package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.br +0 -0
  9. package/dist/web/assets/accounts-settings-section-BYWnY9hH.js.gz +0 -0
  10. package/dist/web/assets/{ai-resource-editor-Dn_wB-o5.js → ai-resource-editor-CY4n_Ccv.js} +1 -1
  11. package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.br +0 -0
  12. package/dist/web/assets/ai-resource-editor-CY4n_Ccv.js.gz +0 -0
  13. package/dist/web/assets/ai-settings-section-Bs3iUW4j.js +1 -0
  14. package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.br +0 -0
  15. package/dist/web/assets/ai-settings-section-Bs3iUW4j.js.gz +0 -0
  16. package/dist/web/assets/ai-settings-section-DDs11XV4.js +1 -0
  17. package/dist/web/assets/api-client-CJPnCOG-.js +1 -0
  18. package/dist/web/assets/{api-client-c8YOpTlT.js → api-client-D-SMbdiH.js} +1 -1
  19. package/dist/web/assets/api-client-D-SMbdiH.js.br +0 -0
  20. package/dist/web/assets/api-client-D-SMbdiH.js.gz +0 -0
  21. package/dist/web/assets/api-settings-BDkkhFMq.js +1 -0
  22. package/dist/web/assets/{api-settings-BmvNoCbn.js → api-settings-Bv9mLCty.js} +1 -1
  23. package/dist/web/assets/api-settings-Bv9mLCty.js.br +0 -0
  24. package/dist/web/assets/api-settings-Bv9mLCty.js.gz +0 -0
  25. package/dist/web/assets/{api-themes-6fOQJgM9.js → api-themes-3wW-zLzU.js} +1 -1
  26. package/dist/web/assets/appearance-settings-section-C0BoIBmz.js +1 -0
  27. package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.br +0 -0
  28. package/dist/web/assets/appearance-settings-section-C0BoIBmz.js.gz +0 -0
  29. package/dist/web/assets/apps-panel-DXAFsveZ.js +1 -0
  30. package/dist/web/assets/apps-panel-DXAFsveZ.js.br +0 -0
  31. package/dist/web/assets/apps-panel-DXAFsveZ.js.gz +0 -0
  32. package/dist/web/assets/{audio-preview-vaG2cB-k.js → audio-preview-BfES4Cx5.js} +1 -1
  33. package/dist/web/assets/audio-preview-BfES4Cx5.js.br +0 -0
  34. package/dist/web/assets/audio-preview-BfES4Cx5.js.gz +0 -0
  35. package/dist/web/assets/branch-review-tab-CA8hPSX9.js +1 -0
  36. package/dist/web/assets/branch-review-tab-CA8hPSX9.js.br +0 -0
  37. package/dist/web/assets/branch-review-tab-CA8hPSX9.js.gz +0 -0
  38. package/dist/web/assets/{button-Ci9EUgqd.js → button-DeAyLH9U.js} +1 -1
  39. package/dist/web/assets/button-DeAyLH9U.js.br +0 -0
  40. package/dist/web/assets/button-DeAyLH9U.js.gz +0 -0
  41. package/dist/web/assets/{chat-preparation-cache-BSIQGiWx.js → chat-preparation-cache-CcCd2g7A.js} +1 -1
  42. package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.br +0 -0
  43. package/dist/web/assets/chat-preparation-cache-CcCd2g7A.js.gz +0 -0
  44. package/dist/web/assets/chat-tab-BWi_bUzA.js +1 -0
  45. package/dist/web/assets/chat-tab-BWi_bUzA.js.br +0 -0
  46. package/dist/web/assets/chat-tab-BWi_bUzA.js.gz +0 -0
  47. package/dist/web/assets/chat-tab-BvJhIaVE.js +15 -0
  48. package/dist/web/assets/chat-tab-BvJhIaVE.js.br +0 -0
  49. package/dist/web/assets/chat-tab-BvJhIaVE.js.gz +0 -0
  50. package/dist/web/assets/code-editor-OJUdLVnn.js +7 -0
  51. package/dist/web/assets/code-editor-OJUdLVnn.js.br +0 -0
  52. package/dist/web/assets/code-editor-OJUdLVnn.js.gz +0 -0
  53. package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js +1 -0
  54. package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.br +0 -0
  55. package/dist/web/assets/codex-reset-credit-button-Dgxy_VNp.js.gz +0 -0
  56. package/dist/web/assets/{conflict-editor-BA3y-qSH.js → conflict-editor-BpKW_vbL.js} +1 -1
  57. package/dist/web/assets/conflict-editor-BpKW_vbL.js.br +0 -0
  58. package/dist/web/assets/conflict-editor-BpKW_vbL.js.gz +0 -0
  59. package/dist/web/assets/context-menu-7OX80HDB.js +1 -0
  60. package/dist/web/assets/context-menu-7OX80HDB.js.br +0 -0
  61. package/dist/web/assets/context-menu-7OX80HDB.js.gz +0 -0
  62. package/dist/web/assets/{csv-preview-BioxkrQF.js → csv-preview-COYdTABa.js} +1 -1
  63. package/dist/web/assets/csv-preview-COYdTABa.js.br +0 -0
  64. package/dist/web/assets/csv-preview-COYdTABa.js.gz +0 -0
  65. package/dist/web/assets/{data-grid-overlay-editor-CQRVKr7y.js → data-grid-overlay-editor-CeZoduke.js} +1 -1
  66. package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.br +0 -0
  67. package/dist/web/assets/data-grid-overlay-editor-CeZoduke.js.gz +0 -0
  68. package/dist/web/assets/{database-viewer-BSoJ0E6p.js → database-viewer-BQcBqCKm.js} +1 -1
  69. package/dist/web/assets/database-viewer-BQcBqCKm.js.br +0 -0
  70. package/dist/web/assets/database-viewer-BQcBqCKm.js.gz +0 -0
  71. package/dist/web/assets/{design-export-client-ChMcMoqW.js → design-export-client-OrnGCSwu.js} +1 -1
  72. package/dist/web/assets/design-export-client-OrnGCSwu.js.br +0 -0
  73. package/dist/web/assets/design-export-client-OrnGCSwu.js.gz +0 -0
  74. package/dist/web/assets/{design-responsive-dialog-DNBhJJLf.js → design-responsive-dialog-vJjQrC4n.js} +1 -1
  75. package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.br +0 -0
  76. package/dist/web/assets/design-responsive-dialog-vJjQrC4n.js.gz +0 -0
  77. package/dist/web/assets/design-tab-BbmFk38H.js +33 -0
  78. package/dist/web/assets/design-tab-BbmFk38H.js.br +0 -0
  79. package/dist/web/assets/design-tab-BbmFk38H.js.gz +0 -0
  80. package/dist/web/assets/{designs-sidebar-panel-DHh5QsH9.js → designs-sidebar-panel-D2kXwB8B.js} +1 -1
  81. package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.br +0 -0
  82. package/dist/web/assets/designs-sidebar-panel-D2kXwB8B.js.gz +0 -0
  83. package/dist/web/assets/dialog-Bocc0Fgb.js +1 -0
  84. package/dist/web/assets/dialog-Bocc0Fgb.js.br +0 -0
  85. package/dist/web/assets/dialog-Bocc0Fgb.js.gz +0 -0
  86. package/dist/web/assets/{diff-viewer-lctWoSmG.js → diff-viewer-Bj4DT6Mj.js} +1 -1
  87. package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.br +0 -0
  88. package/dist/web/assets/diff-viewer-Bj4DT6Mj.js.gz +0 -0
  89. package/dist/web/assets/{docx-preview-C9XdKEj1.js → docx-preview-C2QqNGRE.js} +1 -1
  90. package/dist/web/assets/docx-preview-C2QqNGRE.js.br +0 -0
  91. package/dist/web/assets/docx-preview-C2QqNGRE.js.gz +0 -0
  92. package/dist/web/assets/editor-language-service-COqB2tuv.js +1 -0
  93. package/dist/web/assets/editor-language-service-COqB2tuv.js.br +3 -0
  94. package/dist/web/assets/editor-language-service-COqB2tuv.js.gz +0 -0
  95. package/dist/web/assets/explorer-body-DGrYajDN.js +2 -0
  96. package/dist/web/assets/explorer-body-DGrYajDN.js.br +0 -0
  97. package/dist/web/assets/explorer-body-DGrYajDN.js.gz +0 -0
  98. package/dist/web/assets/explorer-window-content-DLzkODTP.js +1 -0
  99. package/dist/web/assets/explorer-window-content-DLzkODTP.js.br +0 -0
  100. package/dist/web/assets/explorer-window-content-DLzkODTP.js.gz +0 -0
  101. package/dist/web/assets/{extension-manager-section-rbkXsZVR.js → extension-manager-section-Bu0qcB2i.js} +1 -1
  102. package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.br +0 -0
  103. package/dist/web/assets/extension-manager-section-Bu0qcB2i.js.gz +0 -0
  104. package/dist/web/assets/{extension-webview-uPO0WgCq.js → extension-webview-G_WJ0N52.js} +1 -1
  105. package/dist/web/assets/extension-webview-G_WJ0N52.js.br +0 -0
  106. package/dist/web/assets/extension-webview-G_WJ0N52.js.gz +0 -0
  107. package/dist/web/assets/{file-icons-gx_FBOXY.js → file-icons-l7fNOmdS.js} +1 -1
  108. package/dist/web/assets/file-icons-l7fNOmdS.js.br +0 -0
  109. package/dist/web/assets/file-icons-l7fNOmdS.js.gz +0 -0
  110. package/dist/web/assets/{file-store-D9KE0kQC.js → file-store-B0e7sLag.js} +1 -1
  111. package/dist/web/assets/file-store-B0e7sLag.js.br +0 -0
  112. package/dist/web/assets/file-store-B0e7sLag.js.gz +0 -0
  113. package/dist/web/assets/{files-settings-section-CT7mDb0L.js → files-settings-section-ChZ_CynX.js} +1 -1
  114. package/dist/web/assets/files-settings-section-ChZ_CynX.js.br +0 -0
  115. package/dist/web/assets/files-settings-section-ChZ_CynX.js.gz +0 -0
  116. package/dist/web/assets/format-bytes-__396eCN.js +1 -0
  117. package/dist/web/assets/{format-file-meta-mQwA6SuY.js → format-file-meta-o1XBuoKS.js} +1 -1
  118. package/dist/web/assets/format-file-meta-o1XBuoKS.js.br +0 -0
  119. package/dist/web/assets/format-file-meta-o1XBuoKS.js.gz +0 -0
  120. package/dist/web/assets/{fs-api-DL36slbd.js → fs-api-CeHKwx95.js} +1 -1
  121. package/dist/web/assets/fs-api-CeHKwx95.js.br +0 -0
  122. package/dist/web/assets/fs-api-CeHKwx95.js.gz +0 -0
  123. package/dist/web/assets/{general-settings-section-DANPfMRN.js → general-settings-section-MSvDEbFa.js} +1 -1
  124. package/dist/web/assets/general-settings-section-MSvDEbFa.js.br +0 -0
  125. package/dist/web/assets/general-settings-section-MSvDEbFa.js.gz +0 -0
  126. package/dist/web/assets/{git-log-panel-BJPkLiEw.js → git-log-panel-gw5_a6j3.js} +1 -1
  127. package/dist/web/assets/git-log-panel-gw5_a6j3.js.br +0 -0
  128. package/dist/web/assets/git-log-panel-gw5_a6j3.js.gz +0 -0
  129. package/dist/web/assets/{git-repo-store-DSypNj19.js → git-repo-store-BpPnah5l.js} +1 -1
  130. package/dist/web/assets/git-repo-store-BpPnah5l.js.br +0 -0
  131. package/dist/web/assets/git-repo-store-BpPnah5l.js.gz +0 -0
  132. package/dist/web/assets/{glide-data-grid-R4PcAntf.js → glide-data-grid-Db2Cl26B.js} +5 -5
  133. package/dist/web/assets/glide-data-grid-Db2Cl26B.js.br +0 -0
  134. package/dist/web/assets/glide-data-grid-Db2Cl26B.js.gz +0 -0
  135. package/dist/web/assets/group-chat-tab-B4etmYTR.js +1 -0
  136. package/dist/web/assets/group-chat-tab-B4etmYTR.js.br +0 -0
  137. package/dist/web/assets/group-chat-tab-B4etmYTR.js.gz +0 -0
  138. package/dist/web/assets/{image-preview-CGdZrmLo.js → image-preview-CjNhpfWw.js} +1 -1
  139. package/dist/web/assets/index-CtAvmybi.js +52 -0
  140. package/dist/web/assets/index-CtAvmybi.js.br +0 -0
  141. package/dist/web/assets/index-CtAvmybi.js.gz +0 -0
  142. package/dist/web/assets/index-DeCJasyy.css +1 -0
  143. package/dist/web/assets/index-DeCJasyy.css.br +0 -0
  144. package/dist/web/assets/index-DeCJasyy.css.gz +0 -0
  145. package/dist/web/assets/{jira-watcher-section-DHRoyWcG.js → jira-watcher-section-CKSI1Alo.js} +1 -1
  146. package/dist/web/assets/keybindings-store-C4_yyU4u.js +1 -0
  147. package/dist/web/assets/{keyboard-shortcuts-section-BKej4mUf.js → keyboard-shortcuts-section-CEaVUc9a.js} +1 -1
  148. package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.br +0 -0
  149. package/dist/web/assets/keyboard-shortcuts-section-CEaVUc9a.js.gz +0 -0
  150. package/dist/web/assets/language-servers-section-ePtEpnDc.js +2 -0
  151. package/dist/web/assets/language-servers-section-ePtEpnDc.js.br +0 -0
  152. package/dist/web/assets/language-servers-section-ePtEpnDc.js.gz +0 -0
  153. package/dist/web/assets/lsp-client-XzmR3Brr.js +1 -0
  154. package/dist/web/assets/lsp-client-XzmR3Brr.js.br +0 -0
  155. package/dist/web/assets/lsp-client-XzmR3Brr.js.gz +0 -0
  156. package/dist/web/assets/markdown-renderer-CTtDap-l.js +1 -0
  157. package/dist/web/assets/{markdown-renderer-BHtYqkYZ.js → markdown-renderer-D05fAr57.js} +2 -2
  158. package/dist/web/assets/markdown-renderer-D05fAr57.js.br +0 -0
  159. package/dist/web/assets/markdown-renderer-D05fAr57.js.gz +0 -0
  160. package/dist/web/assets/{media-url-CzUJrREv.js → media-url-C6NumuN5.js} +1 -1
  161. package/dist/web/assets/metric-chart-canvas-DHJe0VER.js +1 -0
  162. package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.br +0 -0
  163. package/dist/web/assets/metric-chart-canvas-DHJe0VER.js.gz +0 -0
  164. package/dist/web/assets/{mobile-bottom-sheet-CuNi2FRv.js → mobile-bottom-sheet-D4UyOatB.js} +1 -1
  165. package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.br +0 -0
  166. package/dist/web/assets/mobile-bottom-sheet-D4UyOatB.js.gz +0 -0
  167. package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js +1 -0
  168. package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.br +0 -0
  169. package/dist/web/assets/mobile-explorer-sheet-CcfGdGL8.js.gz +0 -0
  170. package/dist/web/assets/{new-design-dialog-DS_XrpTG.js → new-design-dialog-BgmQopJM.js} +1 -1
  171. package/dist/web/assets/new-design-dialog-BgmQopJM.js.br +0 -0
  172. package/dist/web/assets/new-design-dialog-BgmQopJM.js.gz +0 -0
  173. package/dist/web/assets/notification-store-q5QBklKA.js +1 -0
  174. package/dist/web/assets/{notifications-settings-section-C05WYqcE.js → notifications-settings-section-BBJ0r9OS.js} +1 -1
  175. package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.br +0 -0
  176. package/dist/web/assets/notifications-settings-section-BBJ0r9OS.js.gz +0 -0
  177. package/dist/web/assets/{onboarding-entry-CmUqv1kl.js → onboarding-entry-BYOYswsi.js} +1 -1
  178. package/dist/web/assets/onboarding-entry-BYOYswsi.js.br +0 -0
  179. package/dist/web/assets/onboarding-entry-BYOYswsi.js.gz +0 -0
  180. package/dist/web/assets/{onboarding-quick-orientation-DIhWv1CN.js → onboarding-quick-orientation-DPHYFMbA.js} +1 -1
  181. package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.br +0 -0
  182. package/dist/web/assets/onboarding-quick-orientation-DPHYFMbA.js.gz +0 -0
  183. package/dist/web/assets/overview-panel-pkPs44Tv.js +1 -0
  184. package/dist/web/assets/overview-panel-pkPs44Tv.js.br +0 -0
  185. package/dist/web/assets/overview-panel-pkPs44Tv.js.gz +0 -0
  186. package/dist/web/assets/{pdf-preview-qdXmMkOR.js → pdf-preview-c-L_kDAs.js} +1 -1
  187. package/dist/web/assets/pdf-preview-c-L_kDAs.js.br +0 -0
  188. package/dist/web/assets/pdf-preview-c-L_kDAs.js.gz +0 -0
  189. package/dist/web/assets/performance-panel-yPrq0DJV.js +3 -0
  190. package/dist/web/assets/performance-panel-yPrq0DJV.js.br +0 -0
  191. package/dist/web/assets/performance-panel-yPrq0DJV.js.gz +0 -0
  192. package/dist/web/assets/{postgres-viewer-Xvlun_n5.js → postgres-viewer-CnwDCMNd.js} +1 -1
  193. package/dist/web/assets/postgres-viewer-CnwDCMNd.js.br +0 -0
  194. package/dist/web/assets/postgres-viewer-CnwDCMNd.js.gz +0 -0
  195. package/dist/web/assets/{ppmbot-settings-section-D8AES1BA.js → ppmbot-settings-section-BDXoSIDp.js} +1 -1
  196. package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.br +0 -0
  197. package/dist/web/assets/ppmbot-settings-section-BDXoSIDp.js.gz +0 -0
  198. package/dist/web/assets/{pptx-export-CG6MRNok.js → pptx-export-D0RDY48h.js} +4 -4
  199. package/dist/web/assets/pptx-export-D0RDY48h.js.br +0 -0
  200. package/dist/web/assets/pptx-export-D0RDY48h.js.gz +0 -0
  201. package/dist/web/assets/{problems-panel-DTBG4beI.js → problems-panel-BgY8oyo9.js} +1 -1
  202. package/dist/web/assets/problems-panel-BgY8oyo9.js.br +0 -0
  203. package/dist/web/assets/problems-panel-BgY8oyo9.js.gz +0 -0
  204. package/dist/web/assets/process-row-format-CaaMcHzn.js +1 -0
  205. package/dist/web/assets/process-table-BOwDtXuG.js +1 -0
  206. package/dist/web/assets/process-table-BOwDtXuG.js.br +0 -0
  207. package/dist/web/assets/process-table-BOwDtXuG.js.gz +0 -0
  208. package/dist/web/assets/{project-framework-store-XxFXcUpb.js → project-framework-store-UDEMu28U.js} +1 -1
  209. package/dist/web/assets/{project-store-C5gJXkvb.js → project-store-DGa31y5B.js} +1 -1
  210. package/dist/web/assets/project-store-DGa31y5B.js.br +0 -0
  211. package/dist/web/assets/project-store-DGa31y5B.js.gz +0 -0
  212. package/dist/web/assets/{proxy-settings-section-CqtmbeRj.js → proxy-settings-section-Db6Kc6xG.js} +1 -1
  213. package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.br +0 -0
  214. package/dist/web/assets/proxy-settings-section-Db6Kc6xG.js.gz +0 -0
  215. package/dist/web/assets/{query-audit-section-UKG_smyt.js → query-audit-section-C0PAXhfT.js} +1 -1
  216. package/dist/web/assets/query-audit-section-C0PAXhfT.js.br +0 -0
  217. package/dist/web/assets/query-audit-section-C0PAXhfT.js.gz +0 -0
  218. package/dist/web/assets/{remote-desktop-clipboard-notice-Djt6cpNL.js → remote-desktop-clipboard-notice-DOVtobj_.js} +1 -1
  219. package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.br +0 -0
  220. package/dist/web/assets/remote-desktop-clipboard-notice-DOVtobj_.js.gz +0 -0
  221. package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js +2 -0
  222. package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.br +0 -0
  223. package/dist/web/assets/remote-desktop-mobile-sheet-DXlU3msM.js.gz +0 -0
  224. package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js +1 -0
  225. package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.br +0 -0
  226. package/dist/web/assets/remote-desktop-mobile-view-ClV4YTLU.js.gz +0 -0
  227. package/dist/web/assets/{remote-desktop-readiness-gate-DNJF8TcF.js → remote-desktop-readiness-gate-BsfoJzdc.js} +1 -1
  228. package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.br +0 -0
  229. package/dist/web/assets/remote-desktop-readiness-gate-BsfoJzdc.js.gz +0 -0
  230. package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js +1 -0
  231. package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.br +0 -0
  232. package/dist/web/assets/remote-desktop-window-content-kDaXl1_Q.js.gz +0 -0
  233. package/dist/web/assets/{schedules-settings-section-BKoPovy2.js → schedules-settings-section-CvbjQM8z.js} +1 -1
  234. package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.br +0 -0
  235. package/dist/web/assets/schedules-settings-section-CvbjQM8z.js.gz +0 -0
  236. package/dist/web/assets/{scroll-area-XonvWYn_.js → scroll-area-sjOeAfoD.js} +1 -1
  237. package/dist/web/assets/scroll-area-sjOeAfoD.js.br +0 -0
  238. package/dist/web/assets/scroll-area-sjOeAfoD.js.gz +0 -0
  239. package/dist/web/assets/{separator-sQj04Ari.js → separator-9A3PCG1t.js} +1 -1
  240. package/dist/web/assets/services-panel-CimW47P8.js +1 -0
  241. package/dist/web/assets/services-panel-CimW47P8.js.br +0 -0
  242. package/dist/web/assets/services-panel-CimW47P8.js.gz +0 -0
  243. package/dist/web/assets/settings-body-BTXNtpWL.js +2 -0
  244. package/dist/web/assets/settings-body-BTXNtpWL.js.br +0 -0
  245. package/dist/web/assets/settings-body-BTXNtpWL.js.gz +0 -0
  246. package/dist/web/assets/settings-store-DvftLqzy.js +2 -0
  247. package/dist/web/assets/settings-store-DvftLqzy.js.br +0 -0
  248. package/dist/web/assets/settings-store-DvftLqzy.js.gz +0 -0
  249. package/dist/web/assets/settings-tab-CGhaRXjs.js +1 -0
  250. package/dist/web/assets/settings-window-content--Uyg_Psr.js +1 -0
  251. package/dist/web/assets/sortable-header-DUW9T1Hz.js +1 -0
  252. package/dist/web/assets/sortable-header-DUW9T1Hz.js.br +0 -0
  253. package/dist/web/assets/sortable-header-DUW9T1Hz.js.gz +0 -0
  254. package/dist/web/assets/{sql-query-editor-BjIMi9Wk.js → sql-query-editor-Dn-cgDrW.js} +1 -1
  255. package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.br +0 -0
  256. package/dist/web/assets/sql-query-editor-Dn-cgDrW.js.gz +0 -0
  257. package/dist/web/assets/{sqlite-viewer-BNcA6Irq.js → sqlite-viewer-B0NfaMjq.js} +1 -1
  258. package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.br +0 -0
  259. package/dist/web/assets/sqlite-viewer-B0NfaMjq.js.gz +0 -0
  260. package/dist/web/assets/system-monitor-body-CxCfRQFb.js +2 -0
  261. package/dist/web/assets/system-monitor-body-CxCfRQFb.js.br +0 -0
  262. package/dist/web/assets/system-monitor-body-CxCfRQFb.js.gz +0 -0
  263. package/dist/web/assets/system-monitor-tab-Cb3EZUdp.js +1 -0
  264. package/dist/web/assets/system-monitor-window-content-51CA-2PH.js +1 -0
  265. package/dist/web/assets/{tab-host-window-content-CIRGNCvi.js → tab-host-window-content-CSrcmKNG.js} +1 -1
  266. package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.br +0 -0
  267. package/dist/web/assets/tab-host-window-content-CSrcmKNG.js.gz +0 -0
  268. package/dist/web/assets/{team-member-sheet-CwXpLi3X.js → team-member-sheet-D4X6nh-x.js} +2 -2
  269. package/dist/web/assets/team-member-sheet-D4X6nh-x.js.br +0 -0
  270. package/dist/web/assets/team-member-sheet-D4X6nh-x.js.gz +0 -0
  271. package/dist/web/assets/{team-member-window-content-4swiiDz7.js → team-member-window-content-BxkbGVIL.js} +1 -1
  272. package/dist/web/assets/team-member-window-content-BxkbGVIL.js.br +0 -0
  273. package/dist/web/assets/team-member-window-content-BxkbGVIL.js.gz +0 -0
  274. package/dist/web/assets/{terminal-tab-BGcdtMMT.js → terminal-tab-BYo34IqU.js} +3 -3
  275. package/dist/web/assets/terminal-tab-BYo34IqU.js.br +0 -0
  276. package/dist/web/assets/terminal-tab-BYo34IqU.js.gz +0 -0
  277. package/dist/web/assets/{tool-cards-D0-krtA2.js → tool-cards-DvXjOped.js} +4 -4
  278. package/dist/web/assets/tool-cards-DvXjOped.js.br +0 -0
  279. package/dist/web/assets/tool-cards-DvXjOped.js.gz +0 -0
  280. package/dist/web/assets/{use-blob-url-CmavyEP8.js → use-blob-url-CISBWKG5.js} +1 -1
  281. package/dist/web/assets/use-inline-blame-Br6mheS4.js +16 -0
  282. package/dist/web/assets/use-inline-blame-Br6mheS4.js.br +0 -0
  283. package/dist/web/assets/use-inline-blame-Br6mheS4.js.gz +0 -0
  284. package/dist/web/assets/use-lsp-BpJmvQ0n.js +6 -0
  285. package/dist/web/assets/use-lsp-BpJmvQ0n.js.br +0 -0
  286. package/dist/web/assets/use-lsp-BpJmvQ0n.js.gz +0 -0
  287. package/dist/web/assets/{use-monaco-theme-C88KphGx.js → use-monaco-theme-UGOzyIhv.js} +1 -1
  288. package/dist/web/assets/use-process-kill-CyiiUD1E.js +1 -0
  289. package/dist/web/assets/use-process-kill-CyiiUD1E.js.br +0 -0
  290. package/dist/web/assets/use-process-kill-CyiiUD1E.js.gz +0 -0
  291. package/dist/web/assets/{use-websocket-Chk9guIU.js → use-websocket-Dpl21cHG.js} +1 -1
  292. package/dist/web/assets/{vendor-ui-BHh12_0v.js → vendor-ui-P9Ym7hCv.js} +1 -1
  293. package/dist/web/assets/vendor-ui-P9Ym7hCv.js.br +0 -0
  294. package/dist/web/assets/vendor-ui-P9Ym7hCv.js.gz +0 -0
  295. package/dist/web/assets/video-preview-D6Jw-IZK.js +1 -0
  296. package/dist/web/assets/video-preview-D6Jw-IZK.js.br +0 -0
  297. package/dist/web/assets/video-preview-D6Jw-IZK.js.gz +0 -0
  298. package/dist/web/assets/voice-settings-section-CcHQDYzN.js +1 -0
  299. package/dist/web/assets/voice-settings-section-CcHQDYzN.js.br +0 -0
  300. package/dist/web/assets/voice-settings-section-CcHQDYzN.js.gz +0 -0
  301. package/dist/web/assets/ws-auth-CqI8-aoA.js +1 -0
  302. package/dist/web/index.html +20 -20
  303. package/dist/web/index.html.br +0 -0
  304. package/dist/web/index.html.gz +0 -0
  305. package/dist/web/sw.js +1 -1
  306. package/dist/web/sw.js.br +0 -0
  307. package/dist/web/sw.js.gz +0 -0
  308. package/docs/deployment-guide.md +23 -0
  309. package/package.json +1 -1
  310. package/release-from-worktree.sh +224 -0
  311. package/src/cli/commands/config-cmd.ts +12 -0
  312. package/src/providers/claude-agent-sdk.ts +65 -2
  313. package/src/providers/codex-app-server/codex-jsonrpc-client.ts +16 -1
  314. package/src/providers/codex-app-server/codex-provider.ts +39 -7
  315. package/src/providers/provider-probe.ts +63 -0
  316. package/src/providers/registry.ts +109 -29
  317. package/src/server/index.ts +9 -0
  318. package/src/server/middleware/auth.ts +5 -1
  319. package/src/server/routes/cross-origin-guard.ts +20 -0
  320. package/src/server/routes/git.ts +40 -4
  321. package/src/server/routes/lsp.ts +97 -4
  322. package/src/server/routes/resources.ts +72 -9
  323. package/src/server/routes/settings.ts +19 -1
  324. package/src/server/routes/speech.ts +66 -0
  325. package/src/server/routes/system-services.ts +76 -0
  326. package/src/server/routes/tunnel.ts +33 -1
  327. package/src/server/ws/chat.ts +74 -3
  328. package/src/services/codex-account-login.ts +26 -2
  329. package/src/services/codex-reset-credit.service.ts +16 -3
  330. package/src/services/compaction-savings.ts +127 -0
  331. package/src/services/db.service.ts +123 -8
  332. package/src/services/fs-ops/fs-ops-trash.service.ts +22 -9
  333. package/src/services/git-refs/for-each-ref.ts +118 -0
  334. package/src/services/git.service.ts +25 -2
  335. package/src/services/host-info/spawn-runner.ts +48 -4
  336. package/src/services/jsonl-transcript-parser.ts +7 -42
  337. package/src/services/lsp/lsp-install.ts +289 -0
  338. package/src/services/lsp/lsp-manager.ts +160 -29
  339. package/src/services/lsp/server-registry.ts +120 -4
  340. package/src/services/named-tunnel/named-tunnel-config.ts +8 -2
  341. package/src/services/named-tunnel/named-tunnel-setup.service.ts +4 -0
  342. package/src/services/read-lines.ts +54 -0
  343. package/src/services/speech-to-text/whisper-catalog.ts +142 -0
  344. package/src/services/speech-to-text/whisper-download.ts +113 -0
  345. package/src/services/speech-to-text/whisper-hallucinations.ts +51 -0
  346. package/src/services/speech-to-text/whisper-install.service.ts +247 -0
  347. package/src/services/speech-to-text/whisper-paths.ts +62 -0
  348. package/src/services/speech-to-text/whisper-transcribe.service.ts +117 -0
  349. package/src/services/supervisor.ts +94 -11
  350. package/src/services/system-metrics/cpu-details-linux.ts +252 -0
  351. package/src/services/system-metrics/cpu-memory-collector.ts +140 -18
  352. package/src/services/system-metrics/device-collector-linux.ts +31 -0
  353. package/src/services/system-metrics/device-collector-types.ts +40 -0
  354. package/src/services/system-metrics/disk-devices-linux.ts +156 -0
  355. package/src/services/system-metrics/disk-inventory-linux.ts +150 -0
  356. package/src/services/system-metrics/fans-linux.ts +51 -0
  357. package/src/services/system-metrics/gpu-api-versions.ts +120 -0
  358. package/src/services/system-metrics/gpu-devices-linux.ts +370 -0
  359. package/src/services/system-metrics/gpu-fdinfo-linux.ts +253 -0
  360. package/src/services/system-metrics/hardware-inventory.ts +50 -0
  361. package/src/services/system-metrics/linux-fs.ts +43 -0
  362. package/src/services/system-metrics/memory-darwin.ts +91 -0
  363. package/src/services/system-metrics/memory-linux.ts +156 -0
  364. package/src/services/system-metrics/net-devices-linux.ts +114 -0
  365. package/src/services/system-metrics/net-inventory-linux.ts +100 -0
  366. package/src/services/system-metrics/partitions-linux.ts +158 -0
  367. package/src/services/system-metrics/process-collector-linux.ts +58 -6
  368. package/src/services/system-metrics/process-collector-types.ts +8 -1
  369. package/src/services/system-metrics/process-details-darwin.ts +166 -0
  370. package/src/services/system-metrics/process-details-linux.ts +150 -0
  371. package/src/services/system-metrics/process-grouping.ts +2 -1
  372. package/src/services/system-metrics/process-rows-builder.ts +2 -0
  373. package/src/services/system-metrics/process-swap-linux.ts +37 -0
  374. package/src/services/system-metrics/signal-executor.ts +84 -0
  375. package/src/services/system-metrics/signal-request-handler.ts +92 -0
  376. package/src/services/system-metrics/system-metrics-platform.ts +30 -4
  377. package/src/services/system-metrics/system-metrics-tick.ts +73 -4
  378. package/src/services/system-metrics/system-metrics.service.ts +87 -2
  379. package/src/services/system-metrics/udev-db.ts +40 -0
  380. package/src/services/system-services/app-cgroup-linux.ts +115 -0
  381. package/src/services/system-services/app-icon-service.ts +31 -0
  382. package/src/services/system-services/app-icons-linux.ts +124 -0
  383. package/src/services/system-services/apps-linux.ts +174 -0
  384. package/src/services/system-services/desktop-entry.ts +103 -0
  385. package/src/services/system-services/service-guard.ts +124 -0
  386. package/src/services/system-services/systemd-collector.ts +203 -0
  387. package/src/services/system-services/systemd-parse.ts +162 -0
  388. package/src/shared/prompt-cache-idle.ts +197 -0
  389. package/src/shared/turn-usage.ts +85 -1
  390. package/src/types/chat.ts +27 -0
  391. package/src/types/config.ts +14 -0
  392. package/src/types/git.ts +41 -0
  393. package/src/types/system-hardware.ts +137 -0
  394. package/src/types/system-metrics.ts +231 -2
  395. package/src/types/system-services.ts +73 -0
  396. package/src/web/components/branch-review/branch-review-tab.tsx +20 -20
  397. package/src/web/components/branch-review/branch-review-tree-row.tsx +1 -13
  398. package/src/web/components/chat/chat-tab.tsx +3 -0
  399. package/src/web/components/chat/compaction-divider.tsx +48 -0
  400. package/src/web/components/chat/idle-cache-notice.tsx +67 -0
  401. package/src/web/components/chat/message-input.tsx +48 -13
  402. package/src/web/components/chat/message-list.tsx +15 -0
  403. package/src/web/components/chat/prompt-cache-chip.tsx +74 -0
  404. package/src/web/components/editor/code-editor.tsx +1 -0
  405. package/src/web/components/editor/editor-language-service.tsx +11 -3
  406. package/src/web/components/editor/lsp-status.tsx +115 -13
  407. package/src/web/components/git/branch-picker.tsx +419 -0
  408. package/src/web/components/git/branch-select.tsx +34 -0
  409. package/src/web/components/git/create-worktree-dialog.tsx +17 -16
  410. package/src/web/components/git/git-repo-picker.tsx +28 -40
  411. package/src/web/components/layout/status-bar.tsx +42 -19
  412. package/src/web/components/settings/accounts/account-card.tsx +5 -1
  413. package/src/web/components/settings/ai-settings-section.tsx +67 -3
  414. package/src/web/components/settings/appearance-settings-section.tsx +2 -28
  415. package/src/web/components/settings/language-servers-section.tsx +362 -0
  416. package/src/web/components/settings/settings-categories.ts +8 -3
  417. package/src/web/components/settings/settings-section-content.tsx +2 -0
  418. package/src/web/components/settings/voice-settings-section.tsx +283 -0
  419. package/src/web/components/system/apps/app-row.tsx +110 -0
  420. package/src/web/components/system/apps/app-rows.ts +140 -0
  421. package/src/web/components/system/apps/apps-panel.tsx +129 -0
  422. package/src/web/components/system/metric-chart-canvas.tsx +79 -3
  423. package/src/web/components/system/overview-cards/card-shell.tsx +54 -0
  424. package/src/web/components/system/overview-cards/cpu-card.tsx +11 -6
  425. package/src/web/components/system/overview-cards/disk-card.tsx +11 -6
  426. package/src/web/components/system/overview-cards/gpu-card.tsx +16 -6
  427. package/src/web/components/system/overview-cards/mem-card.tsx +11 -6
  428. package/src/web/components/system/overview-cards/net-card.tsx +11 -6
  429. package/src/web/components/system/overview-device-target.ts +69 -0
  430. package/src/web/components/system/overview-panel.tsx +15 -1
  431. package/src/web/components/system/performance/cpu-core-grid.tsx +74 -0
  432. package/src/web/components/system/performance/cpu-detail.tsx +220 -0
  433. package/src/web/components/system/performance/detail-parts.tsx +149 -0
  434. package/src/web/components/system/performance/device-list.ts +139 -0
  435. package/src/web/components/system/performance/disk-detail.tsx +64 -0
  436. package/src/web/components/system/performance/fans-detail.tsx +35 -0
  437. package/src/web/components/system/performance/gpu-detail.tsx +116 -0
  438. package/src/web/components/system/performance/memory-detail.tsx +109 -0
  439. package/src/web/components/system/performance/nic-detail.tsx +55 -0
  440. package/src/web/components/system/performance/partition-list.tsx +79 -0
  441. package/src/web/components/system/performance/performance-panel.tsx +206 -0
  442. package/src/web/components/system/performance/use-hardware-inventory.ts +48 -0
  443. package/src/web/components/system/process-columns-grid.ts +5 -3
  444. package/src/web/components/system/process-details-dialog.tsx +116 -0
  445. package/src/web/components/system/process-group-row.tsx +6 -1
  446. package/src/web/components/system/process-row-format.ts +6 -0
  447. package/src/web/components/system/process-row-menu.tsx +72 -0
  448. package/src/web/components/system/process-row.tsx +7 -1
  449. package/src/web/components/system/process-table-model.ts +1 -1
  450. package/src/web/components/system/process-table-sort.ts +8 -4
  451. package/src/web/components/system/process-table-toolbar.tsx +10 -2
  452. package/src/web/components/system/process-table-totals.ts +3 -0
  453. package/src/web/components/system/process-table.tsx +25 -2
  454. package/src/web/components/system/services/service-action-confirm.tsx +100 -0
  455. package/src/web/components/system/services/service-columns.ts +103 -0
  456. package/src/web/components/system/services/service-details-sheet.tsx +97 -0
  457. package/src/web/components/system/services/service-resources.ts +105 -0
  458. package/src/web/components/system/services/service-row.tsx +146 -0
  459. package/src/web/components/system/services/service-rows.ts +92 -0
  460. package/src/web/components/system/services/service-sort.ts +90 -0
  461. package/src/web/components/system/services/services-header.tsx +51 -0
  462. package/src/web/components/system/services/services-panel.tsx +207 -0
  463. package/src/web/components/system/services/use-services.ts +92 -0
  464. package/src/web/components/system/sortable-header.tsx +13 -8
  465. package/src/web/components/system/sysmon-preferences.tsx +85 -0
  466. package/src/web/components/system/system-monitor-body.tsx +101 -43
  467. package/src/web/components/system/system-monitor-window-content.tsx +2 -3
  468. package/src/web/components/system/use-process-signal.ts +87 -0
  469. package/src/web/components/tunnels/public-tunnel-switch.tsx +82 -0
  470. package/src/web/components/tunnels/tunnel-manager-tab.tsx +4 -0
  471. package/src/web/components/ui/dialog.tsx +14 -1
  472. package/src/web/components/ui/search-select.tsx +279 -0
  473. package/src/web/components/ui/start-ellipsis.tsx +18 -0
  474. package/src/web/hooks/use-chat.ts +28 -0
  475. package/src/web/hooks/use-global-keybindings.ts +7 -5
  476. package/src/web/hooks/use-lsp.ts +23 -2
  477. package/src/web/hooks/use-minute-clock.ts +20 -0
  478. package/src/web/hooks/use-whisper-voice-input.ts +113 -0
  479. package/src/web/lib/api-tunnels.ts +19 -0
  480. package/src/web/lib/branch-select-items.ts +29 -0
  481. package/src/web/lib/cpu-graph-mode.ts +90 -0
  482. package/src/web/lib/format-bytes.ts +14 -0
  483. package/src/web/lib/git-ref-picker.ts +120 -0
  484. package/src/web/lib/lsp/lsp-client.ts +32 -0
  485. package/src/web/lib/search-select-rows.ts +55 -0
  486. package/src/web/lib/temperature.ts +27 -0
  487. package/src/web/lib/voice-wav.ts +69 -0
  488. package/src/web/stores/git-status-store.ts +31 -17
  489. package/src/web/stores/keybindings-store.ts +14 -1
  490. package/src/web/stores/settings-store.ts +65 -0
  491. package/src/web/styles/globals.css +11 -0
  492. package/dist/web/assets/accounts-settings-section-DUVO32SQ.js +0 -1
  493. package/dist/web/assets/accounts-settings-section-DUVO32SQ.js.br +0 -0
  494. package/dist/web/assets/accounts-settings-section-DUVO32SQ.js.gz +0 -0
  495. package/dist/web/assets/ai-resource-editor-Dn_wB-o5.js.br +0 -0
  496. package/dist/web/assets/ai-resource-editor-Dn_wB-o5.js.gz +0 -0
  497. package/dist/web/assets/ai-settings-section-CEbW8jRy.js +0 -1
  498. package/dist/web/assets/ai-settings-section-CEbW8jRy.js.br +0 -0
  499. package/dist/web/assets/ai-settings-section-CEbW8jRy.js.gz +0 -0
  500. package/dist/web/assets/ai-settings-section-D-mwEYFZ.js +0 -1
  501. package/dist/web/assets/api-client-BgedDTvo.js +0 -1
  502. package/dist/web/assets/api-client-c8YOpTlT.js.br +0 -0
  503. package/dist/web/assets/api-client-c8YOpTlT.js.gz +0 -0
  504. package/dist/web/assets/api-settings-BmvNoCbn.js.br +0 -0
  505. package/dist/web/assets/api-settings-BmvNoCbn.js.gz +0 -0
  506. package/dist/web/assets/api-settings-Bti1-Zn3.js +0 -1
  507. package/dist/web/assets/appearance-settings-section-tw6TpFb6.js +0 -1
  508. package/dist/web/assets/appearance-settings-section-tw6TpFb6.js.br +0 -0
  509. package/dist/web/assets/appearance-settings-section-tw6TpFb6.js.gz +0 -0
  510. package/dist/web/assets/audio-preview-vaG2cB-k.js.br +0 -0
  511. package/dist/web/assets/audio-preview-vaG2cB-k.js.gz +0 -0
  512. package/dist/web/assets/branch-review-tab-MBuMWp_i.js +0 -1
  513. package/dist/web/assets/branch-review-tab-MBuMWp_i.js.br +0 -0
  514. package/dist/web/assets/branch-review-tab-MBuMWp_i.js.gz +0 -0
  515. package/dist/web/assets/button-Ci9EUgqd.js.br +0 -0
  516. package/dist/web/assets/button-Ci9EUgqd.js.gz +0 -0
  517. package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.br +0 -0
  518. package/dist/web/assets/chat-preparation-cache-BSIQGiWx.js.gz +0 -0
  519. package/dist/web/assets/chat-tab-DVvnqhw6.js +0 -1
  520. package/dist/web/assets/chat-tab-DVvnqhw6.js.br +0 -0
  521. package/dist/web/assets/chat-tab-DVvnqhw6.js.gz +0 -0
  522. package/dist/web/assets/chat-tab-DaEHva22.js +0 -15
  523. package/dist/web/assets/chat-tab-DaEHva22.js.br +0 -0
  524. package/dist/web/assets/chat-tab-DaEHva22.js.gz +0 -0
  525. package/dist/web/assets/code-editor-Bu9lhTVp.js +0 -7
  526. package/dist/web/assets/code-editor-Bu9lhTVp.js.br +0 -0
  527. package/dist/web/assets/code-editor-Bu9lhTVp.js.gz +0 -0
  528. package/dist/web/assets/codex-reset-credit-button-CbmE5mRX.js +0 -1
  529. package/dist/web/assets/codex-reset-credit-button-CbmE5mRX.js.br +0 -0
  530. package/dist/web/assets/codex-reset-credit-button-CbmE5mRX.js.gz +0 -0
  531. package/dist/web/assets/conflict-editor-BA3y-qSH.js.br +0 -0
  532. package/dist/web/assets/conflict-editor-BA3y-qSH.js.gz +0 -0
  533. package/dist/web/assets/context-menu-CjYTq8DI.js +0 -1
  534. package/dist/web/assets/context-menu-CjYTq8DI.js.br +0 -0
  535. package/dist/web/assets/context-menu-CjYTq8DI.js.gz +0 -0
  536. package/dist/web/assets/csv-preview-BioxkrQF.js.br +0 -0
  537. package/dist/web/assets/csv-preview-BioxkrQF.js.gz +0 -0
  538. package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.br +0 -0
  539. package/dist/web/assets/data-grid-overlay-editor-CQRVKr7y.js.gz +0 -0
  540. package/dist/web/assets/database-viewer-BSoJ0E6p.js.br +0 -0
  541. package/dist/web/assets/database-viewer-BSoJ0E6p.js.gz +0 -0
  542. package/dist/web/assets/design-export-client-ChMcMoqW.js.br +0 -0
  543. package/dist/web/assets/design-export-client-ChMcMoqW.js.gz +0 -0
  544. package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.br +0 -0
  545. package/dist/web/assets/design-responsive-dialog-DNBhJJLf.js.gz +0 -0
  546. package/dist/web/assets/design-tab-iU2CviE3.js +0 -33
  547. package/dist/web/assets/design-tab-iU2CviE3.js.br +0 -0
  548. package/dist/web/assets/design-tab-iU2CviE3.js.gz +0 -0
  549. package/dist/web/assets/designs-sidebar-panel-DHh5QsH9.js.br +0 -0
  550. package/dist/web/assets/designs-sidebar-panel-DHh5QsH9.js.gz +0 -0
  551. package/dist/web/assets/dialog-B8clVkyi.js +0 -1
  552. package/dist/web/assets/dialog-B8clVkyi.js.br +0 -0
  553. package/dist/web/assets/dialog-B8clVkyi.js.gz +0 -0
  554. package/dist/web/assets/diff-viewer-lctWoSmG.js.br +0 -0
  555. package/dist/web/assets/diff-viewer-lctWoSmG.js.gz +0 -0
  556. package/dist/web/assets/docx-preview-C9XdKEj1.js.br +0 -0
  557. package/dist/web/assets/docx-preview-C9XdKEj1.js.gz +0 -0
  558. package/dist/web/assets/editor-language-service-fPG56hQO.js +0 -1
  559. package/dist/web/assets/editor-language-service-fPG56hQO.js.br +0 -0
  560. package/dist/web/assets/editor-language-service-fPG56hQO.js.gz +0 -0
  561. package/dist/web/assets/explorer-body-BgF7FsxI.js +0 -2
  562. package/dist/web/assets/explorer-body-BgF7FsxI.js.br +0 -0
  563. package/dist/web/assets/explorer-body-BgF7FsxI.js.gz +0 -0
  564. package/dist/web/assets/explorer-window-content-C21w_7lf.js +0 -1
  565. package/dist/web/assets/explorer-window-content-C21w_7lf.js.br +0 -0
  566. package/dist/web/assets/explorer-window-content-C21w_7lf.js.gz +0 -0
  567. package/dist/web/assets/extension-manager-section-rbkXsZVR.js.br +0 -0
  568. package/dist/web/assets/extension-manager-section-rbkXsZVR.js.gz +0 -0
  569. package/dist/web/assets/extension-webview-uPO0WgCq.js.br +0 -0
  570. package/dist/web/assets/extension-webview-uPO0WgCq.js.gz +0 -0
  571. package/dist/web/assets/file-icons-gx_FBOXY.js.br +0 -0
  572. package/dist/web/assets/file-icons-gx_FBOXY.js.gz +0 -0
  573. package/dist/web/assets/file-store-D9KE0kQC.js.br +0 -0
  574. package/dist/web/assets/file-store-D9KE0kQC.js.gz +0 -0
  575. package/dist/web/assets/files-settings-section-CT7mDb0L.js.br +0 -0
  576. package/dist/web/assets/files-settings-section-CT7mDb0L.js.gz +0 -0
  577. package/dist/web/assets/format-bytes-DmSHAKFe.js +0 -1
  578. package/dist/web/assets/format-file-meta-mQwA6SuY.js.br +0 -0
  579. package/dist/web/assets/format-file-meta-mQwA6SuY.js.gz +0 -0
  580. package/dist/web/assets/fs-api-DL36slbd.js.br +0 -0
  581. package/dist/web/assets/fs-api-DL36slbd.js.gz +0 -0
  582. package/dist/web/assets/general-settings-section-DANPfMRN.js.br +0 -0
  583. package/dist/web/assets/general-settings-section-DANPfMRN.js.gz +0 -0
  584. package/dist/web/assets/git-log-panel-BJPkLiEw.js.br +0 -0
  585. package/dist/web/assets/git-log-panel-BJPkLiEw.js.gz +0 -0
  586. package/dist/web/assets/git-repo-store-DSypNj19.js.br +0 -0
  587. package/dist/web/assets/git-repo-store-DSypNj19.js.gz +0 -0
  588. package/dist/web/assets/glide-data-grid-R4PcAntf.js.br +0 -0
  589. package/dist/web/assets/glide-data-grid-R4PcAntf.js.gz +0 -0
  590. package/dist/web/assets/group-chat-tab-BXMdo8w9.js +0 -1
  591. package/dist/web/assets/group-chat-tab-BXMdo8w9.js.br +0 -0
  592. package/dist/web/assets/group-chat-tab-BXMdo8w9.js.gz +0 -0
  593. package/dist/web/assets/index-C8nGqkKN.js +0 -52
  594. package/dist/web/assets/index-C8nGqkKN.js.br +0 -0
  595. package/dist/web/assets/index-C8nGqkKN.js.gz +0 -0
  596. package/dist/web/assets/index-DvClbTQx.css +0 -1
  597. package/dist/web/assets/index-DvClbTQx.css.br +0 -0
  598. package/dist/web/assets/index-DvClbTQx.css.gz +0 -0
  599. package/dist/web/assets/keybindings-store-BGKlQQAv.js +0 -1
  600. package/dist/web/assets/keyboard-shortcuts-section-BKej4mUf.js.br +0 -0
  601. package/dist/web/assets/keyboard-shortcuts-section-BKej4mUf.js.gz +0 -0
  602. package/dist/web/assets/markdown-renderer-BHtYqkYZ.js.br +0 -0
  603. package/dist/web/assets/markdown-renderer-BHtYqkYZ.js.gz +0 -0
  604. package/dist/web/assets/markdown-renderer-vDNuKjel.js +0 -1
  605. package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.br +0 -0
  606. package/dist/web/assets/mobile-bottom-sheet-CuNi2FRv.js.gz +0 -0
  607. package/dist/web/assets/mobile-explorer-sheet-DtvxS7-r.js +0 -1
  608. package/dist/web/assets/mobile-explorer-sheet-DtvxS7-r.js.br +0 -0
  609. package/dist/web/assets/mobile-explorer-sheet-DtvxS7-r.js.gz +0 -0
  610. package/dist/web/assets/new-design-dialog-DS_XrpTG.js.br +0 -0
  611. package/dist/web/assets/new-design-dialog-DS_XrpTG.js.gz +0 -0
  612. package/dist/web/assets/notification-store-Dnnj20Ky.js +0 -1
  613. package/dist/web/assets/notifications-settings-section-C05WYqcE.js.br +0 -0
  614. package/dist/web/assets/notifications-settings-section-C05WYqcE.js.gz +0 -0
  615. package/dist/web/assets/onboarding-entry-CmUqv1kl.js.br +0 -0
  616. package/dist/web/assets/onboarding-entry-CmUqv1kl.js.gz +0 -0
  617. package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.br +0 -0
  618. package/dist/web/assets/onboarding-quick-orientation-DIhWv1CN.js.gz +0 -0
  619. package/dist/web/assets/overview-panel-BZAW_nag.js +0 -1
  620. package/dist/web/assets/overview-panel-BZAW_nag.js.br +0 -0
  621. package/dist/web/assets/overview-panel-BZAW_nag.js.gz +0 -0
  622. package/dist/web/assets/pdf-preview-qdXmMkOR.js.br +0 -0
  623. package/dist/web/assets/pdf-preview-qdXmMkOR.js.gz +0 -0
  624. package/dist/web/assets/postgres-viewer-Xvlun_n5.js.br +0 -0
  625. package/dist/web/assets/postgres-viewer-Xvlun_n5.js.gz +0 -0
  626. package/dist/web/assets/ppmbot-settings-section-D8AES1BA.js.br +0 -0
  627. package/dist/web/assets/ppmbot-settings-section-D8AES1BA.js.gz +0 -0
  628. package/dist/web/assets/pptx-export-CG6MRNok.js.br +0 -0
  629. package/dist/web/assets/pptx-export-CG6MRNok.js.gz +0 -0
  630. package/dist/web/assets/problems-panel-DTBG4beI.js.br +0 -0
  631. package/dist/web/assets/problems-panel-DTBG4beI.js.gz +0 -0
  632. package/dist/web/assets/process-table-D_gR2XA6.js +0 -1
  633. package/dist/web/assets/process-table-D_gR2XA6.js.br +0 -0
  634. package/dist/web/assets/process-table-D_gR2XA6.js.gz +0 -0
  635. package/dist/web/assets/project-store-C5gJXkvb.js.br +0 -0
  636. package/dist/web/assets/project-store-C5gJXkvb.js.gz +0 -0
  637. package/dist/web/assets/proxy-settings-section-CqtmbeRj.js.br +0 -0
  638. package/dist/web/assets/proxy-settings-section-CqtmbeRj.js.gz +0 -0
  639. package/dist/web/assets/query-audit-section-UKG_smyt.js.br +0 -0
  640. package/dist/web/assets/query-audit-section-UKG_smyt.js.gz +0 -0
  641. package/dist/web/assets/remote-desktop-clipboard-notice-Djt6cpNL.js.br +0 -0
  642. package/dist/web/assets/remote-desktop-clipboard-notice-Djt6cpNL.js.gz +0 -0
  643. package/dist/web/assets/remote-desktop-mobile-sheet-BQxhJUkm.js +0 -2
  644. package/dist/web/assets/remote-desktop-mobile-sheet-BQxhJUkm.js.br +0 -0
  645. package/dist/web/assets/remote-desktop-mobile-sheet-BQxhJUkm.js.gz +0 -0
  646. package/dist/web/assets/remote-desktop-mobile-view-DGnT8RY7.js +0 -1
  647. package/dist/web/assets/remote-desktop-mobile-view-DGnT8RY7.js.br +0 -0
  648. package/dist/web/assets/remote-desktop-mobile-view-DGnT8RY7.js.gz +0 -0
  649. package/dist/web/assets/remote-desktop-readiness-gate-DNJF8TcF.js.br +0 -0
  650. package/dist/web/assets/remote-desktop-readiness-gate-DNJF8TcF.js.gz +0 -0
  651. package/dist/web/assets/remote-desktop-window-content-Ds0DKmM0.js +0 -1
  652. package/dist/web/assets/remote-desktop-window-content-Ds0DKmM0.js.br +0 -0
  653. package/dist/web/assets/remote-desktop-window-content-Ds0DKmM0.js.gz +0 -0
  654. package/dist/web/assets/schedules-settings-section-BKoPovy2.js.br +0 -0
  655. package/dist/web/assets/schedules-settings-section-BKoPovy2.js.gz +0 -0
  656. package/dist/web/assets/scroll-area-XonvWYn_.js.br +0 -0
  657. package/dist/web/assets/scroll-area-XonvWYn_.js.gz +0 -0
  658. package/dist/web/assets/settings-body-k2qPgRFe.js +0 -2
  659. package/dist/web/assets/settings-body-k2qPgRFe.js.br +0 -0
  660. package/dist/web/assets/settings-body-k2qPgRFe.js.gz +0 -0
  661. package/dist/web/assets/settings-store-Br5c1efC.js +0 -2
  662. package/dist/web/assets/settings-store-Br5c1efC.js.br +0 -0
  663. package/dist/web/assets/settings-store-Br5c1efC.js.gz +0 -0
  664. package/dist/web/assets/settings-tab-Bb23VdBu.js +0 -1
  665. package/dist/web/assets/settings-window-content-ClarWusz.js +0 -1
  666. package/dist/web/assets/sql-query-editor-BjIMi9Wk.js.br +0 -0
  667. package/dist/web/assets/sql-query-editor-BjIMi9Wk.js.gz +0 -0
  668. package/dist/web/assets/sqlite-viewer-BNcA6Irq.js.br +0 -0
  669. package/dist/web/assets/sqlite-viewer-BNcA6Irq.js.gz +0 -0
  670. package/dist/web/assets/system-monitor-body-D0T3ggt2.js +0 -2
  671. package/dist/web/assets/system-monitor-body-D0T3ggt2.js.br +0 -0
  672. package/dist/web/assets/system-monitor-body-D0T3ggt2.js.gz +0 -0
  673. package/dist/web/assets/system-monitor-tab-I1Igmoey.js +0 -1
  674. package/dist/web/assets/system-monitor-window-content-C5SeUv1H.js +0 -1
  675. package/dist/web/assets/tab-host-window-content-CIRGNCvi.js.br +0 -0
  676. package/dist/web/assets/tab-host-window-content-CIRGNCvi.js.gz +0 -0
  677. package/dist/web/assets/team-member-sheet-CwXpLi3X.js.br +0 -0
  678. package/dist/web/assets/team-member-sheet-CwXpLi3X.js.gz +0 -0
  679. package/dist/web/assets/team-member-window-content-4swiiDz7.js.br +0 -0
  680. package/dist/web/assets/team-member-window-content-4swiiDz7.js.gz +0 -0
  681. package/dist/web/assets/terminal-tab-BGcdtMMT.js.br +0 -0
  682. package/dist/web/assets/terminal-tab-BGcdtMMT.js.gz +0 -0
  683. package/dist/web/assets/tool-cards-D0-krtA2.js.br +0 -0
  684. package/dist/web/assets/tool-cards-D0-krtA2.js.gz +0 -0
  685. package/dist/web/assets/use-inline-blame-pKaXyGU-.js +0 -16
  686. package/dist/web/assets/use-inline-blame-pKaXyGU-.js.br +0 -0
  687. package/dist/web/assets/use-inline-blame-pKaXyGU-.js.gz +0 -0
  688. package/dist/web/assets/use-lsp-71azTXQz.js +0 -6
  689. package/dist/web/assets/use-lsp-71azTXQz.js.br +0 -0
  690. package/dist/web/assets/use-lsp-71azTXQz.js.gz +0 -0
  691. package/dist/web/assets/vendor-ui-BHh12_0v.js.br +0 -0
  692. package/dist/web/assets/vendor-ui-BHh12_0v.js.gz +0 -0
  693. package/dist/web/assets/video-preview-DxEX-uGo.js +0 -1
  694. package/dist/web/assets/video-preview-DxEX-uGo.js.br +0 -0
  695. package/dist/web/assets/video-preview-DxEX-uGo.js.gz +0 -0
  696. package/dist/web/assets/ws-auth-kXgsX2Js.js +0 -1
  697. /package/dist/web/assets/{__vite-browser-external-BpSQWxcq.js → __vite-browser-external-BILfYaAs.js} +0 -0
  698. /package/dist/web/assets/{run-in-terminal-HIdNgYim.js → run-in-terminal-2pSiup8e.js} +0 -0
  699. /package/dist/web/assets/{vendor-xterm-DLDtzLib.js → vendor-xterm-DF1JTfsy.js} +0 -0
  700. /package/dist/web/assets/{vendor-xterm-DLDtzLib.js.br → vendor-xterm-DF1JTfsy.js.br} +0 -0
  701. /package/dist/web/assets/{vendor-xterm-DLDtzLib.js.gz → vendor-xterm-DF1JTfsy.js.gz} +0 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * Whole-GPU figures for Mission Center's GPU page, from `/sys/class/drm/card*`.
3
+ *
4
+ * There is no one source: utilisation comes from the DRM fdinfo engine counters
5
+ * where the driver publishes them (Intel, AMD, and the only unprivileged option
6
+ * on Intel at all), from amdgpu's own `gpu_busy_percent` where it exists, and
7
+ * from i915's rc6 idle residency as a last resort. Anything a given driver does
8
+ * not expose stays ABSENT rather than 0, so the page hides the row.
9
+ *
10
+ * NVIDIA keeps its existing `nvidia-smi` collector: the proprietary driver
11
+ * publishes neither fdinfo engine counters nor the sysfs attributes below.
12
+ */
13
+ import os from "node:os";
14
+ import type { GpuInfo, GpuMetrics } from "../../types/system-metrics.ts";
15
+ import { readAttr, readNumber, realLinuxFs, type LinuxFs } from "./linux-fs.ts";
16
+ import { readUdevProperties, udevValue } from "./udev-db.ts";
17
+ import type { DrmUsage } from "./gpu-fdinfo-linux.ts";
18
+
19
+ export const DRM_DIR = "/sys/class/drm";
20
+ const RAPL_DIR = "/sys/class/powercap";
21
+
22
+ const VENDORS: Record<string, string> = {
23
+ "0x8086": "Intel",
24
+ "0x1002": "AMD",
25
+ "0x1022": "AMD",
26
+ "0x10de": "NVIDIA",
27
+ };
28
+
29
+ /** `current_link_speed` reads "8.0 GT/s PCIe"; the rate identifies the generation. */
30
+ const PCIE_GEN_BY_RATE: Record<string, number> = {
31
+ "2.5": 1, "5.0": 2, "8.0": 3, "16.0": 4, "32.0": 5, "64.0": 6,
32
+ };
33
+
34
+ export interface GpuCard {
35
+ /** "card1". */
36
+ card: string;
37
+ /** PCI address — the id both `GpuMetrics` and `GpuInfo` are keyed by. */
38
+ pdev: string;
39
+ driver: string;
40
+ vendor?: string;
41
+ }
42
+
43
+ /** Real render nodes only: `card1-DP-1` is a connector, not a device, and a
44
+ * driver with no PCI parent (vkms, a virtio display) has nothing to report. */
45
+ export function listGpuCards(fs: Pick<LinuxFs, "list" | "read" | "readlink" | "realpath">): GpuCard[] {
46
+ const cards: GpuCard[] = [];
47
+ for (const entry of (fs.list(DRM_DIR) ?? []).sort()) {
48
+ if (!/^card\d+$/.test(entry)) continue;
49
+ const real = fs.realpath(`${DRM_DIR}/${entry}/device`);
50
+ const pdev = real ? real.slice(real.lastIndexOf("/") + 1) : "";
51
+ if (!/^[0-9a-f]{4}:/i.test(pdev)) continue;
52
+ const driver = basename(fs.readlink(`${DRM_DIR}/${entry}/device/driver`) ?? "");
53
+ const vendorId = readAttr(fs, `${DRM_DIR}/${entry}/device/vendor`);
54
+ cards.push({
55
+ card: entry,
56
+ pdev,
57
+ driver,
58
+ ...(vendorId && VENDORS[vendorId] ? { vendor: VENDORS[vendorId] } : {}),
59
+ });
60
+ }
61
+ return cards;
62
+ }
63
+
64
+ export interface GpuSample {
65
+ atSec: number;
66
+ /** i915 cumulative idle residency, ms. */
67
+ rc6Ms?: number;
68
+ /** RAPL uncore energy, µJ — the integrated GPU's share of package power. */
69
+ uncoreUj?: number;
70
+ }
71
+
72
+ export type GpuSampleState = Map<string, GpuSample>;
73
+
74
+ export interface GpuDeviceCollection {
75
+ gpus: GpuMetrics[];
76
+ next: GpuSampleState;
77
+ }
78
+
79
+ export function collectLinuxGpus(
80
+ prev: GpuSampleState,
81
+ usage: DrmUsage,
82
+ fs: Pick<LinuxFs, "list" | "read" | "readlink" | "realpath"> = realLinuxFs,
83
+ now: () => number = Date.now,
84
+ totalRamBytes: number = os.totalmem(),
85
+ ): GpuDeviceCollection {
86
+ const atSec = now() / 1000;
87
+ const uncoreUj = readUncoreEnergy(fs);
88
+ const next: GpuSampleState = new Map();
89
+ const gpus: GpuMetrics[] = [];
90
+
91
+ for (const card of listGpuCards(fs)) {
92
+ // NVIDIA is covered by the nvidia-smi collector; listing it here too would
93
+ // publish the same GPU twice with half the figures missing.
94
+ if (card.driver === "nvidia" || card.driver === "nouveau") continue;
95
+ const dir = `${DRM_DIR}/${card.card}`;
96
+ const sample: GpuSample = {
97
+ atSec,
98
+ ...defined("rc6Ms", readNumber(fs, `${dir}/power/rc6_residency_ms`)),
99
+ ...defined("uncoreUj", uncoreUj),
100
+ };
101
+ next.set(card.card, sample);
102
+ gpus.push(buildGpuMetrics(card, dir, prev.get(card.card) ?? null, sample, usage, fs, totalRamBytes));
103
+ }
104
+ return { gpus, next };
105
+ }
106
+
107
+ function buildGpuMetrics(
108
+ card: GpuCard,
109
+ dir: string,
110
+ prev: GpuSample | null,
111
+ next: GpuSample,
112
+ usage: DrmUsage,
113
+ fs: Pick<LinuxFs, "list" | "read">,
114
+ totalRamBytes: number,
115
+ ): GpuMetrics {
116
+ const engines = usage.perDevice.get(card.pdev);
117
+ const integrated = card.driver === "i915" || card.driver === "xe";
118
+ const vram = readVram(dir, fs);
119
+ const hwmon = findHwmon(dir, fs);
120
+
121
+ return {
122
+ id: card.pdev,
123
+ name: gpuName(card, fs),
124
+ utilPercent: utilisation(card, dir, engines, prev, next, fs),
125
+ vramUsedMB: vram.usedMB,
126
+ vramTotalMB: vram.totalMB,
127
+ ...sharedMemory(card, dir, usage, fs, totalRamBytes),
128
+ ...videoEngines(card, engines),
129
+ ...defined("clockMHz", currentClockMHz(card, dir, fs)),
130
+ ...defined("clockMaxMHz", maxClockMHz(card, dir, fs)),
131
+ ...defined("memClockMHz", pickDpmCurrent(readAttr(fs, `${dir}/device/pp_dpm_mclk`))),
132
+ ...defined("powerW", integrated ? uncoreWatts(prev, next) : hwmonPowerW(hwmon, fs)),
133
+ ...defined("tempC", hwmonTempC(hwmon, fs)),
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Engine busy first: it is the only unprivileged per-engine figure Intel has and
139
+ * it matches what the process rows are built from, so the page and the table
140
+ * agree. amdgpu's own counter is preferred over rc6 where fdinfo said nothing,
141
+ * and rc6 is the last resort — it measures IDLE, so busy is its complement.
142
+ */
143
+ export function utilisation(
144
+ card: GpuCard,
145
+ dir: string,
146
+ engines: ReadonlyMap<string, number> | undefined,
147
+ prev: GpuSample | null,
148
+ next: GpuSample,
149
+ fs: Pick<LinuxFs, "read">,
150
+ ): number {
151
+ const render = (engines?.get("render") ?? 0) + (engines?.get("compute") ?? 0);
152
+ if (render > 0) return round1(Math.min(100, render));
153
+
154
+ const amdBusy = readNumber(fs, `${dir}/device/gpu_busy_percent`);
155
+ if (amdBusy !== undefined) return round1(Math.min(100, Math.max(0, amdBusy)));
156
+
157
+ const rc6 = rc6BusyPercent(prev, next);
158
+ return rc6 ?? 0;
159
+ }
160
+
161
+ /** i915's rc6 counter is cumulative time spent in the deepest idle state. */
162
+ export function rc6BusyPercent(prev: GpuSample | null, next: GpuSample): number | undefined {
163
+ if (!prev || prev.rc6Ms === undefined || next.rc6Ms === undefined) return undefined;
164
+ const dtMs = (next.atSec - prev.atSec) * 1000;
165
+ const idle = next.rc6Ms - prev.rc6Ms;
166
+ if (!(dtMs > 0) || idle < 0) return undefined;
167
+ return round1(Math.min(100, Math.max(0, 100 - idle / dtMs * 100)));
168
+ }
169
+
170
+ /** Intel exposes ONE video engine doing both encode and decode, so the page
171
+ * labels it "Video encode/decode" rather than inventing a second figure. */
172
+ function videoEngines(card: GpuCard, engines: ReadonlyMap<string, number> | undefined): Partial<GpuMetrics> {
173
+ if (!engines) return {};
174
+ const video = engines.get("video");
175
+ const enhance = engines.get("video-enhance");
176
+ if (video === undefined && enhance === undefined) return {};
177
+ if (card.driver === "i915" || card.driver === "xe") {
178
+ return { encodePercent: round1(Math.min(100, (video ?? 0) + (enhance ?? 0))) };
179
+ }
180
+ return {
181
+ ...defined("encodePercent", video === undefined ? undefined : round1(video)),
182
+ ...defined("decodePercent", enhance === undefined ? undefined : round1(enhance)),
183
+ };
184
+ }
185
+
186
+ /**
187
+ * System memory the GPU is using — Mission Center's "Memory Usage" on a card
188
+ * with no VRAM of its own, where it is the page's only memory figure.
189
+ *
190
+ * amdgpu's own GTT counters win where they exist: they are the driver's view of
191
+ * its aperture, and the aperture — not system RAM — is that card's ceiling. An
192
+ * integrated GPU publishes neither, so the figure is summed out of its clients'
193
+ * DRM fdinfo against the machine's RAM, which is what Mission Center shows (it
194
+ * read 2.52 GiB / 62.5 GiB here while this summed 2.54 GiB).
195
+ *
196
+ * Both halves must be present or neither is reported: a used figure over an
197
+ * unknown ceiling is not a reading.
198
+ */
199
+ export function sharedMemory(
200
+ card: GpuCard,
201
+ dir: string,
202
+ usage: DrmUsage,
203
+ fs: Pick<LinuxFs, "read">,
204
+ totalRamBytes: number,
205
+ ): Partial<GpuMetrics> {
206
+ const gttUsed = bytesToMB(readNumber(fs, `${dir}/device/mem_info_gtt_used`));
207
+ const gttTotal = bytesToMB(readNumber(fs, `${dir}/device/mem_info_gtt_total`));
208
+ if (gttUsed !== undefined && gttTotal !== undefined) {
209
+ return { sharedUsedMB: gttUsed, sharedTotalMB: gttTotal };
210
+ }
211
+ const fromClients = usage.sharedByDevice.get(card.pdev);
212
+ const ram = bytesToMB(totalRamBytes > 0 ? totalRamBytes : undefined);
213
+ if (fromClients === undefined || ram === undefined) return {};
214
+ return { sharedUsedMB: Math.round(fromClients), sharedTotalMB: ram };
215
+ }
216
+
217
+ /** amdgpu is the only driver here with dedicated memory counters; an integrated
218
+ * GPU reports a 0 total, which the contract defines as "has none to report". */
219
+ function readVram(dir: string, fs: Pick<LinuxFs, "read">): { usedMB: number; totalMB: number } {
220
+ const used = readNumber(fs, `${dir}/device/mem_info_vram_used`);
221
+ const total = readNumber(fs, `${dir}/device/mem_info_vram_total`);
222
+ return { usedMB: bytesToMB(used) ?? 0, totalMB: bytesToMB(total) ?? 0 };
223
+ }
224
+
225
+ /**
226
+ * i915's REQUESTED clock, not its actual one.
227
+ *
228
+ * `gt_act_freq_mhz` is sampled the instant the file is read, and an idle GPU is
229
+ * parked in RC6 for most of any 2 s tick — so it returns 0 far more often than
230
+ * not and the figure flickers between 0 and 1400 MHz beside a fixed maximum,
231
+ * which reads as a broken number rather than as an idle GPU. `gt_cur_freq_mhz`
232
+ * is the frequency the driver is holding the card at and moves with load.
233
+ */
234
+ function currentClockMHz(card: GpuCard, dir: string, fs: Pick<LinuxFs, "read">): number | undefined {
235
+ return readNumber(fs, `${dir}/gt_cur_freq_mhz`)
236
+ ?? readNumber(fs, `${dir}/gt_act_freq_mhz`)
237
+ ?? pickDpmCurrent(readAttr(fs, `${dir}/device/pp_dpm_sclk`));
238
+ }
239
+
240
+ function maxClockMHz(card: GpuCard, dir: string, fs: Pick<LinuxFs, "read">): number | undefined {
241
+ return readNumber(fs, `${dir}/gt_RP0_freq_mhz`)
242
+ ?? readNumber(fs, `${dir}/gt_max_freq_mhz`)
243
+ ?? pickDpmMax(readAttr(fs, `${dir}/device/pp_dpm_sclk`));
244
+ }
245
+
246
+ /** amdgpu's DPM tables list one state per line and mark the live one with `*`:
247
+ * `1: 500Mhz *`. */
248
+ export function pickDpmCurrent(table: string | undefined): number | undefined {
249
+ if (!table) return undefined;
250
+ for (const line of table.split("\n")) {
251
+ if (!line.includes("*")) continue;
252
+ const m = /(\d+)\s*Mhz/i.exec(line);
253
+ if (m) return Number(m[1]);
254
+ }
255
+ return undefined;
256
+ }
257
+
258
+ export function pickDpmMax(table: string | undefined): number | undefined {
259
+ if (!table) return undefined;
260
+ let max: number | undefined;
261
+ for (const line of table.split("\n")) {
262
+ const m = /(\d+)\s*Mhz/i.exec(line);
263
+ if (m) max = Math.max(max ?? 0, Number(m[1]));
264
+ }
265
+ return max;
266
+ }
267
+
268
+ /** The card's own hwmon directory, e.g. `.../card0/device/hwmon/hwmon4`. */
269
+ function findHwmon(dir: string, fs: Pick<LinuxFs, "list">): string | undefined {
270
+ const base = `${dir}/device/hwmon`;
271
+ const entry = fs.list(base)?.find((n) => n.startsWith("hwmon"));
272
+ return entry ? `${base}/${entry}` : undefined;
273
+ }
274
+
275
+ function hwmonTempC(hwmon: string | undefined, fs: Pick<LinuxFs, "read">): number | undefined {
276
+ if (!hwmon) return undefined;
277
+ const milli = readNumber(fs, `${hwmon}/temp1_input`);
278
+ return milli === undefined ? undefined : Math.round(milli / 100) / 10;
279
+ }
280
+
281
+ /** amdgpu publishes microwatts. */
282
+ function hwmonPowerW(hwmon: string | undefined, fs: Pick<LinuxFs, "read">): number | undefined {
283
+ if (!hwmon) return undefined;
284
+ const uw = readNumber(fs, `${hwmon}/power1_average`) ?? readNumber(fs, `${hwmon}/power1_input`);
285
+ return uw === undefined ? undefined : Math.round(uw / 1000) / 1000;
286
+ }
287
+
288
+ /** RAPL's uncore domain is the integrated GPU's slice of the package budget —
289
+ * the only power figure an Intel iGPU has without root. */
290
+ export function readUncoreEnergy(fs: Pick<LinuxFs, "list" | "read">): number | undefined {
291
+ for (const pkg of fs.list(RAPL_DIR) ?? []) {
292
+ if (!/^intel-rapl:\d+$/.test(pkg)) continue;
293
+ for (const sub of fs.list(`${RAPL_DIR}/${pkg}`) ?? []) {
294
+ if (!sub.startsWith("intel-rapl:")) continue;
295
+ if (readAttr(fs, `${RAPL_DIR}/${pkg}/${sub}/name`) !== "uncore") continue;
296
+ const uj = readNumber(fs, `${RAPL_DIR}/${pkg}/${sub}/energy_uj`);
297
+ if (uj !== undefined) return uj;
298
+ }
299
+ }
300
+ return undefined;
301
+ }
302
+
303
+ function uncoreWatts(prev: GpuSample | null, next: GpuSample): number | undefined {
304
+ if (!prev || prev.uncoreUj === undefined || next.uncoreUj === undefined) return undefined;
305
+ const dt = next.atSec - prev.atSec;
306
+ const dj = next.uncoreUj - prev.uncoreUj;
307
+ if (!(dt > 0) || dj < 0) return undefined;
308
+ return Math.round(dj / dt / 1000) / 1000;
309
+ }
310
+
311
+ // ---------------------------------------------------------------- static
312
+
313
+ export function readGpuInfo(
314
+ card: GpuCard,
315
+ api: { opengl?: string; vulkan?: string; mesa?: string },
316
+ fs: Pick<LinuxFs, "list" | "read">,
317
+ ): GpuInfo {
318
+ const dir = `${DRM_DIR}/${card.card}`;
319
+ const link = pcieLink(dir, fs);
320
+ return {
321
+ id: card.pdev,
322
+ name: gpuName(card, fs),
323
+ ...defined("vendor", card.vendor),
324
+ ...defined("driver", card.driver || undefined),
325
+ ...defined("driverVersion", readAttr(fs, `/sys/module/${card.driver}/version`) ?? api.mesa),
326
+ ...defined("openglVersion", api.opengl),
327
+ ...defined("vulkanVersion", api.vulkan),
328
+ ...link,
329
+ ...(card.driver === "i915" || card.driver === "xe" ? { encodeDecodeShared: true } : {}),
330
+ };
331
+ }
332
+
333
+ /** The PCI database name udev already resolved, e.g. "Alder Lake-S GT1 [UHD
334
+ * Graphics 770]". Falls back to the driver, never to an empty string. */
335
+ export function gpuName(card: GpuCard, fs: Pick<LinuxFs, "read">): string {
336
+ const props = readUdevProperties(`+pci:${card.pdev}`, fs.read);
337
+ const fallback = [card.vendor, card.driver].filter(Boolean).join(" ");
338
+ return udevValue(props, "ID_MODEL_FROM_DATABASE") ?? (fallback || card.pdev);
339
+ }
340
+
341
+ /** Current and maximum link, with the maximum reported only when it DIFFERS —
342
+ * Mission Center hides an equal one, and an integrated GPU has no real link at
343
+ * all (its `current_link_speed` reads "Unknown" and its width 0). */
344
+ export function pcieLink(dir: string, fs: Pick<LinuxFs, "read">): Partial<GpuInfo> {
345
+ const gen = pcieGen(readAttr(fs, `${dir}/device/current_link_speed`));
346
+ const lanes = positive(readNumber(fs, `${dir}/device/current_link_width`));
347
+ const maxGen = pcieGen(readAttr(fs, `${dir}/device/max_link_speed`));
348
+ const maxLanes = positive(readNumber(fs, `${dir}/device/max_link_width`));
349
+ if (gen === undefined && lanes === undefined) return {};
350
+ return {
351
+ ...defined("pcieGen", gen),
352
+ ...defined("pcieLanes", lanes),
353
+ ...(maxGen !== undefined && maxGen !== gen ? { pcieMaxGen: maxGen } : {}),
354
+ ...(maxLanes !== undefined && maxLanes !== lanes ? { pcieMaxLanes: maxLanes } : {}),
355
+ };
356
+ }
357
+
358
+ export function pcieGen(speed: string | undefined): number | undefined {
359
+ const m = /^([\d.]+)\s*GT\/s/i.exec(speed?.trim() ?? "");
360
+ return m ? PCIE_GEN_BY_RATE[m[1]!] : undefined;
361
+ }
362
+
363
+ const basename = (p: string) => p.slice(p.lastIndexOf("/") + 1);
364
+ const round1 = (n: number) => Math.round(n * 10) / 10;
365
+ const positive = (n: number | undefined) => (n !== undefined && n > 0 ? n : undefined);
366
+ const bytesToMB = (n: number | undefined) => (n === undefined ? undefined : Math.round(n / 1024 ** 2));
367
+
368
+ function defined<K extends string, V>(key: K, value: V | undefined): Partial<Record<K, V>> {
369
+ return value === undefined ? {} : ({ [key]: value } as Record<K, V>);
370
+ }
@@ -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
+ }