@hienlh/ppm 0.19.12 → 0.20.1

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 (537) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/CLAUDE.md +8 -1
  3. package/assets/skills/ppm/SKILL.md +1 -1
  4. package/assets/skills/ppm/references/http-api.md +4 -1
  5. package/dist/web/assets/accounts-settings-section-_sG1AqtH.js +1 -0
  6. package/dist/web/assets/{ai-resource-editor-CqL-X2zu.js → ai-resource-editor-BLctP1Pr.js} +1 -1
  7. package/dist/web/assets/ai-settings-section-0hSDp7xx.js +1 -0
  8. package/dist/web/assets/{ai-settings-section-CcqokdzF.js → ai-settings-section-CTEU2uGa.js} +1 -1
  9. package/dist/web/assets/{angular-html-C8nsn2VC.js → angular-html-BtJLNcMw.js} +1 -1
  10. package/dist/web/assets/{angular-ts-Caw9nRCH.js → angular-ts-CQiu5F_W.js} +1 -1
  11. package/dist/web/assets/api-client-QfreGirP.js +1 -0
  12. package/dist/web/assets/api-settings-D18Q4Ocp.js +1 -0
  13. package/dist/web/assets/{api-themes-BoOJPqwM.js → api-themes-sSv-6OLk.js} +1 -1
  14. package/dist/web/assets/{apl-Dhu1LORF.js → apl-CKZvgs-w.js} +1 -1
  15. package/dist/web/assets/{appearance-settings-section-2hZBnpPS.js → appearance-settings-section-BkcoSV0E.js} +1 -1
  16. package/dist/web/assets/architecture-PBZL5I3N-DfR8k8Bc.js +1 -0
  17. package/dist/web/assets/{astro-DwkX9sLZ.js → astro-D0b8ssLD.js} +1 -1
  18. package/dist/web/assets/{audio-preview-CkGZO8Uy.js → audio-preview-B4E5_7TW.js} +1 -1
  19. package/dist/web/assets/{blade-D1fC9Osx.js → blade-DOIpINLw.js} +1 -1
  20. package/dist/web/assets/{bsl-pur-PRip.js → bsl-CJ7_JlQ8.js} +1 -1
  21. package/dist/web/assets/{button-2WCAS8dI.js → button-DX32ffge.js} +1 -1
  22. package/dist/web/assets/{cairo-DDJs9amR.js → cairo-5CpV2T1P.js} +1 -1
  23. package/dist/web/assets/chat-tab-CJi4uYb9.js +15 -0
  24. package/dist/web/assets/{cobol-Bo0usdoM.js → cobol-BreMVrtc.js} +1 -1
  25. package/dist/web/assets/code-editor-C4kKhamq.js +7 -0
  26. package/dist/web/assets/{coffee-DBBsbX1e.js → coffee-D7DoKh5b.js} +1 -1
  27. package/dist/web/assets/{conflict-editor-NqFAJP47.js → conflict-editor-BEULKwHk.js} +1 -1
  28. package/dist/web/assets/{context-menu-C13b1uT3.js → context-menu-CJL8L98s.js} +1 -1
  29. package/dist/web/assets/{cpp-6CTO78A-.js → cpp-BnWeWAoE.js} +1 -1
  30. package/dist/web/assets/{crystal-DAlvpAP7.js → crystal-D2pJbFUn.js} +1 -1
  31. package/dist/web/assets/{csv-preview-Dvriy18_.js → csv-preview-k3eM8nFf.js} +1 -1
  32. package/dist/web/assets/{data-grid-overlay-editor-iRrpAV0e.js → data-grid-overlay-editor-CbBkhhJa.js} +1 -1
  33. package/dist/web/assets/{database-viewer-Bs2RMm5a.js → database-viewer-Cslir1dr.js} +1 -1
  34. package/dist/web/assets/diff-viewer-DzAIvBbn.js +4 -0
  35. package/dist/web/assets/{docx-preview-Bgw838ry.js → docx-preview-Xy7SZ7Vf.js} +1 -1
  36. package/dist/web/assets/{edge-D0tZrpC7.js → edge-aqGMtbY3.js} +1 -1
  37. package/dist/web/assets/{edit-diff-preview-DUgmjNre.js → edit-diff-preview-xdDQvPhs.js} +2 -2
  38. package/dist/web/assets/{elixir-C1E6-o4G.js → elixir-CsYMBLc5.js} +1 -1
  39. package/dist/web/assets/{elm-BKyTfeQX.js → elm-BU8C2gFp.js} +1 -1
  40. package/dist/web/assets/{erb-Cdnr4BMI.js → erb-Z_E49ab8.js} +1 -1
  41. package/dist/web/assets/{erlang-N1237mFq.js → erlang-BRqOjXH9.js} +1 -1
  42. package/dist/web/assets/{esm-ClNnmD9t.js → esm-B-vXxHUd.js} +1 -1
  43. package/dist/web/assets/{explorer-body-DfWunSf9.js → explorer-body-D6H-4cz9.js} +2 -2
  44. package/dist/web/assets/explorer-window-content-C5W_JzCz.js +1 -0
  45. package/dist/web/assets/{extension-manager-section-Zc9yag2c.js → extension-manager-section-DwicI3CA.js} +1 -1
  46. package/dist/web/assets/{extension-webview-B5FayDPz.js → extension-webview-Z3HhC8Pd.js} +1 -1
  47. package/dist/web/assets/{files-settings-section-CxAtO3GL.js → files-settings-section-ByV7bRbB.js} +1 -1
  48. package/dist/web/assets/{fortran-fixed-form-D_f9tVv0.js → fortran-fixed-form-CWbxmDGR.js} +1 -1
  49. package/dist/web/assets/{fsharp-DEJmdhc2.js → fsharp-DKMYbq6I.js} +1 -1
  50. package/dist/web/assets/{gdresource-Dmmrx2gP.js → gdresource-B3yFau4V.js} +1 -1
  51. package/dist/web/assets/{general-settings-section-9DfKrBns.js → general-settings-section-nLksKHur.js} +1 -1
  52. package/dist/web/assets/{git-commit-qwJ867ig.js → git-commit-BW0p3Ekk.js} +1 -1
  53. package/dist/web/assets/{git-log-panel-BsmDZurR.js → git-log-panel-iUXltiTB.js} +1 -1
  54. package/dist/web/assets/{git-rebase-Dkv8Zw86.js → git-rebase-DrvmQCS7.js} +1 -1
  55. package/dist/web/assets/gitGraph-HDMCJU4V-CPd78aaT.js +1 -0
  56. package/dist/web/assets/{glide-data-grid-BuYwXIoS.js → glide-data-grid-C1QB4DHt.js} +6 -6
  57. package/dist/web/assets/{glimmer-js-CcrXn8lQ.js → glimmer-js-BY1XApJJ.js} +1 -1
  58. package/dist/web/assets/{glimmer-ts-CLab3lU7.js → glimmer-ts-DPZtxF8N.js} +1 -1
  59. package/dist/web/assets/{glsl-DXhkG9dU.js → glsl-DZ8G_Hvf.js} +1 -1
  60. package/dist/web/assets/{graphql-Cuqsx8r3.js → graphql-Ck06uCZy.js} +1 -1
  61. package/dist/web/assets/group-chat-tab-ezXKVVpg.js +1 -0
  62. package/dist/web/assets/{hack-C_CBVdBc.js → hack-pLogDwAs.js} +1 -1
  63. package/dist/web/assets/{haml-CBWfzZpv.js → haml-CBqeCQ2A.js} +1 -1
  64. package/dist/web/assets/{handlebars-4e8OR-VI.js → handlebars-B66JmVtp.js} +1 -1
  65. package/dist/web/assets/{html-C5X4urn_.js → html-Dk4wmfwC.js} +1 -1
  66. package/dist/web/assets/{html-derivative-nH-jXfsL.js → html-derivative-CJWqfN51.js} +1 -1
  67. package/dist/web/assets/{http-CQgaG7eu.js → http-CexhotTh.js} +1 -1
  68. package/dist/web/assets/{hurl-B8gidJhu.js → hurl-BScKFk3Z.js} +1 -1
  69. package/dist/web/assets/{hxml-BAK94GX8.js → hxml-DAle7vtu.js} +1 -1
  70. package/dist/web/assets/{image-preview-1kGj4W9R.js → image-preview-fuU4h2nr.js} +1 -1
  71. package/dist/web/assets/index-CJWbFurD.js +52 -0
  72. package/dist/web/assets/index-CoaVwAF6.css +2 -0
  73. package/dist/web/assets/info-3K5VOQVL-BciNds6R.js +1 -0
  74. package/dist/web/assets/{jinja-BuAjvj8M.js → jinja-yHpyD80t.js} +1 -1
  75. package/dist/web/assets/{jira-watcher-section-rJVz40wR.js → jira-watcher-section-DwARwskB.js} +1 -1
  76. package/dist/web/assets/{jison-Dhhs26-L.js → jison-BY6WLJVu.js} +1 -1
  77. package/dist/web/assets/{julia-gZavIyll.js → julia-Dd_iYQDT.js} +1 -1
  78. package/dist/web/assets/{just-DDHrFReO.js → just-BTuUKpxF.js} +1 -1
  79. package/dist/web/assets/keybindings-store-S7YLGd8B.js +1 -0
  80. package/dist/web/assets/{keyboard-shortcuts-section-Dv4e3M8h.js → keyboard-shortcuts-section-BoUDvdDy.js} +1 -1
  81. package/dist/web/assets/{latex-BdGZa8ov.js → latex-Dt3OUlMZ.js} +1 -1
  82. package/dist/web/assets/{liquid-urFfd9Fr.js → liquid-BxPJU2ix.js} +1 -1
  83. package/dist/web/assets/{lua-CcS6tnB5.js → lua-DEGIvn7d.js} +1 -1
  84. package/dist/web/assets/{markdown-renderer-CEloCR06.js → markdown-renderer-DKdfiSIX.js} +3 -3
  85. package/dist/web/assets/markdown-renderer-DYFmMC_8.js +1 -0
  86. package/dist/web/assets/{marko-CSQqk4oG.js → marko-BDxwBJur.js} +1 -1
  87. package/dist/web/assets/{mdc-CYIe80m_.js → mdc-B8aXPTg7.js} +1 -1
  88. package/dist/web/assets/{media-url-DBmHJRft.js → media-url-BMeBaB0q.js} +1 -1
  89. package/dist/web/assets/{mobile-bottom-sheet-B3uDlD79.js → mobile-bottom-sheet-CmySDB6I.js} +1 -1
  90. package/dist/web/assets/mobile-explorer-sheet-CMdqLCV-.js +1 -0
  91. package/dist/web/assets/{nextflow-C-W8vX8I.js → nextflow-DBVZnnT_.js} +1 -1
  92. package/dist/web/assets/{nginx-COGi5you.js → nginx-Crpyd-Do.js} +1 -1
  93. package/dist/web/assets/{nim-CPitYGel.js → nim-BSqTBYDj.js} +1 -1
  94. package/dist/web/assets/notification-store-DpSYFj36.js +1 -0
  95. package/dist/web/assets/{notifications-settings-section-B2GkfB0l.js → notifications-settings-section-CIf3-3xd.js} +1 -1
  96. package/dist/web/assets/{number-overlay-editor-C_eD-Bdr.js → number-overlay-editor-DRjTIcPH.js} +1 -1
  97. package/dist/web/assets/{overview-panel-Cm2HRtEz.js → overview-panel-DiiA3B_Z.js} +1 -1
  98. package/dist/web/assets/packet-RMMSAZCW-ClD3l2Bs.js +1 -0
  99. package/dist/web/assets/{panel-store-BRrnDO_i.js → panel-store-BmdcSBNb.js} +1 -1
  100. package/dist/web/assets/{pdf-preview-Dmz8jkZZ.js → pdf-preview-C-7IdkIK.js} +1 -1
  101. package/dist/web/assets/{perl-B0yW3hbb.js → perl-BU0kbB-K.js} +1 -1
  102. package/dist/web/assets/{php-BNMYN5Jp.js → php-D9n1ikHN.js} +1 -1
  103. package/dist/web/assets/pie-UPGHQEXC-PU6kD0rW.js +1 -0
  104. package/dist/web/assets/{postgres-viewer-BtZbfC3f.js → postgres-viewer-BYXFYlLg.js} +3 -3
  105. package/dist/web/assets/{ppmbot-settings-section-OYfVG5-l.js → ppmbot-settings-section-B9dXvQ6p.js} +1 -1
  106. package/dist/web/assets/{process-table-DRu3NyO_.js → process-table-B6NeTWt8.js} +1 -1
  107. package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-wWZ6bjwq.js} +1 -1
  108. package/dist/web/assets/{proxy-settings-section-xMbrcyr8.js → proxy-settings-section-CUqq7pdU.js} +1 -1
  109. package/dist/web/assets/{pug-CP5fxsxD.js → pug-BOpaczDr.js} +1 -1
  110. package/dist/web/assets/{qml-CHGpQosY.js → qml-B-cQXhrg.js} +1 -1
  111. package/dist/web/assets/{query-audit-section-CC5Mf6qp.js → query-audit-section-C1TYrPIp.js} +1 -1
  112. package/dist/web/assets/radar-KQ55EAFF-4LzjL0Zt.js +1 -0
  113. package/dist/web/assets/{razor-Cfe0AYo4.js → razor-DFB0pNCh.js} +1 -1
  114. package/dist/web/assets/remote-desktop-mobile-sheet-DuKwKe5F.js +2 -0
  115. package/dist/web/assets/{remote-desktop-mobile-view-B4BSHIxU.js → remote-desktop-mobile-view-DxMTtsa6.js} +1 -1
  116. package/dist/web/assets/{remote-desktop-readiness-gate-C0d0PSX4.js → remote-desktop-readiness-gate-BwjfLWqg.js} +1 -1
  117. package/dist/web/assets/{remote-desktop-window-content-DjeCZzqq.js → remote-desktop-window-content-BIl4N5Hn.js} +1 -1
  118. package/dist/web/assets/{rst-DxSSGshb.js → rst-DAXedsgm.js} +1 -1
  119. package/dist/web/assets/{ruby-rbK5SsJa.js → ruby-BCO4polC.js} +1 -1
  120. package/dist/web/assets/{run-in-terminal-DHAvC1AG.js → run-in-terminal-wHKwAnWP.js} +1 -1
  121. package/dist/web/assets/{sas-DDM28fbb.js → sas-DfB5dcFC.js} +1 -1
  122. package/dist/web/assets/schedules-settings-section-C0qOb7GI.js +1 -0
  123. package/dist/web/assets/{scss-TDl-6ro4.js → scss-BknIVgn7.js} +1 -1
  124. package/dist/web/assets/{send-to-chat-BOyUWNKw.js → send-to-chat-BI7-fFpG.js} +1 -1
  125. package/dist/web/assets/{separator-BzGoTlR7.js → separator-W_hJcXPk.js} +1 -1
  126. package/dist/web/assets/{settings-body-CXTQwhbM.js → settings-body-qbrJXX_A.js} +2 -2
  127. package/dist/web/assets/settings-store-CzLnTR8p.js +2 -0
  128. package/dist/web/assets/settings-tab-BKRgYVxf.js +1 -0
  129. package/dist/web/assets/settings-window-content-DWsxJDu-.js +1 -0
  130. package/dist/web/assets/{shaderlab-CCvmDgGS.js → shaderlab-CPu31TpY.js} +1 -1
  131. package/dist/web/assets/{shellsession-BzKS9yKh.js → shellsession-CFX4c_TM.js} +1 -1
  132. package/dist/web/assets/{soy-QfO35nDh.js → soy-rz9hilgm.js} +1 -1
  133. package/dist/web/assets/{sparql-D29RDfp2.js → sparql-Bo8ZDeqE.js} +1 -1
  134. package/dist/web/assets/{sql-query-editor-B-VwfyTf.js → sql-query-editor-CsZOpCW3.js} +1 -1
  135. package/dist/web/assets/{sqlite-viewer-CpUhDEhN.js → sqlite-viewer-DUrnCaP7.js} +1 -1
  136. package/dist/web/assets/{stata-Be6j_SNv.js → stata-B5J-QMyE.js} +1 -1
  137. package/dist/web/assets/{streaming-store-9_9j70zt.js → streaming-store-9XVmAMfV.js} +1 -1
  138. package/dist/web/assets/{surrealql-CykFjgNG.js → surrealql-Caw1ADnG.js} +1 -1
  139. package/dist/web/assets/{svelte-CdBF8z4-.js → svelte-D3oMVcxz.js} +1 -1
  140. package/dist/web/assets/{system-monitor-body-DzWXd1wM.js → system-monitor-body-C5cMpg7I.js} +2 -2
  141. package/dist/web/assets/system-monitor-tab-C66ywbVZ.js +1 -0
  142. package/dist/web/assets/{system-monitor-window-content-DIIP3dff.js → system-monitor-window-content-CENaXYJ-.js} +1 -1
  143. package/dist/web/assets/{tab-host-window-content-CLdcz84K.js → tab-host-window-content-HViABGoS.js} +1 -1
  144. package/dist/web/assets/{tab-store-PZsP5ZwG.js → tab-store-1xYJb0gM.js} +1 -1
  145. package/dist/web/assets/team-member-sheet-CK6eyBRL.js +2 -0
  146. package/dist/web/assets/team-member-window-content-D6wxg37P.js +1 -0
  147. package/dist/web/assets/{templ-BVHE2CNb.js → templ-BAdNyIOL.js} +1 -1
  148. package/dist/web/assets/{terminal-tab-BeKLpaPM.js → terminal-tab-BmyTawmN.js} +2 -2
  149. package/dist/web/assets/{tex-C8XwrbBw.js → tex-IQ8mELOR.js} +1 -1
  150. package/dist/web/assets/tool-cards-y-XJcJHo.js +9 -0
  151. package/dist/web/assets/treemap-KZPCXAKY-DiumLNMg.js +1 -0
  152. package/dist/web/assets/{ts-tags-BIHk5AM0.js → ts-tags-D1DDO2jl.js} +1 -1
  153. package/dist/web/assets/{twig-YcE6Q2Ox.js → twig-C8Fd5RvV.js} +1 -1
  154. package/dist/web/assets/use-accounts-data-BbOb2QNP.js +1 -0
  155. package/dist/web/assets/{use-blob-url-De-ZtcpX.js → use-blob-url-CrwoZJUJ.js} +1 -1
  156. package/dist/web/assets/use-inline-blame-C1G-_NjC.js +8 -0
  157. package/dist/web/assets/{use-monaco-theme-Kj2OGDlm.js → use-monaco-theme-BkM5rMgL.js} +1 -1
  158. package/dist/web/assets/{use-open-team-member-DWhhWdmJ.js → use-open-team-member-CSUU7Z8N.js} +1 -1
  159. package/dist/web/assets/{use-remote-desktop-display-choice-8AkPpM_0.js → use-remote-desktop-display-choice-BT2r1td1.js} +1 -1
  160. package/dist/web/assets/{use-websocket-kR1omKO1.js → use-websocket-BvVTwMjI.js} +1 -1
  161. package/dist/web/assets/{vendor-mermaid-CKKtYuTV.js → vendor-mermaid-CfY1-PkA.js} +1 -1
  162. package/dist/web/assets/{video-preview-BdV7LjbZ.js → video-preview-BEaofjN7.js} +1 -1
  163. package/dist/web/assets/{vue-q7m01V0h.js → vue-EvccXlkP.js} +1 -1
  164. package/dist/web/assets/{vue-html-D1y9feOj.js → vue-html-Dg0xUJCh.js} +1 -1
  165. package/dist/web/assets/{vue-vine-DrIGs03v.js → vue-vine-BdrMg72k.js} +1 -1
  166. package/dist/web/assets/{window-pip-registry-CSLhJfpL.js → window-pip-registry-CrCYYTTf.js} +1 -1
  167. package/dist/web/assets/{window-store-DGKf9rPG.js → window-store-D0HbQ1pM.js} +1 -1
  168. package/dist/web/assets/ws-auth-D111jj32.js +1 -0
  169. package/dist/web/assets/{xml-oyvc1920.js → xml-MmDclzxK.js} +1 -1
  170. package/dist/web/assets/{xsl-BbIGdNPF.js → xsl-DQEFs6Dd.js} +1 -1
  171. package/dist/web/index.html +23 -23
  172. package/dist/web/sw.js +1 -1
  173. package/docs/extension-development-guide.md +47 -0
  174. package/docs/project-roadmap.md +3 -1
  175. package/package.json +2 -2
  176. package/packages/ext-git-graph/package.json +23 -5
  177. package/packages/ext-git-graph/src/blame-parser.test.ts +154 -0
  178. package/packages/ext-git-graph/src/blame-parser.ts +148 -0
  179. package/packages/ext-git-graph/src/blame-view.ts +389 -0
  180. package/packages/ext-git-graph/src/commit-search.test.ts +118 -0
  181. package/packages/ext-git-graph/src/commit-search.ts +102 -0
  182. package/packages/ext-git-graph/src/compare-args.test.ts +111 -0
  183. package/packages/ext-git-graph/src/compare-args.ts +109 -0
  184. package/packages/ext-git-graph/src/compare-view.ts +343 -0
  185. package/packages/ext-git-graph/src/extension-integration.test.ts +6 -3
  186. package/packages/ext-git-graph/src/extension.ts +202 -249
  187. package/packages/ext-git-graph/src/file-history-parser.test.ts +102 -0
  188. package/packages/ext-git-graph/src/file-history-parser.ts +89 -0
  189. package/packages/ext-git-graph/src/file-history-view.ts +386 -0
  190. package/packages/ext-git-graph/src/git-exec.ts +122 -0
  191. package/packages/ext-git-graph/src/git-state.ts +81 -0
  192. package/packages/ext-git-graph/src/panel-nav.ts +65 -0
  193. package/packages/ext-git-graph/src/panel-registry.ts +72 -0
  194. package/packages/ext-git-graph/src/ppm-api.ts +48 -0
  195. package/packages/ext-git-graph/src/rebase-todo.test.ts +196 -0
  196. package/packages/ext-git-graph/src/rebase-todo.ts +119 -0
  197. package/packages/ext-git-graph/src/rebase-view.ts +483 -0
  198. package/packages/ext-git-graph/src/reflog-parser.test.ts +93 -0
  199. package/packages/ext-git-graph/src/reflog-parser.ts +86 -0
  200. package/packages/ext-git-graph/src/reflog-view.ts +375 -0
  201. package/packages/ext-git-graph/src/register-view-command.ts +47 -0
  202. package/packages/ext-git-graph/src/submodule-parser.test.ts +79 -0
  203. package/packages/ext-git-graph/src/submodule-parser.ts +77 -0
  204. package/packages/ext-git-graph/src/types.ts +10 -1
  205. package/packages/ext-git-graph/src/webview-html.ts +438 -3
  206. package/packages/ext-git-graph/src/webview-shell.ts +236 -0
  207. package/src/providers/claude-agent-sdk.ts +7 -0
  208. package/src/providers/codex-app-server/codex-provider.ts +89 -1
  209. package/src/server/index.ts +1 -1
  210. package/src/server/routes/accounts.ts +25 -1
  211. package/src/server/routes/chat.ts +29 -1
  212. package/src/server/routes/codex-accounts.ts +41 -1
  213. package/src/server/routes/git.ts +97 -0
  214. package/src/server/routes/settings.ts +1 -0
  215. package/src/server/routes/teams.ts +14 -3
  216. package/src/services/account-selector.service.ts +125 -20
  217. package/src/services/account.service.ts +176 -54
  218. package/src/services/autostart-generator.ts +12 -1
  219. package/src/services/codex-account.service.ts +87 -13
  220. package/src/services/db.service.ts +58 -3
  221. package/src/services/extension.service.ts +55 -5
  222. package/src/services/git-blame/blame-porcelain.ts +89 -0
  223. package/src/services/git-blame/git-blame.service.ts +90 -0
  224. package/src/services/git-hunks/git-hunks.service.ts +264 -0
  225. package/src/services/git-hunks/unified-diff.ts +359 -0
  226. package/src/services/picked-account-binding.ts +57 -0
  227. package/src/services/team-member-activity/member-transcript-tail.ts +64 -0
  228. package/src/shared/blame.ts +79 -0
  229. package/src/web/components/chat/chat-history-bar.tsx +34 -5
  230. package/src/web/components/chat/chat-tab.tsx +120 -3
  231. package/src/web/components/chat/codex-usage-panel.tsx +132 -14
  232. package/src/web/components/chat/edit-diff-preview.tsx +14 -7
  233. package/src/web/components/chat/team-member-window-content.tsx +8 -39
  234. package/src/web/components/chat/usage-badge.tsx +176 -52
  235. package/src/web/components/editor/code-editor.tsx +33 -1
  236. package/src/web/components/editor/diff-viewer.tsx +83 -8
  237. package/src/web/components/editor/editor-toolbar.tsx +25 -1
  238. package/src/web/components/git/git-status-panel.tsx +46 -0
  239. package/src/web/components/git/hunk-selection.ts +132 -0
  240. package/src/web/components/git/hunk-stage-dialog.tsx +419 -0
  241. package/src/web/components/settings/accounts/account-bucket-row.tsx +19 -3
  242. package/src/web/components/settings/accounts/account-card.tsx +153 -33
  243. package/src/web/components/settings/accounts/account-hint.tsx +33 -0
  244. package/src/web/components/settings/accounts/account-usage-format.ts +26 -0
  245. package/src/web/components/settings/accounts/codex-accounts-section.tsx +24 -9
  246. package/src/web/components/settings/accounts/codex-usage-rows.tsx +2 -2
  247. package/src/web/components/settings/accounts/use-accounts-data.ts +32 -2
  248. package/src/web/components/settings/accounts/use-codex-accounts.ts +24 -2
  249. package/src/web/components/shared/markdown-code-block.tsx +10 -5
  250. package/src/web/hooks/use-chat.ts +27 -0
  251. package/src/web/hooks/use-inline-blame.ts +175 -0
  252. package/src/web/hooks/use-member-transcript-tail.ts +95 -0
  253. package/src/web/lib/api-settings.ts +24 -0
  254. package/src/web/stores/settings-store.ts +12 -0
  255. package/dist/web/assets/accounts-settings-section-Cd1Nh04o.js +0 -1
  256. package/dist/web/assets/ai-settings-section-OaYGT5OF.js +0 -1
  257. package/dist/web/assets/api-client-BogqsFna.js +0 -1
  258. package/dist/web/assets/api-settings-BVfemzZW.js +0 -1
  259. package/dist/web/assets/architecture-PBZL5I3N-BZBxtErN.js +0 -1
  260. package/dist/web/assets/chat-tab-Cc1W2pTu.js +0 -15
  261. package/dist/web/assets/code-editor-gLRyCPWE.js +0 -7
  262. package/dist/web/assets/diff-viewer-ix1Ko0xk.js +0 -4
  263. package/dist/web/assets/explorer-window-content-B7HmteDI.js +0 -1
  264. package/dist/web/assets/gitGraph-HDMCJU4V-BHxJbXfV.js +0 -1
  265. package/dist/web/assets/group-chat-tab-DmZ1vLk-.js +0 -1
  266. package/dist/web/assets/index-N-pnuwFP.css +0 -2
  267. package/dist/web/assets/index-yV5RgBDh.js +0 -52
  268. package/dist/web/assets/info-3K5VOQVL-DI4p3vbH.js +0 -1
  269. package/dist/web/assets/keybindings-store-88_Zkn5F.js +0 -1
  270. package/dist/web/assets/markdown-renderer-UhszMWSn.js +0 -1
  271. package/dist/web/assets/mobile-explorer-sheet-D9cqcU3X.js +0 -1
  272. package/dist/web/assets/notification-store-B6X6RYqQ.js +0 -1
  273. package/dist/web/assets/packet-RMMSAZCW-YNQWTi_-.js +0 -1
  274. package/dist/web/assets/pie-UPGHQEXC-E3Wiw76n.js +0 -1
  275. package/dist/web/assets/radar-KQ55EAFF-BPQJUKON.js +0 -1
  276. package/dist/web/assets/remote-desktop-mobile-sheet-DefltAPl.js +0 -2
  277. package/dist/web/assets/schedules-settings-section-D76Ob6tI.js +0 -1
  278. package/dist/web/assets/settings-store-DWUx3865.js +0 -2
  279. package/dist/web/assets/settings-tab-DToHQXfC.js +0 -1
  280. package/dist/web/assets/settings-window-content-B2rlq-Dv.js +0 -1
  281. package/dist/web/assets/system-monitor-tab-CuDpwAZU.js +0 -1
  282. package/dist/web/assets/team-member-sheet-DLrT_Kxo.js +0 -2
  283. package/dist/web/assets/team-member-window-content-S8cdLcg3.js +0 -1
  284. package/dist/web/assets/tool-cards-CLxrQlUs.js +0 -9
  285. package/dist/web/assets/treemap-KZPCXAKY-C2CvKBQR.js +0 -1
  286. package/dist/web/assets/use-accounts-data-snYPJED_.js +0 -1
  287. package/dist/web/assets/ws-auth-ZSZkAT6p.js +0 -1
  288. /package/dist/web/assets/{abap-Cq9nYQyd.js → abap-D-o-bvtI.js} +0 -0
  289. /package/dist/web/assets/{actionscript-3-DL8rUIy0.js → actionscript-3-D2tIzAm8.js} +0 -0
  290. /package/dist/web/assets/{ada-BY8TLuhY.js → ada-DxatBcxS.js} +0 -0
  291. /package/dist/web/assets/{andromeeda-CyVfHsS0.js → andromeeda-D0LK9YJ3.js} +0 -0
  292. /package/dist/web/assets/{apache-DTQUy73x.js → apache-B7uMPNiW.js} +0 -0
  293. /package/dist/web/assets/{apex-CiSZngrZ.js → apex-B7GiXrAr.js} +0 -0
  294. /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-CEYCBt_0.js} +0 -0
  295. /package/dist/web/assets/{applescript-CzuRDd3h.js → applescript-D_SIFkKe.js} +0 -0
  296. /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-BVXAUYoN.js} +0 -0
  297. /package/dist/web/assets/{ara-CSFV93kR.js → ara-C3iECgdg.js} +0 -0
  298. /package/dist/web/assets/{asciidoc-hOQNnXhz.js → asciidoc-gc8z_MMY.js} +0 -0
  299. /package/dist/web/assets/{asm-DdG53NGR.js → asm-27qeoPOI.js} +0 -0
  300. /package/dist/web/assets/{aurora-x-DJ7ORZG6.js → aurora-x-C5J9fWe1.js} +0 -0
  301. /package/dist/web/assets/{awk-B3FFpX_J.js → awk-B7GBzF92.js} +0 -0
  302. /package/dist/web/assets/{ayu-dark-D_P1CfTe.js → ayu-dark-XlnQhuLL.js} +0 -0
  303. /package/dist/web/assets/{ayu-light-Cz3YLUqH.js → ayu-light-DKLLilkU.js} +0 -0
  304. /package/dist/web/assets/{ayu-mirage-JcMq5tgJ.js → ayu-mirage-Dv-XOprJ.js} +0 -0
  305. /package/dist/web/assets/{ballerina-7Hs4Rfpk.js → ballerina-DACRT-sT.js} +0 -0
  306. /package/dist/web/assets/{bat-COgQdnFA.js → bat-BlQCq2XE.js} +0 -0
  307. /package/dist/web/assets/{beancount-BJ3CutBG.js → beancount-DYDztRI0.js} +0 -0
  308. /package/dist/web/assets/{berry-D7so2Cui.js → berry-BE5YKqvx.js} +0 -0
  309. /package/dist/web/assets/{bibtex-Ct3acHa1.js → bibtex-CAm7mgEm.js} +0 -0
  310. /package/dist/web/assets/{bicep-jXDwCQcm.js → bicep-BKgAbfVa.js} +0 -0
  311. /package/dist/web/assets/{bird2-CjLD2cna.js → bird2-yrBNbW1G.js} +0 -0
  312. /package/dist/web/assets/{c-CdgZojnL.js → c-txZFr4RM.js} +0 -0
  313. /package/dist/web/assets/{c3-DtpZYego.js → c3-D_DrzBUd.js} +0 -0
  314. /package/dist/web/assets/{cadence-CPLkixoh.js → cadence-CUFCEgG9.js} +0 -0
  315. /package/dist/web/assets/{can-open-in-ppm-BntPywsy.js → can-open-in-ppm-_yqa69dV.js} +0 -0
  316. /package/dist/web/assets/{catppuccin-frappe-Xt0_qJa9.js → catppuccin-frappe-Dn32MuhA.js} +0 -0
  317. /package/dist/web/assets/{catppuccin-latte-CJnbLec5.js → catppuccin-latte-Dewo7yDf.js} +0 -0
  318. /package/dist/web/assets/{catppuccin-macchiato-BPHP1wYB.js → catppuccin-macchiato-D8WOWZeo.js} +0 -0
  319. /package/dist/web/assets/{catppuccin-mocha-ssWJodGc.js → catppuccin-mocha-b0md48XM.js} +0 -0
  320. /package/dist/web/assets/{clarity-DB0408F6.js → clarity-Ct3UqLBj.js} +0 -0
  321. /package/dist/web/assets/{clojure-dpRPlAO4.js → clojure-CwP5aPEo.js} +0 -0
  322. /package/dist/web/assets/{cmake-r2VkeX9y.js → cmake-D5RF49e5.js} +0 -0
  323. /package/dist/web/assets/{codeowners-v5nQqHiB.js → codeowners-Cl5897L_.js} +0 -0
  324. /package/dist/web/assets/{codeql-BSSEhKRR.js → codeql-BKh1RCg0.js} +0 -0
  325. /package/dist/web/assets/{common-lisp-BXb2MMMA.js → common-lisp-BhHuD6t0.js} +0 -0
  326. /package/dist/web/assets/{coq-BjBNi73X.js → coq-Dco3qwD8.js} +0 -0
  327. /package/dist/web/assets/{csharp-CdPjZRbV.js → csharp-Dpjw9ZA9.js} +0 -0
  328. /package/dist/web/assets/{css-BuxbQOHL.js → css-CPe4cabH.js} +0 -0
  329. /package/dist/web/assets/{csv-Dj-eoZwH.js → csv-BAcTN8Kt.js} +0 -0
  330. /package/dist/web/assets/{csv-parser-CBbALsFU.js → csv-parser-DbxGKdMG.js} +0 -0
  331. /package/dist/web/assets/{cue-Dz73VksN.js → cue-Cr6y60TA.js} +0 -0
  332. /package/dist/web/assets/{cypher-CkVkDywZ.js → cypher-DWtqXXFP.js} +0 -0
  333. /package/dist/web/assets/{d-oU51Pbpf.js → d-BASiTZk4.js} +0 -0
  334. /package/dist/web/assets/{dark-plus-DcwvjjfO.js → dark-plus-56kmnrcl.js} +0 -0
  335. /package/dist/web/assets/{dart-DGvu8mcX.js → dart-C5mZnjoE.js} +0 -0
  336. /package/dist/web/assets/{data-grid-types-B6kUrF-M.js → data-grid-types-DeY60CLU.js} +0 -0
  337. /package/dist/web/assets/{dax-BmbjKQ90.js → dax-B4I2e7d8.js} +0 -0
  338. /package/dist/web/assets/{desktop-IRBSiFfh.js → desktop-DekGg0BT.js} +0 -0
  339. /package/dist/web/assets/{diff-Bdik2JR8.js → diff-CWnJBd73.js} +0 -0
  340. /package/dist/web/assets/{dist-Bv4zQpS1.js → dist-B0J2eYBz.js} +0 -0
  341. /package/dist/web/assets/{dist-C8YPBIOb.js → dist-BKOM_AoT.js} +0 -0
  342. /package/dist/web/assets/{docker-Bgdl0H8I.js → docker-DevchDxK.js} +0 -0
  343. /package/dist/web/assets/{dotenv-COe5NdnA.js → dotenv-DCvtdaGJ.js} +0 -0
  344. /package/dist/web/assets/{dracula-DjURaSAa.js → dracula-8U6Vkk3l.js} +0 -0
  345. /package/dist/web/assets/{dracula-soft-Do7d0bo-.js → dracula-soft-Bfeew5Wg.js} +0 -0
  346. /package/dist/web/assets/{dream-maker-DYwQvJIj.js → dream-maker-DDTJVuoy.js} +0 -0
  347. /package/dist/web/assets/{emacs-lisp-BXjlHyDW.js → emacs-lisp-CEHJqI4m.js} +0 -0
  348. /package/dist/web/assets/{everforest-dark-h8Q2loiX.js → everforest-dark-CHHLt7cq.js} +0 -0
  349. /package/dist/web/assets/{everforest-light-CdsJiL-9.js → everforest-light-TcTbK1sM.js} +0 -0
  350. /package/dist/web/assets/{fennel-D0CBtOvY.js → fennel-Bh4vsA1A.js} +0 -0
  351. /package/dist/web/assets/{fish-DzayfrMj.js → fish-D0T0fILG.js} +0 -0
  352. /package/dist/web/assets/{fluent-CatjkUfx.js → fluent-CgXDXdyy.js} +0 -0
  353. /package/dist/web/assets/{format-bytes-BulRSULX.js → format-bytes-Bc6SqF7M.js} +0 -0
  354. /package/dist/web/assets/{fortran-free-form-DM04ud0b.js → fortran-free-form-CUg3Xhz4.js} +0 -0
  355. /package/dist/web/assets/{gdscript-BLN5iZwf.js → gdscript-Cx056p_k.js} +0 -0
  356. /package/dist/web/assets/{gdshader-BK-1iZQs.js → gdshader-Dj1qP4I5.js} +0 -0
  357. /package/dist/web/assets/{genie-BGPOFWDN.js → genie-SBt15wn7.js} +0 -0
  358. /package/dist/web/assets/{gherkin-CdVOJ_JY.js → gherkin-bTJiHIrB.js} +0 -0
  359. /package/dist/web/assets/{github-dark-B_xW8TaY.js → github-dark-BOPhyugs.js} +0 -0
  360. /package/dist/web/assets/{github-dark-default-h_iKq7CX.js → github-dark-default-Cktqe6NF.js} +0 -0
  361. /package/dist/web/assets/{github-dark-dimmed-nKDYE62T.js → github-dark-dimmed-CeWfII_B.js} +0 -0
  362. /package/dist/web/assets/{github-dark-high-contrast-l8I771QT.js → github-dark-high-contrast-CTx8LpdV.js} +0 -0
  363. /package/dist/web/assets/{github-light-DNhjkg8Z.js → github-light-DVsO5Un1.js} +0 -0
  364. /package/dist/web/assets/{github-light-default-DQ06uS3g.js → github-light-default-BiyPxicr.js} +0 -0
  365. /package/dist/web/assets/{github-light-high-contrast-qc2sZCVN.js → github-light-high-contrast-BPlnke0a.js} +0 -0
  366. /package/dist/web/assets/{gleam-DPPatkNH.js → gleam-CWSxvD0C.js} +0 -0
  367. /package/dist/web/assets/{gn-BnzGQTy5.js → gn-D7NUVJuv.js} +0 -0
  368. /package/dist/web/assets/{gnuplot-Ct1leoo3.js → gnuplot-c5uXmJKc.js} +0 -0
  369. /package/dist/web/assets/{go-qysCdbrn.js → go-CRsHi4Cw.js} +0 -0
  370. /package/dist/web/assets/{groovy-BkWIdrRQ.js → groovy-DpV0RA5r.js} +0 -0
  371. /package/dist/web/assets/{gruvbox-dark-hard-L6Iiybbl.js → gruvbox-dark-hard-DD9fguUN.js} +0 -0
  372. /package/dist/web/assets/{gruvbox-dark-medium-DbD3yl0b.js → gruvbox-dark-medium-BvcwpYJ-.js} +0 -0
  373. /package/dist/web/assets/{gruvbox-dark-soft-Ban2cHK3.js → gruvbox-dark-soft-48dCxX9M.js} +0 -0
  374. /package/dist/web/assets/{gruvbox-light-hard-CK_SjS3e.js → gruvbox-light-hard-CjJ994Ip.js} +0 -0
  375. /package/dist/web/assets/{gruvbox-light-medium-Bjgvhc2w.js → gruvbox-light-medium-DQJQZ_rV.js} +0 -0
  376. /package/dist/web/assets/{gruvbox-light-soft-EWnyYGUu.js → gruvbox-light-soft-AqsC9mKw.js} +0 -0
  377. /package/dist/web/assets/{haskell-rT1N9nYr.js → haskell-B5V7yvmh.js} +0 -0
  378. /package/dist/web/assets/{haxe-Bbc91axo.js → haxe-LWTKfPKr.js} +0 -0
  379. /package/dist/web/assets/{hcl-D-3qJA4v.js → hcl-QmNZdAtI.js} +0 -0
  380. /package/dist/web/assets/{hjson-DWtewcyQ.js → hjson-CboJsAzK.js} +0 -0
  381. /package/dist/web/assets/{hlsl-C7t6hCrO.js → hlsl-Bd-DO_WL.js} +0 -0
  382. /package/dist/web/assets/{horizon-zJAGUQ3j.js → horizon-CU8Bi_ss.js} +0 -0
  383. /package/dist/web/assets/{horizon-bright-CRNyrJy8.js → horizon-bright-iSQ3_3jP.js} +0 -0
  384. /package/dist/web/assets/{houston-BFKa4W1-.js → houston-CTXv3Puz.js} +0 -0
  385. /package/dist/web/assets/{hy-W1hNjMBM.js → hy-Cd4hTidD.js} +0 -0
  386. /package/dist/web/assets/{image-gallery-BycaVWOu.js → image-gallery-J8h8IY0c.js} +0 -0
  387. /package/dist/web/assets/{imba-CPphaV-V.js → imba-BipCHEY2.js} +0 -0
  388. /package/dist/web/assets/{ini-CayaZ4vW.js → ini-Dd1KkMrB.js} +0 -0
  389. /package/dist/web/assets/{java-BPAE_9Ej.js → java-rGr0OQD8.js} +0 -0
  390. /package/dist/web/assets/{javascript-C5SKIVqe.js → javascript-DHhCH2TN.js} +0 -0
  391. /package/dist/web/assets/{json-C-0EUYmu.js → json-CMMLBCUf.js} +0 -0
  392. /package/dist/web/assets/{json5-BqVetEZL.js → json5-mPOwTOsB.js} +0 -0
  393. /package/dist/web/assets/{jsonc-DnjIzkUB.js → jsonc-Bie0E6D3.js} +0 -0
  394. /package/dist/web/assets/{jsonl-rbV-3EqO.js → jsonl-CpqHmtC1.js} +0 -0
  395. /package/dist/web/assets/{jsonnet-9hvXw4qr.js → jsonnet-D21Ms51e.js} +0 -0
  396. /package/dist/web/assets/{jssm-1ifXyIhu.js → jssm-BXnl1nur.js} +0 -0
  397. /package/dist/web/assets/{jsx-hPumywTY.js → jsx-Chnii_vB.js} +0 -0
  398. /package/dist/web/assets/{kanagawa-dragon-BCPjmoMO.js → kanagawa-dragon-DhxR0UjL.js} +0 -0
  399. /package/dist/web/assets/{kanagawa-lotus-DN2VNjy7.js → kanagawa-lotus-fupRg5wp.js} +0 -0
  400. /package/dist/web/assets/{kanagawa-wave-B8RZEsgc.js → kanagawa-wave-DSWbRS9M.js} +0 -0
  401. /package/dist/web/assets/{kdl-CkvjWQIa.js → kdl-DirGHqUy.js} +0 -0
  402. /package/dist/web/assets/{kotlin-D8cnRi5o.js → kotlin-DkhUVMDC.js} +0 -0
  403. /package/dist/web/assets/{kusto-CmeeBEJG.js → kusto-DU7OpovF.js} +0 -0
  404. /package/dist/web/assets/{laserwave--rtV8Mv9.js → laserwave-iLKb1ZCT.js} +0 -0
  405. /package/dist/web/assets/{lean-CS-Pyi42.js → lean-Cuvh_QAP.js} +0 -0
  406. /package/dist/web/assets/{less-Ddi_7qGm.js → less-B5tQZOdI.js} +0 -0
  407. /package/dist/web/assets/{lib-DIrxSyLb.js → lib-el9rA30D.js} +0 -0
  408. /package/dist/web/assets/{libesm-CYLrAeW3.js → libesm-CdttTIAd.js} +0 -0
  409. /package/dist/web/assets/{light-plus-CrEjpkkX.js → light-plus-DiHU2KL5.js} +0 -0
  410. /package/dist/web/assets/{llvm-Cm7ZXmAO.js → llvm-sCBTKCKN.js} +0 -0
  411. /package/dist/web/assets/{log-lW2sYxtp.js → log-DtyR34Ie.js} +0 -0
  412. /package/dist/web/assets/{logo-B-PiRb53.js → logo-CP91aQyG.js} +0 -0
  413. /package/dist/web/assets/{luau-8St0pC-R.js → luau-nfGo2ulP.js} +0 -0
  414. /package/dist/web/assets/{make-DbNdhH54.js → make-XbDn9GHe.js} +0 -0
  415. /package/dist/web/assets/{markdown-CYocYSGd.js → markdown-BB1_3xLr.js} +0 -0
  416. /package/dist/web/assets/{material-theme-CVBF1Cbi.js → material-theme-CIx_sxAy.js} +0 -0
  417. /package/dist/web/assets/{material-theme-darker-HFrlNZD8.js → material-theme-darker-DsQeueDD.js} +0 -0
  418. /package/dist/web/assets/{material-theme-lighter-BM-uz2ft.js → material-theme-lighter-x_lMi74j.js} +0 -0
  419. /package/dist/web/assets/{material-theme-ocean-PbvUez8m.js → material-theme-ocean-Bkgh25P8.js} +0 -0
  420. /package/dist/web/assets/{material-theme-palenight-B22Ck6qd.js → material-theme-palenight-CoOjdbti.js} +0 -0
  421. /package/dist/web/assets/{matlab-GFFNzucw.js → matlab-D6xZas_8.js} +0 -0
  422. /package/dist/web/assets/{mdx-BVnJzMDx.js → mdx-Cl5_YNoz.js} +0 -0
  423. /package/dist/web/assets/{mermaid-BlCmSTCM.js → mermaid-8Eow4jz_.js} +0 -0
  424. /package/dist/web/assets/{min-dark-B0PwlAfC.js → min-dark-D4DhjgRE.js} +0 -0
  425. /package/dist/web/assets/{min-light-C_H2ABKu.js → min-light-DzAckd0t.js} +0 -0
  426. /package/dist/web/assets/{mipsasm-C3rf2fo6.js → mipsasm-zI_zu0tj.js} +0 -0
  427. /package/dist/web/assets/{mojo-CDRuzM3o.js → mojo-DQnGGPHf.js} +0 -0
  428. /package/dist/web/assets/{monokai-KiebNy_Y.js → monokai-CrFoD2VD.js} +0 -0
  429. /package/dist/web/assets/{moonbit-CdVUHoWl.js → moonbit-DdphIGs6.js} +0 -0
  430. /package/dist/web/assets/{move-Dq9iKZRY.js → move-BUlkn0xW.js} +0 -0
  431. /package/dist/web/assets/{narrat-7y5L5sGM.js → narrat-CbOBdtCn.js} +0 -0
  432. /package/dist/web/assets/{nextflow-groovy-Cwcbkcrx.js → nextflow-groovy-CeVLUTuQ.js} +0 -0
  433. /package/dist/web/assets/{night-owl-TBgLPzj4.js → night-owl-Bm4LhUR1.js} +0 -0
  434. /package/dist/web/assets/{night-owl-light-Dehb9My2.js → night-owl-light-DtcBiMEf.js} +0 -0
  435. /package/dist/web/assets/{nix-BKj-gzoX.js → nix-DrdwQ70g.js} +0 -0
  436. /package/dist/web/assets/{nord-BF_r8PeX.js → nord-B0Head1v.js} +0 -0
  437. /package/dist/web/assets/{nushell-CeAjk9_E.js → nushell-CmUdltKn.js} +0 -0
  438. /package/dist/web/assets/{objective-c-Xl1OMcDp.js → objective-c-BGDR33XN.js} +0 -0
  439. /package/dist/web/assets/{objective-cpp-C4ZgzrRq.js → objective-cpp-Buc1Zhd0.js} +0 -0
  440. /package/dist/web/assets/{ocaml-CGj8IYTq.js → ocaml-xdzl9rRD.js} +0 -0
  441. /package/dist/web/assets/{odin-C4veqvW2.js → odin-DiUFngnd.js} +0 -0
  442. /package/dist/web/assets/{one-dark-pro-gAcWejsV.js → one-dark-pro-BURxmIp3.js} +0 -0
  443. /package/dist/web/assets/{one-light-B7E-UUXy.js → one-light-BMRMInAj.js} +0 -0
  444. /package/dist/web/assets/{openscad-C-wN9BEv.js → openscad-DAgLA5J3.js} +0 -0
  445. /package/dist/web/assets/{pascal-C8WBtxMs.js → pascal-CoDZxcRB.js} +0 -0
  446. /package/dist/web/assets/{pkl-BBfF4G2A.js → pkl-DzKM6BuM.js} +0 -0
  447. /package/dist/web/assets/{plastic-DFOAb89P.js → plastic-vF15LmNS.js} +0 -0
  448. /package/dist/web/assets/{plsql-BS2CEMMV.js → plsql-C1v07teA.js} +0 -0
  449. /package/dist/web/assets/{po-BKJDtPmy.js → po-CLLC8BuA.js} +0 -0
  450. /package/dist/web/assets/{poimandres-Cg3hL13e.js → poimandres-Uq0xysxM.js} +0 -0
  451. /package/dist/web/assets/{polar-B9ebiXUU.js → polar-2-luY1Pc.js} +0 -0
  452. /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DKymYOY-.js} +0 -0
  453. /package/dist/web/assets/{postcss-B5REwNY_.js → postcss-lbFhe0rz.js} +0 -0
  454. /package/dist/web/assets/{powerquery-stc0Gzkf.js → powerquery-RL8O9Wie.js} +0 -0
  455. /package/dist/web/assets/{powershell-jgd0ahJN.js → powershell-upFWhb-b.js} +0 -0
  456. /package/dist/web/assets/{prisma-CWuZjRhI.js → prisma-DBtYaWFk.js} +0 -0
  457. /package/dist/web/assets/{prolog-D5zNFE63.js → prolog-D8jSMykJ.js} +0 -0
  458. /package/dist/web/assets/{proto-B-6RrU7n.js → proto-BM4eadF7.js} +0 -0
  459. /package/dist/web/assets/{puppet-DONP8TDR.js → puppet-BQDQ3Snu.js} +0 -0
  460. /package/dist/web/assets/{purescript-DDJITf-A.js → purescript-Cj2aTX4s.js} +0 -0
  461. /package/dist/web/assets/{python-bmWy9j1-.js → python-BYpqq6u4.js} +0 -0
  462. /package/dist/web/assets/{qmldir-CKDwMt7X.js → qmldir-4uiTcwAu.js} +0 -0
  463. /package/dist/web/assets/{qss-DptQQzBn.js → qss-D5xB-Mqo.js} +0 -0
  464. /package/dist/web/assets/{r-BorKpJ8r.js → r-P6Ujy8rf.js} +0 -0
  465. /package/dist/web/assets/{racket-BaHpYVsZ.js → racket-COKgCFuM.js} +0 -0
  466. /package/dist/web/assets/{raku-DFJIV4k_.js → raku-z14Q9i7K.js} +0 -0
  467. /package/dist/web/assets/{react-X4jUntEf.js → react-D1Yg256j.js} +0 -0
  468. /package/dist/web/assets/{red-70oJGrkb.js → red-ObAvgvQ5.js} +0 -0
  469. /package/dist/web/assets/{reg-C4g235Dv.js → reg-Qv4f0JFA.js} +0 -0
  470. /package/dist/web/assets/{regexp-Cw1UVmti.js → regexp-CEOVGIzy.js} +0 -0
  471. /package/dist/web/assets/{rel-DeLhIz5L.js → rel-cWOyTUwd.js} +0 -0
  472. /package/dist/web/assets/{resolve-theme-b_yGlUFQ.js → resolve-theme-CMp5xxCW.js} +0 -0
  473. /package/dist/web/assets/{riscv-ChnTiBRK.js → riscv-BZDwx-lv.js} +0 -0
  474. /package/dist/web/assets/{ron-C5IWeqDn.js → ron-BpjosPS0.js} +0 -0
  475. /package/dist/web/assets/{rose-pine-DCR0ohYW.js → rose-pine-DhxfJWcm.js} +0 -0
  476. /package/dist/web/assets/{rose-pine-dawn-DvGZs1PR.js → rose-pine-dawn-BbGt9Wy_.js} +0 -0
  477. /package/dist/web/assets/{rose-pine-moon-BWACHjeS.js → rose-pine-moon-dj4Zxgik.js} +0 -0
  478. /package/dist/web/assets/{rosmsg-CQlIVazE.js → rosmsg-EciOaHo2.js} +0 -0
  479. /package/dist/web/assets/{rust-DGGVzWI8.js → rust-C_eH59O_.js} +0 -0
  480. /package/dist/web/assets/{sass-bj1C0rFI.js → sass-Dos3R5lL.js} +0 -0
  481. /package/dist/web/assets/{scala-MU1tLKDi.js → scala-8lVKsTvf.js} +0 -0
  482. /package/dist/web/assets/{scheme-CeGXtv7t.js → scheme-Bd3VdaZj.js} +0 -0
  483. /package/dist/web/assets/{sdbl-D0HLSGKp.js → sdbl-C4Yrjlgh.js} +0 -0
  484. /package/dist/web/assets/{shellscript-DkYiDvh1.js → shellscript-OFZbPKZH.js} +0 -0
  485. /package/dist/web/assets/{slack-dark-YqeHiGWH.js → slack-dark-aL6vlxZs.js} +0 -0
  486. /package/dist/web/assets/{slack-ochin-9Oe0DljJ.js → slack-ochin-BKt1E7sL.js} +0 -0
  487. /package/dist/web/assets/{smalltalk-CaaxedR7.js → smalltalk-D6qVBL1U.js} +0 -0
  488. /package/dist/web/assets/{snazzy-light-C8s2FHNC.js → snazzy-light-BrWerT0t.js} +0 -0
  489. /package/dist/web/assets/{solarized-dark-0ryY6oN3.js → solarized-dark-CzxTJgVQ.js} +0 -0
  490. /package/dist/web/assets/{solarized-light-bltgZeNh.js → solarized-light-BecaDAnG.js} +0 -0
  491. /package/dist/web/assets/{solidity-DOf5HmLZ.js → solidity-C9t7pZ84.js} +0 -0
  492. /package/dist/web/assets/{splunk-D1u4WHDW.js → splunk-CtxfFKtF.js} +0 -0
  493. /package/dist/web/assets/{sql-G8uQZljX.js → sql-7FE_Duz0.js} +0 -0
  494. /package/dist/web/assets/{ssh-config-rbXvly7Q.js → ssh-config-3Fr37ACC.js} +0 -0
  495. /package/dist/web/assets/{stylus-Cii1iADy.js → stylus-BP4mE1sO.js} +0 -0
  496. /package/dist/web/assets/{swift-DpIgonN4.js → swift-0uqTFouD.js} +0 -0
  497. /package/dist/web/assets/{synthwave-84-DjGLzU2b.js → synthwave-84-DQaNM3DI.js} +0 -0
  498. /package/dist/web/assets/{system-verilog-_Z0M0Hs1.js → system-verilog-DUIVrZHZ.js} +0 -0
  499. /package/dist/web/assets/{systemd-BhyDCh9P.js → systemd-CtNo_WIh.js} +0 -0
  500. /package/dist/web/assets/{talonscript-CNchgCRD.js → talonscript-B0HXaCVT.js} +0 -0
  501. /package/dist/web/assets/{tasl-m--W7Pyr.js → tasl-Cb7RAl11.js} +0 -0
  502. /package/dist/web/assets/{tcl-gY-M_2BC.js → tcl-B79aC6l5.js} +0 -0
  503. /package/dist/web/assets/{terraform-CB6CzMwo.js → terraform-75jJDjpW.js} +0 -0
  504. /package/dist/web/assets/{tokyo-night-COh9oRYi.js → tokyo-night-Cz9Uh9qb.js} +0 -0
  505. /package/dist/web/assets/{toml-Bg2bsY-v.js → toml-CD6y9A-z.js} +0 -0
  506. /package/dist/web/assets/{tsv-DceGDiJC.js → tsv-CXJMbBrb.js} +0 -0
  507. /package/dist/web/assets/{tsx-EccDYa7i.js → tsx-BuYN0I2t.js} +0 -0
  508. /package/dist/web/assets/{turtle-C_l2JhvX.js → turtle-DWH-Rjse.js} +0 -0
  509. /package/dist/web/assets/{typescript-C1ytmMcr.js → typescript-BPd-hsrR.js} +0 -0
  510. /package/dist/web/assets/{typespec-De2ztzG5.js → typespec-B0wvVykx.js} +0 -0
  511. /package/dist/web/assets/{typst-DNcS7gSr.js → typst-DWG2c48-.js} +0 -0
  512. /package/dist/web/assets/{use-is-mobile-CYW3FJFE.js → use-is-mobile-D1weanbj.js} +0 -0
  513. /package/dist/web/assets/{use-visual-viewport-D9O48HQt.js → use-visual-viewport-Bfl3Bxxb.js} +0 -0
  514. /package/dist/web/assets/{utils-C9OjCrou.js → utils-IrnQujwR.js} +0 -0
  515. /package/dist/web/assets/{v-DSMUsdMO.js → v-Ch6E4Egd.js} +0 -0
  516. /package/dist/web/assets/{vala-BEazLpkO.js → vala-CGVKwbPx.js} +0 -0
  517. /package/dist/web/assets/{vb-CV9pEJzW.js → vb-BIEbSxyT.js} +0 -0
  518. /package/dist/web/assets/{vendor-xterm-cuGQmUjN.js → vendor-xterm-YQlzIWv2.js} +0 -0
  519. /package/dist/web/assets/{verilog-DhXkLVMk.js → verilog-A-3pYPad.js} +0 -0
  520. /package/dist/web/assets/{vesper-n4IltYq-.js → vesper-CK1naZAV.js} +0 -0
  521. /package/dist/web/assets/{vhdl-CePjFbvs.js → vhdl-CVawmgMg.js} +0 -0
  522. /package/dist/web/assets/{viml-ChndFsm5.js → viml-CiWjaqBY.js} +0 -0
  523. /package/dist/web/assets/{vitesse-black-BytVdvhC.js → vitesse-black-PYXW4-BO.js} +0 -0
  524. /package/dist/web/assets/{vitesse-dark-83Wech0N.js → vitesse-dark-BU5s4AuG.js} +0 -0
  525. /package/dist/web/assets/{vitesse-light-Xg4wbeMO.js → vitesse-light-DgkIT-N7.js} +0 -0
  526. /package/dist/web/assets/{vyper-eW1NC3FG.js → vyper-D8cRDPp3.js} +0 -0
  527. /package/dist/web/assets/{wasm-BucxKwBZ.js → wasm-C02wlRbP.js} +0 -0
  528. /package/dist/web/assets/{wasm-Bhucqu1z.js → wasm-ClFJgzmT.js} +0 -0
  529. /package/dist/web/assets/{wenyan-BJFBuXuH.js → wenyan-B8plxIr8.js} +0 -0
  530. /package/dist/web/assets/{wgsl-6z4FUyMK.js → wgsl-CVujtm3V.js} +0 -0
  531. /package/dist/web/assets/{wikitext-BoDU6teJ.js → wikitext-CmcG9B69.js} +0 -0
  532. /package/dist/web/assets/{wit-DerMPpgt.js → wit--gl2Jium.js} +0 -0
  533. /package/dist/web/assets/{wolfram-D78v0Ap-.js → wolfram-6T_1D-NS.js} +0 -0
  534. /package/dist/web/assets/{x-YDu4m2Iq.js → x-CxoBA0po.js} +0 -0
  535. /package/dist/web/assets/{yaml-C4TIgIw4.js → yaml-CIVV9uA1.js} +0 -0
  536. /package/dist/web/assets/{zenscript-BydW-dLC.js → zenscript-CoT4yNdA.js} +0 -0
  537. /package/dist/web/assets/{zig-b9lTqL5E.js → zig-BWs2Qo3G.js} +0 -0
