@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.
- package/CHANGELOG.md +36 -0
- package/CLAUDE.md +8 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +4 -1
- package/dist/web/assets/accounts-settings-section-_sG1AqtH.js +1 -0
- package/dist/web/assets/{ai-resource-editor-CqL-X2zu.js → ai-resource-editor-BLctP1Pr.js} +1 -1
- package/dist/web/assets/ai-settings-section-0hSDp7xx.js +1 -0
- package/dist/web/assets/{ai-settings-section-CcqokdzF.js → ai-settings-section-CTEU2uGa.js} +1 -1
- package/dist/web/assets/{angular-html-C8nsn2VC.js → angular-html-BtJLNcMw.js} +1 -1
- package/dist/web/assets/{angular-ts-Caw9nRCH.js → angular-ts-CQiu5F_W.js} +1 -1
- package/dist/web/assets/api-client-QfreGirP.js +1 -0
- package/dist/web/assets/api-settings-D18Q4Ocp.js +1 -0
- package/dist/web/assets/{api-themes-BoOJPqwM.js → api-themes-sSv-6OLk.js} +1 -1
- package/dist/web/assets/{apl-Dhu1LORF.js → apl-CKZvgs-w.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-2hZBnpPS.js → appearance-settings-section-BkcoSV0E.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-DfR8k8Bc.js +1 -0
- package/dist/web/assets/{astro-DwkX9sLZ.js → astro-D0b8ssLD.js} +1 -1
- package/dist/web/assets/{audio-preview-CkGZO8Uy.js → audio-preview-B4E5_7TW.js} +1 -1
- package/dist/web/assets/{blade-D1fC9Osx.js → blade-DOIpINLw.js} +1 -1
- package/dist/web/assets/{bsl-pur-PRip.js → bsl-CJ7_JlQ8.js} +1 -1
- package/dist/web/assets/{button-2WCAS8dI.js → button-DX32ffge.js} +1 -1
- package/dist/web/assets/{cairo-DDJs9amR.js → cairo-5CpV2T1P.js} +1 -1
- package/dist/web/assets/chat-tab-CJi4uYb9.js +15 -0
- package/dist/web/assets/{cobol-Bo0usdoM.js → cobol-BreMVrtc.js} +1 -1
- package/dist/web/assets/code-editor-C4kKhamq.js +7 -0
- package/dist/web/assets/{coffee-DBBsbX1e.js → coffee-D7DoKh5b.js} +1 -1
- package/dist/web/assets/{conflict-editor-NqFAJP47.js → conflict-editor-BEULKwHk.js} +1 -1
- package/dist/web/assets/{context-menu-C13b1uT3.js → context-menu-CJL8L98s.js} +1 -1
- package/dist/web/assets/{cpp-6CTO78A-.js → cpp-BnWeWAoE.js} +1 -1
- package/dist/web/assets/{crystal-DAlvpAP7.js → crystal-D2pJbFUn.js} +1 -1
- package/dist/web/assets/{csv-preview-Dvriy18_.js → csv-preview-k3eM8nFf.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-iRrpAV0e.js → data-grid-overlay-editor-CbBkhhJa.js} +1 -1
- package/dist/web/assets/{database-viewer-Bs2RMm5a.js → database-viewer-Cslir1dr.js} +1 -1
- package/dist/web/assets/diff-viewer-DzAIvBbn.js +4 -0
- package/dist/web/assets/{docx-preview-Bgw838ry.js → docx-preview-Xy7SZ7Vf.js} +1 -1
- package/dist/web/assets/{edge-D0tZrpC7.js → edge-aqGMtbY3.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-DUgmjNre.js → edit-diff-preview-xdDQvPhs.js} +2 -2
- package/dist/web/assets/{elixir-C1E6-o4G.js → elixir-CsYMBLc5.js} +1 -1
- package/dist/web/assets/{elm-BKyTfeQX.js → elm-BU8C2gFp.js} +1 -1
- package/dist/web/assets/{erb-Cdnr4BMI.js → erb-Z_E49ab8.js} +1 -1
- package/dist/web/assets/{erlang-N1237mFq.js → erlang-BRqOjXH9.js} +1 -1
- package/dist/web/assets/{esm-ClNnmD9t.js → esm-B-vXxHUd.js} +1 -1
- package/dist/web/assets/{explorer-body-DfWunSf9.js → explorer-body-D6H-4cz9.js} +2 -2
- package/dist/web/assets/explorer-window-content-C5W_JzCz.js +1 -0
- package/dist/web/assets/{extension-manager-section-Zc9yag2c.js → extension-manager-section-DwicI3CA.js} +1 -1
- package/dist/web/assets/{extension-webview-B5FayDPz.js → extension-webview-Z3HhC8Pd.js} +1 -1
- package/dist/web/assets/{files-settings-section-CxAtO3GL.js → files-settings-section-ByV7bRbB.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-D_f9tVv0.js → fortran-fixed-form-CWbxmDGR.js} +1 -1
- package/dist/web/assets/{fsharp-DEJmdhc2.js → fsharp-DKMYbq6I.js} +1 -1
- package/dist/web/assets/{gdresource-Dmmrx2gP.js → gdresource-B3yFau4V.js} +1 -1
- package/dist/web/assets/{general-settings-section-9DfKrBns.js → general-settings-section-nLksKHur.js} +1 -1
- package/dist/web/assets/{git-commit-qwJ867ig.js → git-commit-BW0p3Ekk.js} +1 -1
- package/dist/web/assets/{git-log-panel-BsmDZurR.js → git-log-panel-iUXltiTB.js} +1 -1
- package/dist/web/assets/{git-rebase-Dkv8Zw86.js → git-rebase-DrvmQCS7.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CPd78aaT.js +1 -0
- package/dist/web/assets/{glide-data-grid-BuYwXIoS.js → glide-data-grid-C1QB4DHt.js} +6 -6
- package/dist/web/assets/{glimmer-js-CcrXn8lQ.js → glimmer-js-BY1XApJJ.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CLab3lU7.js → glimmer-ts-DPZtxF8N.js} +1 -1
- package/dist/web/assets/{glsl-DXhkG9dU.js → glsl-DZ8G_Hvf.js} +1 -1
- package/dist/web/assets/{graphql-Cuqsx8r3.js → graphql-Ck06uCZy.js} +1 -1
- package/dist/web/assets/group-chat-tab-ezXKVVpg.js +1 -0
- package/dist/web/assets/{hack-C_CBVdBc.js → hack-pLogDwAs.js} +1 -1
- package/dist/web/assets/{haml-CBWfzZpv.js → haml-CBqeCQ2A.js} +1 -1
- package/dist/web/assets/{handlebars-4e8OR-VI.js → handlebars-B66JmVtp.js} +1 -1
- package/dist/web/assets/{html-C5X4urn_.js → html-Dk4wmfwC.js} +1 -1
- package/dist/web/assets/{html-derivative-nH-jXfsL.js → html-derivative-CJWqfN51.js} +1 -1
- package/dist/web/assets/{http-CQgaG7eu.js → http-CexhotTh.js} +1 -1
- package/dist/web/assets/{hurl-B8gidJhu.js → hurl-BScKFk3Z.js} +1 -1
- package/dist/web/assets/{hxml-BAK94GX8.js → hxml-DAle7vtu.js} +1 -1
- package/dist/web/assets/{image-preview-1kGj4W9R.js → image-preview-fuU4h2nr.js} +1 -1
- package/dist/web/assets/index-CJWbFurD.js +52 -0
- package/dist/web/assets/index-CoaVwAF6.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BciNds6R.js +1 -0
- package/dist/web/assets/{jinja-BuAjvj8M.js → jinja-yHpyD80t.js} +1 -1
- package/dist/web/assets/{jira-watcher-section-rJVz40wR.js → jira-watcher-section-DwARwskB.js} +1 -1
- package/dist/web/assets/{jison-Dhhs26-L.js → jison-BY6WLJVu.js} +1 -1
- package/dist/web/assets/{julia-gZavIyll.js → julia-Dd_iYQDT.js} +1 -1
- package/dist/web/assets/{just-DDHrFReO.js → just-BTuUKpxF.js} +1 -1
- package/dist/web/assets/keybindings-store-S7YLGd8B.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-Dv4e3M8h.js → keyboard-shortcuts-section-BoUDvdDy.js} +1 -1
- package/dist/web/assets/{latex-BdGZa8ov.js → latex-Dt3OUlMZ.js} +1 -1
- package/dist/web/assets/{liquid-urFfd9Fr.js → liquid-BxPJU2ix.js} +1 -1
- package/dist/web/assets/{lua-CcS6tnB5.js → lua-DEGIvn7d.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CEloCR06.js → markdown-renderer-DKdfiSIX.js} +3 -3
- package/dist/web/assets/markdown-renderer-DYFmMC_8.js +1 -0
- package/dist/web/assets/{marko-CSQqk4oG.js → marko-BDxwBJur.js} +1 -1
- package/dist/web/assets/{mdc-CYIe80m_.js → mdc-B8aXPTg7.js} +1 -1
- package/dist/web/assets/{media-url-DBmHJRft.js → media-url-BMeBaB0q.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-B3uDlD79.js → mobile-bottom-sheet-CmySDB6I.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CMdqLCV-.js +1 -0
- package/dist/web/assets/{nextflow-C-W8vX8I.js → nextflow-DBVZnnT_.js} +1 -1
- package/dist/web/assets/{nginx-COGi5you.js → nginx-Crpyd-Do.js} +1 -1
- package/dist/web/assets/{nim-CPitYGel.js → nim-BSqTBYDj.js} +1 -1
- package/dist/web/assets/notification-store-DpSYFj36.js +1 -0
- package/dist/web/assets/{notifications-settings-section-B2GkfB0l.js → notifications-settings-section-CIf3-3xd.js} +1 -1
- package/dist/web/assets/{number-overlay-editor-C_eD-Bdr.js → number-overlay-editor-DRjTIcPH.js} +1 -1
- package/dist/web/assets/{overview-panel-Cm2HRtEz.js → overview-panel-DiiA3B_Z.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-ClD3l2Bs.js +1 -0
- package/dist/web/assets/{panel-store-BRrnDO_i.js → panel-store-BmdcSBNb.js} +1 -1
- package/dist/web/assets/{pdf-preview-Dmz8jkZZ.js → pdf-preview-C-7IdkIK.js} +1 -1
- package/dist/web/assets/{perl-B0yW3hbb.js → perl-BU0kbB-K.js} +1 -1
- package/dist/web/assets/{php-BNMYN5Jp.js → php-D9n1ikHN.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-PU6kD0rW.js +1 -0
- package/dist/web/assets/{postgres-viewer-BtZbfC3f.js → postgres-viewer-BYXFYlLg.js} +3 -3
- package/dist/web/assets/{ppmbot-settings-section-OYfVG5-l.js → ppmbot-settings-section-B9dXvQ6p.js} +1 -1
- package/dist/web/assets/{process-table-DRu3NyO_.js → process-table-B6NeTWt8.js} +1 -1
- package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-wWZ6bjwq.js} +1 -1
- package/dist/web/assets/{proxy-settings-section-xMbrcyr8.js → proxy-settings-section-CUqq7pdU.js} +1 -1
- package/dist/web/assets/{pug-CP5fxsxD.js → pug-BOpaczDr.js} +1 -1
- package/dist/web/assets/{qml-CHGpQosY.js → qml-B-cQXhrg.js} +1 -1
- package/dist/web/assets/{query-audit-section-CC5Mf6qp.js → query-audit-section-C1TYrPIp.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-4LzjL0Zt.js +1 -0
- package/dist/web/assets/{razor-Cfe0AYo4.js → razor-DFB0pNCh.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-DuKwKe5F.js +2 -0
- package/dist/web/assets/{remote-desktop-mobile-view-B4BSHIxU.js → remote-desktop-mobile-view-DxMTtsa6.js} +1 -1
- package/dist/web/assets/{remote-desktop-readiness-gate-C0d0PSX4.js → remote-desktop-readiness-gate-BwjfLWqg.js} +1 -1
- package/dist/web/assets/{remote-desktop-window-content-DjeCZzqq.js → remote-desktop-window-content-BIl4N5Hn.js} +1 -1
- package/dist/web/assets/{rst-DxSSGshb.js → rst-DAXedsgm.js} +1 -1
- package/dist/web/assets/{ruby-rbK5SsJa.js → ruby-BCO4polC.js} +1 -1
- package/dist/web/assets/{run-in-terminal-DHAvC1AG.js → run-in-terminal-wHKwAnWP.js} +1 -1
- package/dist/web/assets/{sas-DDM28fbb.js → sas-DfB5dcFC.js} +1 -1
- package/dist/web/assets/schedules-settings-section-C0qOb7GI.js +1 -0
- package/dist/web/assets/{scss-TDl-6ro4.js → scss-BknIVgn7.js} +1 -1
- package/dist/web/assets/{send-to-chat-BOyUWNKw.js → send-to-chat-BI7-fFpG.js} +1 -1
- package/dist/web/assets/{separator-BzGoTlR7.js → separator-W_hJcXPk.js} +1 -1
- package/dist/web/assets/{settings-body-CXTQwhbM.js → settings-body-qbrJXX_A.js} +2 -2
- package/dist/web/assets/settings-store-CzLnTR8p.js +2 -0
- package/dist/web/assets/settings-tab-BKRgYVxf.js +1 -0
- package/dist/web/assets/settings-window-content-DWsxJDu-.js +1 -0
- package/dist/web/assets/{shaderlab-CCvmDgGS.js → shaderlab-CPu31TpY.js} +1 -1
- package/dist/web/assets/{shellsession-BzKS9yKh.js → shellsession-CFX4c_TM.js} +1 -1
- package/dist/web/assets/{soy-QfO35nDh.js → soy-rz9hilgm.js} +1 -1
- package/dist/web/assets/{sparql-D29RDfp2.js → sparql-Bo8ZDeqE.js} +1 -1
- package/dist/web/assets/{sql-query-editor-B-VwfyTf.js → sql-query-editor-CsZOpCW3.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-CpUhDEhN.js → sqlite-viewer-DUrnCaP7.js} +1 -1
- package/dist/web/assets/{stata-Be6j_SNv.js → stata-B5J-QMyE.js} +1 -1
- package/dist/web/assets/{streaming-store-9_9j70zt.js → streaming-store-9XVmAMfV.js} +1 -1
- package/dist/web/assets/{surrealql-CykFjgNG.js → surrealql-Caw1ADnG.js} +1 -1
- package/dist/web/assets/{svelte-CdBF8z4-.js → svelte-D3oMVcxz.js} +1 -1
- package/dist/web/assets/{system-monitor-body-DzWXd1wM.js → system-monitor-body-C5cMpg7I.js} +2 -2
- package/dist/web/assets/system-monitor-tab-C66ywbVZ.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-DIIP3dff.js → system-monitor-window-content-CENaXYJ-.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-CLdcz84K.js → tab-host-window-content-HViABGoS.js} +1 -1
- package/dist/web/assets/{tab-store-PZsP5ZwG.js → tab-store-1xYJb0gM.js} +1 -1
- package/dist/web/assets/team-member-sheet-CK6eyBRL.js +2 -0
- package/dist/web/assets/team-member-window-content-D6wxg37P.js +1 -0
- package/dist/web/assets/{templ-BVHE2CNb.js → templ-BAdNyIOL.js} +1 -1
- package/dist/web/assets/{terminal-tab-BeKLpaPM.js → terminal-tab-BmyTawmN.js} +2 -2
- package/dist/web/assets/{tex-C8XwrbBw.js → tex-IQ8mELOR.js} +1 -1
- package/dist/web/assets/tool-cards-y-XJcJHo.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-DiumLNMg.js +1 -0
- package/dist/web/assets/{ts-tags-BIHk5AM0.js → ts-tags-D1DDO2jl.js} +1 -1
- package/dist/web/assets/{twig-YcE6Q2Ox.js → twig-C8Fd5RvV.js} +1 -1
- package/dist/web/assets/use-accounts-data-BbOb2QNP.js +1 -0
- package/dist/web/assets/{use-blob-url-De-ZtcpX.js → use-blob-url-CrwoZJUJ.js} +1 -1
- package/dist/web/assets/use-inline-blame-C1G-_NjC.js +8 -0
- package/dist/web/assets/{use-monaco-theme-Kj2OGDlm.js → use-monaco-theme-BkM5rMgL.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DWhhWdmJ.js → use-open-team-member-CSUU7Z8N.js} +1 -1
- package/dist/web/assets/{use-remote-desktop-display-choice-8AkPpM_0.js → use-remote-desktop-display-choice-BT2r1td1.js} +1 -1
- package/dist/web/assets/{use-websocket-kR1omKO1.js → use-websocket-BvVTwMjI.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-CKKtYuTV.js → vendor-mermaid-CfY1-PkA.js} +1 -1
- package/dist/web/assets/{video-preview-BdV7LjbZ.js → video-preview-BEaofjN7.js} +1 -1
- package/dist/web/assets/{vue-q7m01V0h.js → vue-EvccXlkP.js} +1 -1
- package/dist/web/assets/{vue-html-D1y9feOj.js → vue-html-Dg0xUJCh.js} +1 -1
- package/dist/web/assets/{vue-vine-DrIGs03v.js → vue-vine-BdrMg72k.js} +1 -1
- package/dist/web/assets/{window-pip-registry-CSLhJfpL.js → window-pip-registry-CrCYYTTf.js} +1 -1
- package/dist/web/assets/{window-store-DGKf9rPG.js → window-store-D0HbQ1pM.js} +1 -1
- package/dist/web/assets/ws-auth-D111jj32.js +1 -0
- package/dist/web/assets/{xml-oyvc1920.js → xml-MmDclzxK.js} +1 -1
- package/dist/web/assets/{xsl-BbIGdNPF.js → xsl-DQEFs6Dd.js} +1 -1
- package/dist/web/index.html +23 -23
- package/dist/web/sw.js +1 -1
- package/docs/extension-development-guide.md +47 -0
- package/docs/project-roadmap.md +3 -1
- package/package.json +2 -2
- package/packages/ext-git-graph/package.json +23 -5
- package/packages/ext-git-graph/src/blame-parser.test.ts +154 -0
- package/packages/ext-git-graph/src/blame-parser.ts +148 -0
- package/packages/ext-git-graph/src/blame-view.ts +389 -0
- package/packages/ext-git-graph/src/commit-search.test.ts +118 -0
- package/packages/ext-git-graph/src/commit-search.ts +102 -0
- package/packages/ext-git-graph/src/compare-args.test.ts +111 -0
- package/packages/ext-git-graph/src/compare-args.ts +109 -0
- package/packages/ext-git-graph/src/compare-view.ts +343 -0
- package/packages/ext-git-graph/src/extension-integration.test.ts +6 -3
- package/packages/ext-git-graph/src/extension.ts +202 -249
- package/packages/ext-git-graph/src/file-history-parser.test.ts +102 -0
- package/packages/ext-git-graph/src/file-history-parser.ts +89 -0
- package/packages/ext-git-graph/src/file-history-view.ts +386 -0
- package/packages/ext-git-graph/src/git-exec.ts +122 -0
- package/packages/ext-git-graph/src/git-state.ts +81 -0
- package/packages/ext-git-graph/src/panel-nav.ts +65 -0
- package/packages/ext-git-graph/src/panel-registry.ts +72 -0
- package/packages/ext-git-graph/src/ppm-api.ts +48 -0
- package/packages/ext-git-graph/src/rebase-todo.test.ts +196 -0
- package/packages/ext-git-graph/src/rebase-todo.ts +119 -0
- package/packages/ext-git-graph/src/rebase-view.ts +483 -0
- package/packages/ext-git-graph/src/reflog-parser.test.ts +93 -0
- package/packages/ext-git-graph/src/reflog-parser.ts +86 -0
- package/packages/ext-git-graph/src/reflog-view.ts +375 -0
- package/packages/ext-git-graph/src/register-view-command.ts +47 -0
- package/packages/ext-git-graph/src/submodule-parser.test.ts +79 -0
- package/packages/ext-git-graph/src/submodule-parser.ts +77 -0
- package/packages/ext-git-graph/src/types.ts +10 -1
- package/packages/ext-git-graph/src/webview-html.ts +438 -3
- package/packages/ext-git-graph/src/webview-shell.ts +236 -0
- package/src/providers/claude-agent-sdk.ts +7 -0
- package/src/providers/codex-app-server/codex-provider.ts +89 -1
- package/src/server/index.ts +1 -1
- package/src/server/routes/accounts.ts +25 -1
- package/src/server/routes/chat.ts +29 -1
- package/src/server/routes/codex-accounts.ts +41 -1
- package/src/server/routes/git.ts +97 -0
- package/src/server/routes/settings.ts +1 -0
- package/src/server/routes/teams.ts +14 -3
- package/src/services/account-selector.service.ts +125 -20
- package/src/services/account.service.ts +176 -54
- package/src/services/autostart-generator.ts +12 -1
- package/src/services/codex-account.service.ts +87 -13
- package/src/services/db.service.ts +58 -3
- package/src/services/extension.service.ts +55 -5
- package/src/services/git-blame/blame-porcelain.ts +89 -0
- package/src/services/git-blame/git-blame.service.ts +90 -0
- package/src/services/git-hunks/git-hunks.service.ts +264 -0
- package/src/services/git-hunks/unified-diff.ts +359 -0
- package/src/services/picked-account-binding.ts +57 -0
- package/src/services/team-member-activity/member-transcript-tail.ts +64 -0
- package/src/shared/blame.ts +79 -0
- package/src/web/components/chat/chat-history-bar.tsx +34 -5
- package/src/web/components/chat/chat-tab.tsx +120 -3
- package/src/web/components/chat/codex-usage-panel.tsx +132 -14
- package/src/web/components/chat/edit-diff-preview.tsx +14 -7
- package/src/web/components/chat/team-member-window-content.tsx +8 -39
- package/src/web/components/chat/usage-badge.tsx +176 -52
- package/src/web/components/editor/code-editor.tsx +33 -1
- package/src/web/components/editor/diff-viewer.tsx +83 -8
- package/src/web/components/editor/editor-toolbar.tsx +25 -1
- package/src/web/components/git/git-status-panel.tsx +46 -0
- package/src/web/components/git/hunk-selection.ts +132 -0
- package/src/web/components/git/hunk-stage-dialog.tsx +419 -0
- package/src/web/components/settings/accounts/account-bucket-row.tsx +19 -3
- package/src/web/components/settings/accounts/account-card.tsx +153 -33
- package/src/web/components/settings/accounts/account-hint.tsx +33 -0
- package/src/web/components/settings/accounts/account-usage-format.ts +26 -0
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +24 -9
- package/src/web/components/settings/accounts/codex-usage-rows.tsx +2 -2
- package/src/web/components/settings/accounts/use-accounts-data.ts +32 -2
- package/src/web/components/settings/accounts/use-codex-accounts.ts +24 -2
- package/src/web/components/shared/markdown-code-block.tsx +10 -5
- package/src/web/hooks/use-chat.ts +27 -0
- package/src/web/hooks/use-inline-blame.ts +175 -0
- package/src/web/hooks/use-member-transcript-tail.ts +95 -0
- package/src/web/lib/api-settings.ts +24 -0
- package/src/web/stores/settings-store.ts +12 -0
- package/dist/web/assets/accounts-settings-section-Cd1Nh04o.js +0 -1
- package/dist/web/assets/ai-settings-section-OaYGT5OF.js +0 -1
- package/dist/web/assets/api-client-BogqsFna.js +0 -1
- package/dist/web/assets/api-settings-BVfemzZW.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-BZBxtErN.js +0 -1
- package/dist/web/assets/chat-tab-Cc1W2pTu.js +0 -15
- package/dist/web/assets/code-editor-gLRyCPWE.js +0 -7
- package/dist/web/assets/diff-viewer-ix1Ko0xk.js +0 -4
- package/dist/web/assets/explorer-window-content-B7HmteDI.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-BHxJbXfV.js +0 -1
- package/dist/web/assets/group-chat-tab-DmZ1vLk-.js +0 -1
- package/dist/web/assets/index-N-pnuwFP.css +0 -2
- package/dist/web/assets/index-yV5RgBDh.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-DI4p3vbH.js +0 -1
- package/dist/web/assets/keybindings-store-88_Zkn5F.js +0 -1
- package/dist/web/assets/markdown-renderer-UhszMWSn.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-D9cqcU3X.js +0 -1
- package/dist/web/assets/notification-store-B6X6RYqQ.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-YNQWTi_-.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-E3Wiw76n.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-BPQJUKON.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-DefltAPl.js +0 -2
- package/dist/web/assets/schedules-settings-section-D76Ob6tI.js +0 -1
- package/dist/web/assets/settings-store-DWUx3865.js +0 -2
- package/dist/web/assets/settings-tab-DToHQXfC.js +0 -1
- package/dist/web/assets/settings-window-content-B2rlq-Dv.js +0 -1
- package/dist/web/assets/system-monitor-tab-CuDpwAZU.js +0 -1
- package/dist/web/assets/team-member-sheet-DLrT_Kxo.js +0 -2
- package/dist/web/assets/team-member-window-content-S8cdLcg3.js +0 -1
- package/dist/web/assets/tool-cards-CLxrQlUs.js +0 -9
- package/dist/web/assets/treemap-KZPCXAKY-C2CvKBQR.js +0 -1
- package/dist/web/assets/use-accounts-data-snYPJED_.js +0 -1
- package/dist/web/assets/ws-auth-ZSZkAT6p.js +0 -1
- /package/dist/web/assets/{abap-Cq9nYQyd.js → abap-D-o-bvtI.js} +0 -0
- /package/dist/web/assets/{actionscript-3-DL8rUIy0.js → actionscript-3-D2tIzAm8.js} +0 -0
- /package/dist/web/assets/{ada-BY8TLuhY.js → ada-DxatBcxS.js} +0 -0
- /package/dist/web/assets/{andromeeda-CyVfHsS0.js → andromeeda-D0LK9YJ3.js} +0 -0
- /package/dist/web/assets/{apache-DTQUy73x.js → apache-B7uMPNiW.js} +0 -0
- /package/dist/web/assets/{apex-CiSZngrZ.js → apex-B7GiXrAr.js} +0 -0
- /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-CEYCBt_0.js} +0 -0
- /package/dist/web/assets/{applescript-CzuRDd3h.js → applescript-D_SIFkKe.js} +0 -0
- /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-BVXAUYoN.js} +0 -0
- /package/dist/web/assets/{ara-CSFV93kR.js → ara-C3iECgdg.js} +0 -0
- /package/dist/web/assets/{asciidoc-hOQNnXhz.js → asciidoc-gc8z_MMY.js} +0 -0
- /package/dist/web/assets/{asm-DdG53NGR.js → asm-27qeoPOI.js} +0 -0
- /package/dist/web/assets/{aurora-x-DJ7ORZG6.js → aurora-x-C5J9fWe1.js} +0 -0
- /package/dist/web/assets/{awk-B3FFpX_J.js → awk-B7GBzF92.js} +0 -0
- /package/dist/web/assets/{ayu-dark-D_P1CfTe.js → ayu-dark-XlnQhuLL.js} +0 -0
- /package/dist/web/assets/{ayu-light-Cz3YLUqH.js → ayu-light-DKLLilkU.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-JcMq5tgJ.js → ayu-mirage-Dv-XOprJ.js} +0 -0
- /package/dist/web/assets/{ballerina-7Hs4Rfpk.js → ballerina-DACRT-sT.js} +0 -0
- /package/dist/web/assets/{bat-COgQdnFA.js → bat-BlQCq2XE.js} +0 -0
- /package/dist/web/assets/{beancount-BJ3CutBG.js → beancount-DYDztRI0.js} +0 -0
- /package/dist/web/assets/{berry-D7so2Cui.js → berry-BE5YKqvx.js} +0 -0
- /package/dist/web/assets/{bibtex-Ct3acHa1.js → bibtex-CAm7mgEm.js} +0 -0
- /package/dist/web/assets/{bicep-jXDwCQcm.js → bicep-BKgAbfVa.js} +0 -0
- /package/dist/web/assets/{bird2-CjLD2cna.js → bird2-yrBNbW1G.js} +0 -0
- /package/dist/web/assets/{c-CdgZojnL.js → c-txZFr4RM.js} +0 -0
- /package/dist/web/assets/{c3-DtpZYego.js → c3-D_DrzBUd.js} +0 -0
- /package/dist/web/assets/{cadence-CPLkixoh.js → cadence-CUFCEgG9.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-BntPywsy.js → can-open-in-ppm-_yqa69dV.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Xt0_qJa9.js → catppuccin-frappe-Dn32MuhA.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CJnbLec5.js → catppuccin-latte-Dewo7yDf.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-BPHP1wYB.js → catppuccin-macchiato-D8WOWZeo.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-ssWJodGc.js → catppuccin-mocha-b0md48XM.js} +0 -0
- /package/dist/web/assets/{clarity-DB0408F6.js → clarity-Ct3UqLBj.js} +0 -0
- /package/dist/web/assets/{clojure-dpRPlAO4.js → clojure-CwP5aPEo.js} +0 -0
- /package/dist/web/assets/{cmake-r2VkeX9y.js → cmake-D5RF49e5.js} +0 -0
- /package/dist/web/assets/{codeowners-v5nQqHiB.js → codeowners-Cl5897L_.js} +0 -0
- /package/dist/web/assets/{codeql-BSSEhKRR.js → codeql-BKh1RCg0.js} +0 -0
- /package/dist/web/assets/{common-lisp-BXb2MMMA.js → common-lisp-BhHuD6t0.js} +0 -0
- /package/dist/web/assets/{coq-BjBNi73X.js → coq-Dco3qwD8.js} +0 -0
- /package/dist/web/assets/{csharp-CdPjZRbV.js → csharp-Dpjw9ZA9.js} +0 -0
- /package/dist/web/assets/{css-BuxbQOHL.js → css-CPe4cabH.js} +0 -0
- /package/dist/web/assets/{csv-Dj-eoZwH.js → csv-BAcTN8Kt.js} +0 -0
- /package/dist/web/assets/{csv-parser-CBbALsFU.js → csv-parser-DbxGKdMG.js} +0 -0
- /package/dist/web/assets/{cue-Dz73VksN.js → cue-Cr6y60TA.js} +0 -0
- /package/dist/web/assets/{cypher-CkVkDywZ.js → cypher-DWtqXXFP.js} +0 -0
- /package/dist/web/assets/{d-oU51Pbpf.js → d-BASiTZk4.js} +0 -0
- /package/dist/web/assets/{dark-plus-DcwvjjfO.js → dark-plus-56kmnrcl.js} +0 -0
- /package/dist/web/assets/{dart-DGvu8mcX.js → dart-C5mZnjoE.js} +0 -0
- /package/dist/web/assets/{data-grid-types-B6kUrF-M.js → data-grid-types-DeY60CLU.js} +0 -0
- /package/dist/web/assets/{dax-BmbjKQ90.js → dax-B4I2e7d8.js} +0 -0
- /package/dist/web/assets/{desktop-IRBSiFfh.js → desktop-DekGg0BT.js} +0 -0
- /package/dist/web/assets/{diff-Bdik2JR8.js → diff-CWnJBd73.js} +0 -0
- /package/dist/web/assets/{dist-Bv4zQpS1.js → dist-B0J2eYBz.js} +0 -0
- /package/dist/web/assets/{dist-C8YPBIOb.js → dist-BKOM_AoT.js} +0 -0
- /package/dist/web/assets/{docker-Bgdl0H8I.js → docker-DevchDxK.js} +0 -0
- /package/dist/web/assets/{dotenv-COe5NdnA.js → dotenv-DCvtdaGJ.js} +0 -0
- /package/dist/web/assets/{dracula-DjURaSAa.js → dracula-8U6Vkk3l.js} +0 -0
- /package/dist/web/assets/{dracula-soft-Do7d0bo-.js → dracula-soft-Bfeew5Wg.js} +0 -0
- /package/dist/web/assets/{dream-maker-DYwQvJIj.js → dream-maker-DDTJVuoy.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-BXjlHyDW.js → emacs-lisp-CEHJqI4m.js} +0 -0
- /package/dist/web/assets/{everforest-dark-h8Q2loiX.js → everforest-dark-CHHLt7cq.js} +0 -0
- /package/dist/web/assets/{everforest-light-CdsJiL-9.js → everforest-light-TcTbK1sM.js} +0 -0
- /package/dist/web/assets/{fennel-D0CBtOvY.js → fennel-Bh4vsA1A.js} +0 -0
- /package/dist/web/assets/{fish-DzayfrMj.js → fish-D0T0fILG.js} +0 -0
- /package/dist/web/assets/{fluent-CatjkUfx.js → fluent-CgXDXdyy.js} +0 -0
- /package/dist/web/assets/{format-bytes-BulRSULX.js → format-bytes-Bc6SqF7M.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-DM04ud0b.js → fortran-free-form-CUg3Xhz4.js} +0 -0
- /package/dist/web/assets/{gdscript-BLN5iZwf.js → gdscript-Cx056p_k.js} +0 -0
- /package/dist/web/assets/{gdshader-BK-1iZQs.js → gdshader-Dj1qP4I5.js} +0 -0
- /package/dist/web/assets/{genie-BGPOFWDN.js → genie-SBt15wn7.js} +0 -0
- /package/dist/web/assets/{gherkin-CdVOJ_JY.js → gherkin-bTJiHIrB.js} +0 -0
- /package/dist/web/assets/{github-dark-B_xW8TaY.js → github-dark-BOPhyugs.js} +0 -0
- /package/dist/web/assets/{github-dark-default-h_iKq7CX.js → github-dark-default-Cktqe6NF.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-nKDYE62T.js → github-dark-dimmed-CeWfII_B.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-l8I771QT.js → github-dark-high-contrast-CTx8LpdV.js} +0 -0
- /package/dist/web/assets/{github-light-DNhjkg8Z.js → github-light-DVsO5Un1.js} +0 -0
- /package/dist/web/assets/{github-light-default-DQ06uS3g.js → github-light-default-BiyPxicr.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-qc2sZCVN.js → github-light-high-contrast-BPlnke0a.js} +0 -0
- /package/dist/web/assets/{gleam-DPPatkNH.js → gleam-CWSxvD0C.js} +0 -0
- /package/dist/web/assets/{gn-BnzGQTy5.js → gn-D7NUVJuv.js} +0 -0
- /package/dist/web/assets/{gnuplot-Ct1leoo3.js → gnuplot-c5uXmJKc.js} +0 -0
- /package/dist/web/assets/{go-qysCdbrn.js → go-CRsHi4Cw.js} +0 -0
- /package/dist/web/assets/{groovy-BkWIdrRQ.js → groovy-DpV0RA5r.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-L6Iiybbl.js → gruvbox-dark-hard-DD9fguUN.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-DbD3yl0b.js → gruvbox-dark-medium-BvcwpYJ-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-Ban2cHK3.js → gruvbox-dark-soft-48dCxX9M.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CK_SjS3e.js → gruvbox-light-hard-CjJ994Ip.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-Bjgvhc2w.js → gruvbox-light-medium-DQJQZ_rV.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-EWnyYGUu.js → gruvbox-light-soft-AqsC9mKw.js} +0 -0
- /package/dist/web/assets/{haskell-rT1N9nYr.js → haskell-B5V7yvmh.js} +0 -0
- /package/dist/web/assets/{haxe-Bbc91axo.js → haxe-LWTKfPKr.js} +0 -0
- /package/dist/web/assets/{hcl-D-3qJA4v.js → hcl-QmNZdAtI.js} +0 -0
- /package/dist/web/assets/{hjson-DWtewcyQ.js → hjson-CboJsAzK.js} +0 -0
- /package/dist/web/assets/{hlsl-C7t6hCrO.js → hlsl-Bd-DO_WL.js} +0 -0
- /package/dist/web/assets/{horizon-zJAGUQ3j.js → horizon-CU8Bi_ss.js} +0 -0
- /package/dist/web/assets/{horizon-bright-CRNyrJy8.js → horizon-bright-iSQ3_3jP.js} +0 -0
- /package/dist/web/assets/{houston-BFKa4W1-.js → houston-CTXv3Puz.js} +0 -0
- /package/dist/web/assets/{hy-W1hNjMBM.js → hy-Cd4hTidD.js} +0 -0
- /package/dist/web/assets/{image-gallery-BycaVWOu.js → image-gallery-J8h8IY0c.js} +0 -0
- /package/dist/web/assets/{imba-CPphaV-V.js → imba-BipCHEY2.js} +0 -0
- /package/dist/web/assets/{ini-CayaZ4vW.js → ini-Dd1KkMrB.js} +0 -0
- /package/dist/web/assets/{java-BPAE_9Ej.js → java-rGr0OQD8.js} +0 -0
- /package/dist/web/assets/{javascript-C5SKIVqe.js → javascript-DHhCH2TN.js} +0 -0
- /package/dist/web/assets/{json-C-0EUYmu.js → json-CMMLBCUf.js} +0 -0
- /package/dist/web/assets/{json5-BqVetEZL.js → json5-mPOwTOsB.js} +0 -0
- /package/dist/web/assets/{jsonc-DnjIzkUB.js → jsonc-Bie0E6D3.js} +0 -0
- /package/dist/web/assets/{jsonl-rbV-3EqO.js → jsonl-CpqHmtC1.js} +0 -0
- /package/dist/web/assets/{jsonnet-9hvXw4qr.js → jsonnet-D21Ms51e.js} +0 -0
- /package/dist/web/assets/{jssm-1ifXyIhu.js → jssm-BXnl1nur.js} +0 -0
- /package/dist/web/assets/{jsx-hPumywTY.js → jsx-Chnii_vB.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BCPjmoMO.js → kanagawa-dragon-DhxR0UjL.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-DN2VNjy7.js → kanagawa-lotus-fupRg5wp.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-B8RZEsgc.js → kanagawa-wave-DSWbRS9M.js} +0 -0
- /package/dist/web/assets/{kdl-CkvjWQIa.js → kdl-DirGHqUy.js} +0 -0
- /package/dist/web/assets/{kotlin-D8cnRi5o.js → kotlin-DkhUVMDC.js} +0 -0
- /package/dist/web/assets/{kusto-CmeeBEJG.js → kusto-DU7OpovF.js} +0 -0
- /package/dist/web/assets/{laserwave--rtV8Mv9.js → laserwave-iLKb1ZCT.js} +0 -0
- /package/dist/web/assets/{lean-CS-Pyi42.js → lean-Cuvh_QAP.js} +0 -0
- /package/dist/web/assets/{less-Ddi_7qGm.js → less-B5tQZOdI.js} +0 -0
- /package/dist/web/assets/{lib-DIrxSyLb.js → lib-el9rA30D.js} +0 -0
- /package/dist/web/assets/{libesm-CYLrAeW3.js → libesm-CdttTIAd.js} +0 -0
- /package/dist/web/assets/{light-plus-CrEjpkkX.js → light-plus-DiHU2KL5.js} +0 -0
- /package/dist/web/assets/{llvm-Cm7ZXmAO.js → llvm-sCBTKCKN.js} +0 -0
- /package/dist/web/assets/{log-lW2sYxtp.js → log-DtyR34Ie.js} +0 -0
- /package/dist/web/assets/{logo-B-PiRb53.js → logo-CP91aQyG.js} +0 -0
- /package/dist/web/assets/{luau-8St0pC-R.js → luau-nfGo2ulP.js} +0 -0
- /package/dist/web/assets/{make-DbNdhH54.js → make-XbDn9GHe.js} +0 -0
- /package/dist/web/assets/{markdown-CYocYSGd.js → markdown-BB1_3xLr.js} +0 -0
- /package/dist/web/assets/{material-theme-CVBF1Cbi.js → material-theme-CIx_sxAy.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-HFrlNZD8.js → material-theme-darker-DsQeueDD.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BM-uz2ft.js → material-theme-lighter-x_lMi74j.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-PbvUez8m.js → material-theme-ocean-Bkgh25P8.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-B22Ck6qd.js → material-theme-palenight-CoOjdbti.js} +0 -0
- /package/dist/web/assets/{matlab-GFFNzucw.js → matlab-D6xZas_8.js} +0 -0
- /package/dist/web/assets/{mdx-BVnJzMDx.js → mdx-Cl5_YNoz.js} +0 -0
- /package/dist/web/assets/{mermaid-BlCmSTCM.js → mermaid-8Eow4jz_.js} +0 -0
- /package/dist/web/assets/{min-dark-B0PwlAfC.js → min-dark-D4DhjgRE.js} +0 -0
- /package/dist/web/assets/{min-light-C_H2ABKu.js → min-light-DzAckd0t.js} +0 -0
- /package/dist/web/assets/{mipsasm-C3rf2fo6.js → mipsasm-zI_zu0tj.js} +0 -0
- /package/dist/web/assets/{mojo-CDRuzM3o.js → mojo-DQnGGPHf.js} +0 -0
- /package/dist/web/assets/{monokai-KiebNy_Y.js → monokai-CrFoD2VD.js} +0 -0
- /package/dist/web/assets/{moonbit-CdVUHoWl.js → moonbit-DdphIGs6.js} +0 -0
- /package/dist/web/assets/{move-Dq9iKZRY.js → move-BUlkn0xW.js} +0 -0
- /package/dist/web/assets/{narrat-7y5L5sGM.js → narrat-CbOBdtCn.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-Cwcbkcrx.js → nextflow-groovy-CeVLUTuQ.js} +0 -0
- /package/dist/web/assets/{night-owl-TBgLPzj4.js → night-owl-Bm4LhUR1.js} +0 -0
- /package/dist/web/assets/{night-owl-light-Dehb9My2.js → night-owl-light-DtcBiMEf.js} +0 -0
- /package/dist/web/assets/{nix-BKj-gzoX.js → nix-DrdwQ70g.js} +0 -0
- /package/dist/web/assets/{nord-BF_r8PeX.js → nord-B0Head1v.js} +0 -0
- /package/dist/web/assets/{nushell-CeAjk9_E.js → nushell-CmUdltKn.js} +0 -0
- /package/dist/web/assets/{objective-c-Xl1OMcDp.js → objective-c-BGDR33XN.js} +0 -0
- /package/dist/web/assets/{objective-cpp-C4ZgzrRq.js → objective-cpp-Buc1Zhd0.js} +0 -0
- /package/dist/web/assets/{ocaml-CGj8IYTq.js → ocaml-xdzl9rRD.js} +0 -0
- /package/dist/web/assets/{odin-C4veqvW2.js → odin-DiUFngnd.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-gAcWejsV.js → one-dark-pro-BURxmIp3.js} +0 -0
- /package/dist/web/assets/{one-light-B7E-UUXy.js → one-light-BMRMInAj.js} +0 -0
- /package/dist/web/assets/{openscad-C-wN9BEv.js → openscad-DAgLA5J3.js} +0 -0
- /package/dist/web/assets/{pascal-C8WBtxMs.js → pascal-CoDZxcRB.js} +0 -0
- /package/dist/web/assets/{pkl-BBfF4G2A.js → pkl-DzKM6BuM.js} +0 -0
- /package/dist/web/assets/{plastic-DFOAb89P.js → plastic-vF15LmNS.js} +0 -0
- /package/dist/web/assets/{plsql-BS2CEMMV.js → plsql-C1v07teA.js} +0 -0
- /package/dist/web/assets/{po-BKJDtPmy.js → po-CLLC8BuA.js} +0 -0
- /package/dist/web/assets/{poimandres-Cg3hL13e.js → poimandres-Uq0xysxM.js} +0 -0
- /package/dist/web/assets/{polar-B9ebiXUU.js → polar-2-luY1Pc.js} +0 -0
- /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DKymYOY-.js} +0 -0
- /package/dist/web/assets/{postcss-B5REwNY_.js → postcss-lbFhe0rz.js} +0 -0
- /package/dist/web/assets/{powerquery-stc0Gzkf.js → powerquery-RL8O9Wie.js} +0 -0
- /package/dist/web/assets/{powershell-jgd0ahJN.js → powershell-upFWhb-b.js} +0 -0
- /package/dist/web/assets/{prisma-CWuZjRhI.js → prisma-DBtYaWFk.js} +0 -0
- /package/dist/web/assets/{prolog-D5zNFE63.js → prolog-D8jSMykJ.js} +0 -0
- /package/dist/web/assets/{proto-B-6RrU7n.js → proto-BM4eadF7.js} +0 -0
- /package/dist/web/assets/{puppet-DONP8TDR.js → puppet-BQDQ3Snu.js} +0 -0
- /package/dist/web/assets/{purescript-DDJITf-A.js → purescript-Cj2aTX4s.js} +0 -0
- /package/dist/web/assets/{python-bmWy9j1-.js → python-BYpqq6u4.js} +0 -0
- /package/dist/web/assets/{qmldir-CKDwMt7X.js → qmldir-4uiTcwAu.js} +0 -0
- /package/dist/web/assets/{qss-DptQQzBn.js → qss-D5xB-Mqo.js} +0 -0
- /package/dist/web/assets/{r-BorKpJ8r.js → r-P6Ujy8rf.js} +0 -0
- /package/dist/web/assets/{racket-BaHpYVsZ.js → racket-COKgCFuM.js} +0 -0
- /package/dist/web/assets/{raku-DFJIV4k_.js → raku-z14Q9i7K.js} +0 -0
- /package/dist/web/assets/{react-X4jUntEf.js → react-D1Yg256j.js} +0 -0
- /package/dist/web/assets/{red-70oJGrkb.js → red-ObAvgvQ5.js} +0 -0
- /package/dist/web/assets/{reg-C4g235Dv.js → reg-Qv4f0JFA.js} +0 -0
- /package/dist/web/assets/{regexp-Cw1UVmti.js → regexp-CEOVGIzy.js} +0 -0
- /package/dist/web/assets/{rel-DeLhIz5L.js → rel-cWOyTUwd.js} +0 -0
- /package/dist/web/assets/{resolve-theme-b_yGlUFQ.js → resolve-theme-CMp5xxCW.js} +0 -0
- /package/dist/web/assets/{riscv-ChnTiBRK.js → riscv-BZDwx-lv.js} +0 -0
- /package/dist/web/assets/{ron-C5IWeqDn.js → ron-BpjosPS0.js} +0 -0
- /package/dist/web/assets/{rose-pine-DCR0ohYW.js → rose-pine-DhxfJWcm.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-DvGZs1PR.js → rose-pine-dawn-BbGt9Wy_.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-BWACHjeS.js → rose-pine-moon-dj4Zxgik.js} +0 -0
- /package/dist/web/assets/{rosmsg-CQlIVazE.js → rosmsg-EciOaHo2.js} +0 -0
- /package/dist/web/assets/{rust-DGGVzWI8.js → rust-C_eH59O_.js} +0 -0
- /package/dist/web/assets/{sass-bj1C0rFI.js → sass-Dos3R5lL.js} +0 -0
- /package/dist/web/assets/{scala-MU1tLKDi.js → scala-8lVKsTvf.js} +0 -0
- /package/dist/web/assets/{scheme-CeGXtv7t.js → scheme-Bd3VdaZj.js} +0 -0
- /package/dist/web/assets/{sdbl-D0HLSGKp.js → sdbl-C4Yrjlgh.js} +0 -0
- /package/dist/web/assets/{shellscript-DkYiDvh1.js → shellscript-OFZbPKZH.js} +0 -0
- /package/dist/web/assets/{slack-dark-YqeHiGWH.js → slack-dark-aL6vlxZs.js} +0 -0
- /package/dist/web/assets/{slack-ochin-9Oe0DljJ.js → slack-ochin-BKt1E7sL.js} +0 -0
- /package/dist/web/assets/{smalltalk-CaaxedR7.js → smalltalk-D6qVBL1U.js} +0 -0
- /package/dist/web/assets/{snazzy-light-C8s2FHNC.js → snazzy-light-BrWerT0t.js} +0 -0
- /package/dist/web/assets/{solarized-dark-0ryY6oN3.js → solarized-dark-CzxTJgVQ.js} +0 -0
- /package/dist/web/assets/{solarized-light-bltgZeNh.js → solarized-light-BecaDAnG.js} +0 -0
- /package/dist/web/assets/{solidity-DOf5HmLZ.js → solidity-C9t7pZ84.js} +0 -0
- /package/dist/web/assets/{splunk-D1u4WHDW.js → splunk-CtxfFKtF.js} +0 -0
- /package/dist/web/assets/{sql-G8uQZljX.js → sql-7FE_Duz0.js} +0 -0
- /package/dist/web/assets/{ssh-config-rbXvly7Q.js → ssh-config-3Fr37ACC.js} +0 -0
- /package/dist/web/assets/{stylus-Cii1iADy.js → stylus-BP4mE1sO.js} +0 -0
- /package/dist/web/assets/{swift-DpIgonN4.js → swift-0uqTFouD.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DjGLzU2b.js → synthwave-84-DQaNM3DI.js} +0 -0
- /package/dist/web/assets/{system-verilog-_Z0M0Hs1.js → system-verilog-DUIVrZHZ.js} +0 -0
- /package/dist/web/assets/{systemd-BhyDCh9P.js → systemd-CtNo_WIh.js} +0 -0
- /package/dist/web/assets/{talonscript-CNchgCRD.js → talonscript-B0HXaCVT.js} +0 -0
- /package/dist/web/assets/{tasl-m--W7Pyr.js → tasl-Cb7RAl11.js} +0 -0
- /package/dist/web/assets/{tcl-gY-M_2BC.js → tcl-B79aC6l5.js} +0 -0
- /package/dist/web/assets/{terraform-CB6CzMwo.js → terraform-75jJDjpW.js} +0 -0
- /package/dist/web/assets/{tokyo-night-COh9oRYi.js → tokyo-night-Cz9Uh9qb.js} +0 -0
- /package/dist/web/assets/{toml-Bg2bsY-v.js → toml-CD6y9A-z.js} +0 -0
- /package/dist/web/assets/{tsv-DceGDiJC.js → tsv-CXJMbBrb.js} +0 -0
- /package/dist/web/assets/{tsx-EccDYa7i.js → tsx-BuYN0I2t.js} +0 -0
- /package/dist/web/assets/{turtle-C_l2JhvX.js → turtle-DWH-Rjse.js} +0 -0
- /package/dist/web/assets/{typescript-C1ytmMcr.js → typescript-BPd-hsrR.js} +0 -0
- /package/dist/web/assets/{typespec-De2ztzG5.js → typespec-B0wvVykx.js} +0 -0
- /package/dist/web/assets/{typst-DNcS7gSr.js → typst-DWG2c48-.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-CYW3FJFE.js → use-is-mobile-D1weanbj.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-D9O48HQt.js → use-visual-viewport-Bfl3Bxxb.js} +0 -0
- /package/dist/web/assets/{utils-C9OjCrou.js → utils-IrnQujwR.js} +0 -0
- /package/dist/web/assets/{v-DSMUsdMO.js → v-Ch6E4Egd.js} +0 -0
- /package/dist/web/assets/{vala-BEazLpkO.js → vala-CGVKwbPx.js} +0 -0
- /package/dist/web/assets/{vb-CV9pEJzW.js → vb-BIEbSxyT.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-cuGQmUjN.js → vendor-xterm-YQlzIWv2.js} +0 -0
- /package/dist/web/assets/{verilog-DhXkLVMk.js → verilog-A-3pYPad.js} +0 -0
- /package/dist/web/assets/{vesper-n4IltYq-.js → vesper-CK1naZAV.js} +0 -0
- /package/dist/web/assets/{vhdl-CePjFbvs.js → vhdl-CVawmgMg.js} +0 -0
- /package/dist/web/assets/{viml-ChndFsm5.js → viml-CiWjaqBY.js} +0 -0
- /package/dist/web/assets/{vitesse-black-BytVdvhC.js → vitesse-black-PYXW4-BO.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-83Wech0N.js → vitesse-dark-BU5s4AuG.js} +0 -0
- /package/dist/web/assets/{vitesse-light-Xg4wbeMO.js → vitesse-light-DgkIT-N7.js} +0 -0
- /package/dist/web/assets/{vyper-eW1NC3FG.js → vyper-D8cRDPp3.js} +0 -0
- /package/dist/web/assets/{wasm-BucxKwBZ.js → wasm-C02wlRbP.js} +0 -0
- /package/dist/web/assets/{wasm-Bhucqu1z.js → wasm-ClFJgzmT.js} +0 -0
- /package/dist/web/assets/{wenyan-BJFBuXuH.js → wenyan-B8plxIr8.js} +0 -0
- /package/dist/web/assets/{wgsl-6z4FUyMK.js → wgsl-CVujtm3V.js} +0 -0
- /package/dist/web/assets/{wikitext-BoDU6teJ.js → wikitext-CmcG9B69.js} +0 -0
- /package/dist/web/assets/{wit-DerMPpgt.js → wit--gl2Jium.js} +0 -0
- /package/dist/web/assets/{wolfram-D78v0Ap-.js → wolfram-6T_1D-NS.js} +0 -0
- /package/dist/web/assets/{x-YDu4m2Iq.js → x-CxoBA0po.js} +0 -0
- /package/dist/web/assets/{yaml-C4TIgIw4.js → yaml-CIVV9uA1.js} +0 -0
- /package/dist/web/assets/{zenscript-BydW-dLC.js → zenscript-CoT4yNdA.js} +0 -0
- /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 =
|
|
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
|
-
|
|
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
|
|
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();
|