@@ -6,7 +6,7 @@ import { getPpmDir } from "./ppm-dir.ts";
6
6
  import { assertProdDbAccessAllowed } from "./prod-db-guard.ts";
7
7
  import { backupDbSync } from "./db-backup/db-backup-sync.ts";
8
8
  import { CODEX_DEFAULT_MODEL } from "../types/config.ts";
9
- export const CURRENT_SCHEMA_VERSION = 45;
9
+ export const CURRENT_SCHEMA_VERSION = 47;
10
10
 
11
11
  let db: Database | null = null;
12
12
  let dbProfile: string | null = null;
@@ -1078,6 +1078,49 @@ export function runMigrations(database: Database): void {
1078
1078
 
1079
1079
  database.exec(`PRAGMA user_version = 45;`);
1080
1080
  }
1081
+
1082
+ // Nesting this inside the v45 block, as it briefly was, meant it only ran for a database
1083
+ // coming from 44 or below — every install already sitting at 45 skipped it silently and
1084
+ // then read columns that were never added.
1085
+ if (current < 46) {
1086
+ // Anthropic kills the whole refresh-token family a fixed time after the
1087
+ // original grant — rotation does not extend it — so an account that has been
1088
+ // refreshing happily every 7h still dies on a schedule nothing local can see.
1089
+ // Recording when the grant was issued, and what the server says about the
1090
+ // refresh token's own lifetime, is what lets PPM warn before that happens.
1091
+ for (const col of [
1092
+ "granted_at INTEGER",
1093
+ "refresh_expires_at INTEGER",
1094
+ "reauth_required INTEGER NOT NULL DEFAULT 0",
1095
+ "last_refresh_attempt_at INTEGER",
1096
+ ]) {
1097
+ try {
1098
+ database.exec(`ALTER TABLE accounts ADD COLUMN ${col}`);
1099
+ } catch {
1100
+ // Column may already exist
1101
+ }
1102
+ }
1103
+ // Existing rows are deliberately left with granted_at NULL. created_at is the
1104
+ // row's creation, not the grant: an account re-authenticated last week still
1105
+ // carries a created_at from months ago, and backfilling it would render every
1106
+ // healthy account as long past due. NULL reads as "unknown until next sign-in",
1107
+ // which is the truth; reauth_required still catches the death either way.
1108
+ database.exec(`PRAGMA user_version = 46;`);
1109
+ }
1110
+
1111
+ if (current < 47) {
1112
+ // Codex accounts had no way to be switched off. Claude accounts have had `status` since
1113
+ // the beginning, so the chat panel could offer a toggle there and not here — the same
1114
+ // control meaning two different amounts of work depending on which provider you were
1115
+ // looking at.
1116
+ //
1117
+ // Two states, not Claude's three: nothing puts a Codex account into cooldown, and adding
1118
+ // a state no code can currently produce would just be a branch that never runs.
1119
+ //
1120
+ // Defaults to enabled so an upgrade changes nothing about which accounts are in play.
1121
+ try { database.exec(`ALTER TABLE codex_accounts ADD COLUMN status TEXT NOT NULL DEFAULT 'active'`); } catch { /* exists */ }
1122
+ database.exec(`PRAGMA user_version = 47;`);
1123
+ }
1081
1124
  }
1082
1125
 
1083
1126
  // ---------------------------------------------------------------------------
@@ -1967,6 +2010,13 @@ export interface AccountRow {
1967
2010
  last_used_at: number | null;
1968
2011
  profile_json: string | null;
1969
2012
  created_at: number;
2013
+ /** When the current OAuth grant was issued. NULL for rows predating this column. */
2014
+ granted_at: number | null;
2015
+ /** Server-reported refresh-token expiry, when the token endpoint provides one. */
2016
+ refresh_expires_at: number | null;
2017
+ /** Set once the OAuth server rejects the refresh token; only a fresh sign-in clears it. */
2018
+ reauth_required: number;
2019
+ last_refresh_attempt_at: number | null;
1970
2020
  }
1971
2021
 
1972
2022
  export function getAccounts(): AccountRow[] {
@@ -1979,12 +2029,13 @@ export function getAccountById(id: string): AccountRow | null {
1979
2029
 
1980
2030
  export function insertAccount(row: Omit<AccountRow, "created_at">): void {
1981
2031
  getDb().query(
1982
- `INSERT INTO accounts (id, label, email, access_token, refresh_token, expires_at, status, cooldown_until, priority, total_requests, last_used_at, profile_json)
1983
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
2032
+ `INSERT INTO accounts (id, label, email, access_token, refresh_token, expires_at, status, cooldown_until, priority, total_requests, last_used_at, profile_json, granted_at, refresh_expires_at, reauth_required, last_refresh_attempt_at)
2033
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1984
2034
  ).run(
1985
2035
  row.id, row.label, row.email, row.access_token, row.refresh_token,
1986
2036
  row.expires_at, row.status, row.cooldown_until, row.priority,
1987
2037
  row.total_requests, row.last_used_at, row.profile_json,
2038
+ row.granted_at, row.refresh_expires_at, row.reauth_required, row.last_refresh_attempt_at,
1988
2039
  );
1989
2040
  }
1990
2041
 
@@ -2002,6 +2053,10 @@ export function updateAccount(id: string, updates: Partial<Omit<AccountRow, "id"
2002
2053
  if (updates.total_requests !== undefined) { sets.push("total_requests = ?"); vals.push(updates.total_requests); }
2003
2054
  if (updates.last_used_at !== undefined) { sets.push("last_used_at = ?"); vals.push(updates.last_used_at); }
2004
2055
  if (updates.profile_json !== undefined) { sets.push("profile_json = ?"); vals.push(updates.profile_json); }
2056
+ if (updates.granted_at !== undefined) { sets.push("granted_at = ?"); vals.push(updates.granted_at); }
2057
+ if (updates.refresh_expires_at !== undefined) { sets.push("refresh_expires_at = ?"); vals.push(updates.refresh_expires_at); }
2058
+ if (updates.reauth_required !== undefined) { sets.push("reauth_required = ?"); vals.push(updates.reauth_required); }
2059
+ if (updates.last_refresh_attempt_at !== undefined) { sets.push("last_refresh_attempt_at = ?"); vals.push(updates.last_refresh_attempt_at); }
2005
2060
  if (sets.length === 0) return;
2006
2061
  vals.push(id);
2007
2062
  getDb().query(`UPDATE accounts SET ${sets.join(", ")} WHERE id = ?`).run(...(vals as SQLQueryBindings[]));
@@ -1,4 +1,4 @@
1
- import { resolve } from "node:path";
1
+ import { resolve, dirname } from "node:path";
2
2
  import { existsSync } from "node:fs";
3
3
  import type { ExtensionManifest, ExtensionInfo, RpcMessage } from "../types/extension.ts";
4
4
  import { getExtensions, getExtensionById, insertExtension, updateExtension, deleteExtension, deleteExtensionStorage, getExtensionStorage, setExtensionStorageValue } from "./db.service.ts";
@@ -8,6 +8,58 @@ import { parseManifest, discoverManifests, discoverBundledManifests } from "./ex
8
8
  import { installExtension, removeExtension, devLinkExtension, ensureExtensionsDir } from "./extension-installer.ts";
9
9
  import { registerVscodeCompatHandlers } from "./extension-rpc-handlers.ts";
10
10
  import { getPpmDir } from "./ppm-dir.ts";
11
+ import { isCompiledBinary } from "./autostart-generator.ts";
12
+
13
+ /**
14
+ * Where the bundled `packages/ext-*` extensions live on disk.
15
+ *
16
+ * They are loaded as source by the extension host, so they ship *beside* the install rather
17
+ * than inside it. Walking up from `import.meta.dir` finds them when PPM runs from source, but
18
+ * a compiled binary reports `/$bunfs/root` — its embedded filesystem — and the same walk lands
19
+ * on `/packages`, an absolute path at the filesystem root that cannot exist. Every bundled
20
+ * extension then vanished with no error and no log line: the panel simply said none were
21
+ * installed. Fall back to the directory holding the executable (`<install>/dist/ppm` →
22
+ * `<install>/packages`).
23
+ */
24
+ export function bundledExtensionsDir(
25
+ moduleDir: string = import.meta.dir,
26
+ execPath: string = process.execPath,
27
+ ): string {
28
+ const fromSource = resolve(moduleDir, "../../packages");
29
+ if (existsSync(fromSource)) return fromSource;
30
+ return resolve(dirname(execPath), "../packages");
31
+ }
32
+
33
+ /**
34
+ * How to name the extension host worker so Bun will actually load it.
35
+ *
36
+ * Neither form works in both modes, which is why this is a branch and not a constant:
37
+ *
38
+ * - `new URL("./extension-host-worker.ts", import.meta.url)` is right from source — it is
39
+ * anchored to this module, so it survives the service's `WorkingDirectory=~/.ppm`. From a
40
+ * compiled binary it yields `file:///$bunfs/root/...`, the embedded filesystem, and Bun
41
+ * refuses to resolve a worker entry point there: the Worker fails to build and dies at once.
42
+ * `ensureWorker` only null-checks, so every later activation posts to the corpse and reports
43
+ * `Worker has been terminated`.
44
+ * - A relative specifier is right when compiled — but it resolves against the directory of the
45
+ * build's *main entry point* (`src/index.ts`), not against this module, so it reads
46
+ * `./services/...` rather than `./`. Verified at cwd `/`, `~/.ppm` and the binary's own
47
+ * directory; every other spelling tried (`./extension-host-worker.ts`, `src/services/...`,
48
+ * the `/$bunfs/` path) is refused. From source the same specifier resolves against cwd, so
49
+ * it breaks the moment cwd is not this directory, which for the service it never is.
50
+ *
51
+ * The worker also has to be a second entry point of `bun build --compile` (see the `build`
52
+ * script) — a worker referenced only through `new URL` is not pulled into the binary.
53
+ *
54
+ * Compiling this way also keeps the worker's own imports (`@ppm/vscode-compat`) inside the
55
+ * bundle, where loading it off disk would have to resolve them through `node_modules` — which
56
+ * a worker spawned from a compiled binary does not do.
57
+ */
58
+ export function extensionHostWorkerSpec(compiled: boolean = isCompiledBinary()): string {
59
+ return compiled
60
+ ? "./services/extension-host-worker.ts"
61
+ : new URL("./extension-host-worker.ts", import.meta.url).href;
62
+ }
11
63
 
12
64
  class ExtensionService {
13
65
  private worker: Worker | null = null;
@@ -24,8 +76,7 @@ class ExtensionService {
24
76
  private ensureWorker(): { worker: Worker; rpc: RpcChannel } {
25
77
  if (this.worker && this.rpc) return { worker: this.worker, rpc: this.rpc };
26
78
 
27
- const workerPath = new URL("./extension-host-worker.ts", import.meta.url).href;
28
- this.worker = new Worker(workerPath, { type: "module" });
79
+ this.worker = new Worker(extensionHostWorkerSpec(), { type: "module" });
29
80
  this.rpc = new RpcChannel((msg) => this.worker!.postMessage(msg));
30
81
 
31
82
  this.rpc.onRequest("storage:set", async (params) => {
@@ -73,8 +124,7 @@ class ExtensionService {
73
124
  ensureExtensionsDir(resolve(getPpmDir(), "extensions"));
74
125
 
75
126
  // Discover bundled extensions from packages/ext-*
76
- const bundledDir = resolve(import.meta.dir, "../../packages");
77
- const bundled = await discoverBundledManifests(bundledDir);
127
+ const bundled = await discoverBundledManifests(bundledExtensionsDir());
78
128
  for (const m of bundled) {
79
129
  this.extensionPaths.set(m.id, m._dir);
80
130
  this.bundledIds.add(m.id);
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Parser for `git blame --porcelain`.
3
+ *
4
+ * The porcelain format emits a commit's full header only the *first* time that
5
+ * commit is seen; every later line from it carries just the sha, an optional
6
+ * `previous`/`filename`, and the content. So headers accumulate into a map and
7
+ * lines reference it by sha. `--line-porcelain` would repeat every header and
8
+ * roughly triple the output for no extra information.
9
+ */
10
+ import { isUncommittedHash, type BlameCommitInfo, type BlameLine, type BlameResult } from "../../shared/blame.ts";
11
+
12
+ function stripAngles(value: string): string {
13
+ return value.replace(/^</, "").replace(/>$/, "");
14
+ }
15
+
16
+ export function parseBlamePorcelain(stdout: string): BlameResult {
17
+ const commits: Record<string, BlameCommitInfo> = {};
18
+ const lines: BlameLine[] = [];
19
+
20
+ // Split on \n only: a content line may legitimately end with \r (a CRLF
21
+ // file), and that \r belongs to the file, not to the porcelain framing.
22
+ let current: BlameCommitInfo | null = null;
23
+ let finalLine = 0;
24
+ let origLine = 0;
25
+
26
+ for (const raw of stdout.split("\n")) {
27
+ if (raw === "") continue;
28
+
29
+ // A content line is the only kind that starts with a tab, and it closes the
30
+ // record — the next header line begins a new one.
31
+ if (raw.startsWith("\t")) {
32
+ if (current) lines.push({ hash: current.hash, finalLine, origLine });
33
+ current = null;
34
+ continue;
35
+ }
36
+
37
+ const header = /^([0-9a-f]{40})\s+(\d+)\s+(\d+)(?:\s+(\d+))?$/.exec(raw);
38
+ if (header) {
39
+ const hash = header[1]!;
40
+ origLine = Number(header[2]);
41
+ finalLine = Number(header[3]);
42
+ let info = commits[hash];
43
+ if (!info) {
44
+ info = {
45
+ hash,
46
+ author: isUncommittedHash(hash) ? "You" : "",
47
+ authorMail: "",
48
+ authorTime: 0,
49
+ summary: isUncommittedHash(hash) ? "Uncommitted changes" : "",
50
+ };
51
+ commits[hash] = info;
52
+ }
53
+ current = info;
54
+ continue;
55
+ }
56
+
57
+ if (!current) continue;
58
+
59
+ const sep = raw.indexOf(" ");
60
+ const key = sep === -1 ? raw : raw.slice(0, sep);
61
+ const value = sep === -1 ? "" : raw.slice(sep + 1);
62
+
63
+ switch (key) {
64
+ case "author":
65
+ if (value) current.author = value;
66
+ break;
67
+ case "author-mail":
68
+ current.authorMail = stripAngles(value);
69
+ break;
70
+ case "author-time":
71
+ current.authorTime = Number(value) || 0;
72
+ break;
73
+ case "summary":
74
+ if (value) current.summary = value;
75
+ break;
76
+ case "filename":
77
+ current.filename = value;
78
+ break;
79
+ case "previous":
80
+ // "previous <sha> <path>" — only the sha is useful here.
81
+ current.previous = value.split(" ")[0];
82
+ break;
83
+ default:
84
+ break;
85
+ }
86
+ }
87
+
88
+ return { lines, commits };
89
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * `git blame` for the editor's inline annotation.
3
+ *
4
+ * One call blames the whole file — running it per line would be a process per
5
+ * cursor move. The result is small (a line table plus one record per distinct
6
+ * commit) and the frontend indexes into it as the cursor moves.
7
+ */
8
+ import simpleGit from "simple-git";
9
+ import { parseBlamePorcelain } from "./blame-porcelain.ts";
10
+ import type { BlameResult } from "../../shared/blame.ts";
11
+
12
+ /**
13
+ * Reject a path that is absolute, escapes the repository, or could be read as
14
+ * an option. Callers pass paths straight from the browser.
15
+ *
16
+ * Deliberately no `node:path` normalisation: on Windows that would rewrite the
17
+ * separators git expects, and the check is about the string git receives.
18
+ */
19
+ export function assertSafeFilePath(filePath: string): string {
20
+ if (!filePath || filePath.startsWith("-") || filePath.startsWith("/") || /[\x00-\x1f\x7f]/.test(filePath)) {
21
+ throw new Error(`Invalid file path: "${filePath}"`);
22
+ }
23
+ let depth = 0;
24
+ for (const segment of filePath.split(/[\\/]+/)) {
25
+ if (!segment || segment === ".") continue;
26
+ if (segment === "..") {
27
+ depth--;
28
+ if (depth < 0) throw new Error(`File path escapes the repository: "${filePath}"`);
29
+ continue;
30
+ }
31
+ depth++;
32
+ }
33
+ return filePath;
34
+ }
35
+
36
+ /**
37
+ * Reject a revision that could be read as an option or as a range.
38
+ *
39
+ * A revision reaches git as its own argv word, so shell metacharacters are not
40
+ * the hazard here — the two that actually change what the command means are a
41
+ * leading dash (`--reverse`) and the range syntaxes (`a..b`).
42
+ *
43
+ * `~` and `^` stay allowed on purpose: `HEAD~1` and `main^` are exactly the
44
+ * revisions the diff viewer asks for. The rest of the character class is what
45
+ * `git check-ref-format` forbids in a refname anyway.
46
+ */
47
+ export function assertSafeRev(rev: string): string {
48
+ if (
49
+ !rev ||
50
+ rev.startsWith("-") ||
51
+ rev.includes("..") ||
52
+ /[\x00-\x1f\x7f:?*[\]\\ ]/.test(rev)
53
+ ) {
54
+ throw new Error(`Invalid revision: "${rev}"`);
55
+ }
56
+ return rev;
57
+ }
58
+
59
+ class GitBlameService {
60
+ /**
61
+ * Blame every line of `filePath`, in the working tree or as it stood at `rev`.
62
+ *
63
+ * `rev` is what makes blame work inside the diff viewer: each side of a diff
64
+ * is the file at a different revision, so blaming the working tree for the
65
+ * left-hand pane would name the wrong commits.
66
+ *
67
+ * Returns null when there is nothing to blame — the file is untracked, or did
68
+ * not exist at that revision. Both are ordinary states the UI shows as "no
69
+ * annotation" rather than an error.
70
+ */
71
+ async blameFile(projectPath: string, filePath: string, rev?: string): Promise<BlameResult | null> {
72
+ assertSafeFilePath(filePath);
73
+ const git = simpleGit(projectPath);
74
+ // `-w` ignores whitespace-only changes, so a reformat does not claim every
75
+ // line. `--` separates the path from the options for good.
76
+ const args = ["blame", "--porcelain", "-w"];
77
+ if (rev) args.push(assertSafeRev(rev));
78
+ args.push("--", filePath);
79
+
80
+ try {
81
+ return parseBlamePorcelain(await git.raw(args));
82
+ } catch (e) {
83
+ const message = e instanceof Error ? e.message : String(e);
84
+ if (/no such path|does not have|is outside repository|no such file/i.test(message)) return null;
85
+ throw e;
86
+ }
87
+ }
88
+ }
89
+
90
+ export const gitBlameService = new GitBlameService();
@@ -0,0 +1,264 @@
1
+ /**
2
+ * Staging, unstaging and discarding at hunk / line granularity.
3
+ *
4
+ * Each operation is a patch fed to `git apply` on stdin. `simple-git` has no
5
+ * stdin door, so these spawn git directly.
6
+ */
7
+ import { spawn } from "node:child_process";
8
+ import { mkdtemp, rm } from "node:fs/promises";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import type { DiffHunk } from "./unified-diff.ts";
12
+ import {
13
+ buildPatch, hunkFingerprint, parseUnifiedDiff, resolveRequestedHunks, selectionFromRequest,
14
+ } from "./unified-diff.ts";
15
+
16
+ export type HunkScope = "worktree" | "index";
17
+
18
+ export interface HunkRequest {
19
+ /** Where the hunk sat in the list the client was given. A hint, not the key. */
20
+ hunk: number;
21
+ /**
22
+ * `hunkFingerprint` of the hunk the user actually ticked. This is what the
23
+ * selection is resolved by, so a file that moved on cannot be silently staged
24
+ * from a different diff than the one on screen.
25
+ */
26
+ id: string;
27
+ /** Line indexes within the hunk; omitted means the whole hunk. */
28
+ lines?: number[];
29
+ }
30
+
31
+ /** A hunk as the browser receives it: UTF-8 text plus its content address. */
32
+ export type ListedHunk = DiffHunk & { id: string };
33
+
34
+ export interface FileHunks {
35
+ filePath: string;
36
+ scope: HunkScope;
37
+ hunks: ListedHunk[];
38
+ binary: boolean;
39
+ }
40
+
41
+ interface GitResult {
42
+ stdout: string;
43
+ stderr: string;
44
+ exitCode: number;
45
+ }
46
+
47
+ /**
48
+ * Spawn git and hand back its output byte-for-byte.
49
+ *
50
+ * Two things here are load-bearing and neither is visible in review.
51
+ *
52
+ * Chunks are collected as `Buffer` and decoded **once**. Decoding each chunk as
53
+ * it arrives (`stdout += c.toString()`) splits any multi-byte sequence that
54
+ * straddles a chunk boundary into two invalid halves, which become U+FFFD — and
55
+ * because a patch built from that text still *applies*, the corruption lands in
56
+ * the index with no error anywhere. Measured on a 2 MB all-Japanese diff: 43
57
+ * mangled lines.
58
+ *
59
+ * The decode is **latin1**, not utf8, so it is a lossless byte round-trip: the
60
+ * patch written back to `git apply` is the bytes git gave us, whatever the
61
+ * file's encoding actually is (a Latin-1 file could not be hunk-staged at all
62
+ * before). Everything the parser looks at — `@@`, `+`, `-`, `\` — is ASCII and
63
+ * reads the same either way. Only text leaving for the browser is turned back
64
+ * into UTF-8, by `toDisplay` below.
65
+ */
66
+ function runGit(
67
+ projectPath: string,
68
+ args: string[],
69
+ options: { stdin?: Buffer; env?: Record<string, string> } = {},
70
+ ): Promise<GitResult> {
71
+ return new Promise((resolve, reject) => {
72
+ const child = spawn("git", args, {
73
+ cwd: projectPath,
74
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", ...options.env },
75
+ });
76
+
77
+ const stdout: Buffer[] = [];
78
+ const stderr: Buffer[] = [];
79
+ child.stdout.on("data", (c: Buffer) => { stdout.push(c); });
80
+ child.stderr.on("data", (c: Buffer) => { stderr.push(c); });
81
+ child.on("error", reject);
82
+ child.on("close", (code) => resolve({
83
+ stdout: Buffer.concat(stdout).toString("latin1"),
84
+ // stderr only ever becomes a message for a human, so it is decoded the
85
+ // way a human expects to read it.
86
+ stderr: Buffer.concat(stderr).toString("utf8"),
87
+ exitCode: code ?? 0,
88
+ }));
89
+
90
+ if (options.stdin !== undefined) {
91
+ child.stdin.end(options.stdin);
92
+ } else {
93
+ child.stdin.end();
94
+ }
95
+ });
96
+ }
97
+
98
+ /** Undo `runGit`'s latin1 decode, for text on its way to the browser. */
99
+ function toDisplay(text: string): string {
100
+ return Buffer.from(text, "latin1").toString("utf8");
101
+ }
102
+
103
+ /**
104
+ * Reject a path that is absolute, escapes the repository, or could be read as
105
+ * an option. Callers pass paths straight from the browser.
106
+ */
107
+ function assertSafeFilePath(filePath: string): string {
108
+ if (!filePath || filePath.startsWith("-") || filePath.startsWith("/") || /[\x00-\x1f\x7f]/.test(filePath)) {
109
+ throw new Error(`Invalid file path: "${filePath}"`);
110
+ }
111
+ let depth = 0;
112
+ for (const segment of filePath.split(/[\\/]+/)) {
113
+ if (!segment || segment === ".") continue;
114
+ if (segment === "..") {
115
+ depth--;
116
+ if (depth < 0) throw new Error(`File path escapes the repository: "${filePath}"`);
117
+ continue;
118
+ }
119
+ depth++;
120
+ }
121
+ return filePath;
122
+ }
123
+
124
+ class GitHunksService {
125
+ /** Is the file untracked? Hunk staging needs it in the index first. */
126
+ private async isUntracked(projectPath: string, filePath: string): Promise<boolean> {
127
+ const res = await runGit(projectPath, ["ls-files", "--error-unmatch", "--", filePath]);
128
+ return res.exitCode !== 0;
129
+ }
130
+
131
+ /**
132
+ * An index git may write to, for a file it has never seen.
133
+ *
134
+ * A `git diff` needs something to compare against, which for an untracked
135
+ * file is what `git add -N` provides. That is a write, and every caller here
136
+ * is describing a file rather than changing one: doing it to the user's index
137
+ * turns `?? file` into `A file` behind their back, on nothing more than
138
+ * opening a dialog, and the exit code went unread so a failure showed up as
139
+ * "no changes" instead. It goes into a throwaway index instead — git reads the
140
+ * same config and the same `.gitattributes`, normalises line endings the same
141
+ * way and produces the same bytes, while the real index is never opened for
142
+ * writing. `git apply --cached` needs no entry of its own to add a new file,
143
+ * so staging still lands in the real index.
144
+ */
145
+ private async scratchIndexFor(projectPath: string, filePath: string): Promise<string | null> {
146
+ if (!await this.isUntracked(projectPath, filePath)) return null;
147
+ const dir = await mkdtemp(join(tmpdir(), "ppm-hunk-index-"));
148
+ const env = { GIT_INDEX_FILE: join(dir, "index") };
149
+ const added = await runGit(projectPath, ["add", "-N", "--", filePath], { env });
150
+ if (added.exitCode !== 0) {
151
+ await rm(dir, { recursive: true, force: true });
152
+ throw new Error(
153
+ added.stderr.trim() || `git could not read "${filePath}" (exit ${added.exitCode}).`,
154
+ );
155
+ }
156
+ return dir;
157
+ }
158
+
159
+ /**
160
+ * `--no-renames` is not a formatting preference.
161
+ *
162
+ * With rename detection on, a staged `git mv` makes `git diff --cached` emit a
163
+ * *rename patch*, and `git apply --cached --reverse` on one of those rewrites
164
+ * index entries instead of lines: unstaging a single hunk left `D a.txt`
165
+ * staged and `b.txt` untracked, one commit away from losing the file's
166
+ * history. Forcing the content form keeps every patch here a patch about
167
+ * lines in one path.
168
+ */
169
+ private async rawDiff(projectPath: string, filePath: string, scope: HunkScope): Promise<string> {
170
+ const scratch = scope === "worktree" ? await this.scratchIndexFor(projectPath, filePath) : null;
171
+ try {
172
+ const args = ["diff", "--no-color", "--no-ext-diff", "--no-renames"];
173
+ if (scope === "index") args.push("--cached");
174
+ args.push("--", filePath);
175
+ const res = await runGit(projectPath, args, {
176
+ env: scratch ? { GIT_INDEX_FILE: join(scratch, "index") } : undefined,
177
+ });
178
+ if (res.exitCode !== 0) {
179
+ throw new Error(res.stderr.trim() || `git diff exited with ${res.exitCode}`);
180
+ }
181
+ // Belt and braces: a future flag or a repository config that reintroduces
182
+ // rename headers must fail loudly rather than reach `git apply --reverse`.
183
+ if (/^rename (from|to) /m.test(res.stdout)) {
184
+ throw new Error("This file was renamed — stage or unstage it whole rather than by hunk.");
185
+ }
186
+ return res.stdout;
187
+ } finally {
188
+ if (scratch) await rm(scratch, { recursive: true, force: true });
189
+ }
190
+ }
191
+
192
+ /**
193
+ * The hunks the UI shows, each carrying the content address it must send back.
194
+ *
195
+ * Fingerprints are taken from the byte-exact text and the *text* is then
196
+ * converted for display, so what the client echoes identifies the bytes
197
+ * rather than a lossy rendering of them.
198
+ */
199
+ async getHunks(projectPath: string, filePath: string, scope: HunkScope): Promise<FileHunks> {
200
+ assertSafeFilePath(filePath);
201
+ const parsed = parseUnifiedDiff(await this.rawDiff(projectPath, filePath, scope));
202
+ const hunks: ListedHunk[] = parsed.hunks.map((hunk) => ({
203
+ ...hunk,
204
+ id: hunkFingerprint(hunk),
205
+ heading: toDisplay(hunk.heading),
206
+ lines: hunk.lines.map((line) => ({ ...line, text: toDisplay(line.text) })),
207
+ }));
208
+ return { filePath, scope, hunks, binary: parsed.binary };
209
+ }
210
+
211
+ private async applySelection(
212
+ projectPath: string,
213
+ filePath: string,
214
+ scope: HunkScope,
215
+ requested: HunkRequest[],
216
+ apply: { cached: boolean; reverse: boolean },
217
+ ): Promise<void> {
218
+ assertSafeFilePath(filePath);
219
+ if (requested.length === 0) throw new Error("No hunks were selected.");
220
+
221
+ const parsed = parseUnifiedDiff(await this.rawDiff(projectPath, filePath, scope));
222
+ if (parsed.binary) throw new Error("A binary file cannot be staged by hunk — stage the whole file.");
223
+ if (parsed.hunks.length === 0) throw new Error("This file has no changes to apply.");
224
+
225
+ // The client's indexes describe the diff it was *shown*; this diff was read
226
+ // again just now. Resolving by content is what makes the two the same list
227
+ // — or refuses. `git apply` cannot be the guard here, because the patch is
228
+ // built from this fresh parse and therefore always applies cleanly.
229
+ const selection = selectionFromRequest(parsed, resolveRequestedHunks(parsed, requested));
230
+ const patch = buildPatch(parsed, selection, { reverse: apply.reverse });
231
+ if (!patch) throw new Error("The selection contains no actual change.");
232
+
233
+ // Default three lines of context, no `--unidiff-zero`.
234
+ const args = ["apply"];
235
+ if (apply.cached) args.push("--cached");
236
+ if (apply.reverse) args.push("--reverse");
237
+ args.push("-");
238
+
239
+ const res = await runGit(projectPath, args, { stdin: Buffer.from(patch, "latin1") });
240
+ if (res.exitCode !== 0) {
241
+ throw new Error(
242
+ res.stderr.trim() ||
243
+ `git could not apply the patch (exit ${res.exitCode}).`,
244
+ );
245
+ }
246
+ }
247
+
248
+ /** Move the selected worktree changes into the index. */
249
+ async stage(projectPath: string, filePath: string, hunks: HunkRequest[]): Promise<void> {
250
+ await this.applySelection(projectPath, filePath, "worktree", hunks, { cached: true, reverse: false });
251
+ }
252
+
253
+ /** Take the selected staged changes back out of the index. */
254
+ async unstage(projectPath: string, filePath: string, hunks: HunkRequest[]): Promise<void> {
255
+ await this.applySelection(projectPath, filePath, "index", hunks, { cached: true, reverse: true });
256
+ }
257
+
258
+ /** Throw away the selected worktree changes. Not recoverable. */
259
+ async discard(projectPath: string, filePath: string, hunks: HunkRequest[]): Promise<void> {
260
+ await this.applySelection(projectPath, filePath, "worktree", hunks, { cached: false, reverse: true });
261
+ }
262
+ }
263
+
264
+ export const gitHunksService = new GitHunksService();