@hienlh/ppm 0.9.0-beta.9 → 0.9.2

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 (256) hide show
  1. package/.claude.bak/agent-memory/tester/MEMORY.md +3 -0
  2. package/.claude.bak/agent-memory/tester/project-ppm-test-conventions.md +32 -0
  3. package/CHANGELOG.md +240 -0
  4. package/bun.lock +17 -0
  5. package/dist/web/assets/{_basePickBy-3Xe18azI.js → _basePickBy-5PGDJbfF.js} +1 -1
  6. package/dist/web/assets/{_baseUniq-Yy35llnn.js → _baseUniq-BT4Ow4Kk.js} +1 -1
  7. package/dist/web/assets/api-settings-BUvk6Saw.js +1 -0
  8. package/dist/web/assets/{arc-B9n1Gvb5.js → arc-BAOivWpI.js} +1 -1
  9. package/dist/web/assets/architecture-PBZL5I3N-DEO2f3VD.js +1 -0
  10. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-DqAZP_F6.js → architectureDiagram-2XIMDMQ5-Z-4eN4za.js} +1 -1
  11. package/dist/web/assets/arrow-up-BYhx9ckd.js +1 -0
  12. package/dist/web/assets/{blockDiagram-WCTKOSBZ-h3cDF2vI.js → blockDiagram-WCTKOSBZ-BCLqzhuZ.js} +1 -1
  13. package/dist/web/assets/browser-tab-CjUzlPYv.js +1 -0
  14. package/dist/web/assets/{c4Diagram-IC4MRINW--pF1r5lr.js → c4Diagram-IC4MRINW-0Vp0Jeas.js} +1 -1
  15. package/dist/web/assets/channel-By7bn0Yq.js +1 -0
  16. package/dist/web/assets/chat-tab-moB4W7-w.js +8 -0
  17. package/dist/web/assets/chevron-right-5HgK6l7K.js +1 -0
  18. package/dist/web/assets/{chunk-4BX2VUAB-C3aZvW7B.js → chunk-4BX2VUAB-D4tOov49.js} +1 -1
  19. package/dist/web/assets/{chunk-55IACEB6-D5cABeB9.js → chunk-55IACEB6-DJ6BynZ4.js} +1 -1
  20. package/dist/web/assets/{chunk-7E7YKBS2-CkFGv6Zs.js → chunk-7E7YKBS2-CiyUJxNI.js} +1 -1
  21. package/dist/web/assets/{chunk-7R4GIKGN-Dvbyu4Zw.js → chunk-7R4GIKGN-Dv-4cAYn.js} +2 -2
  22. package/dist/web/assets/{chunk-C72U2L5F-CtqKiH4q.js → chunk-C72U2L5F-D21mS_6G.js} +1 -1
  23. package/dist/web/assets/{chunk-EGIJ26TM-Cpr87sBR.js → chunk-EGIJ26TM-DzqmU2Z7.js} +1 -1
  24. package/dist/web/assets/{chunk-FMBD7UC4-D23YVTOU.js → chunk-FMBD7UC4-DXncblvW.js} +1 -1
  25. package/dist/web/assets/{chunk-GEFDOKGD-tDjHsAUs.js → chunk-GEFDOKGD-D-pKjlVd.js} +1 -1
  26. package/dist/web/assets/chunk-GLR3WWYH-DKikpoJM.js +2 -0
  27. package/dist/web/assets/chunk-HHEYEP7N-C7vxA5i9.js +1 -0
  28. package/dist/web/assets/{chunk-JSJVCQXG-BBmymCjA.js → chunk-JSJVCQXG-99JzIdPr.js} +1 -1
  29. package/dist/web/assets/{chunk-KX2RTZJC-DP36BDiU.js → chunk-KX2RTZJC-CRq1OBZv.js} +1 -1
  30. package/dist/web/assets/{chunk-KYZI473N-Djw13C-3.js → chunk-KYZI473N-Bb0MCaIO.js} +1 -1
  31. package/dist/web/assets/{chunk-L3YUKLVL-HG_eMj_C.js → chunk-L3YUKLVL-C7qGJrfV.js} +1 -1
  32. package/dist/web/assets/{chunk-MX3YWQON-C2UEioMs.js → chunk-MX3YWQON-BpS_PtKp.js} +1 -1
  33. package/dist/web/assets/{chunk-NQ4KR5QH-DXUTQ-BL.js → chunk-NQ4KR5QH-z_blpjxi.js} +1 -1
  34. package/dist/web/assets/{chunk-O4XLMI2P-BsUWb9d0.js → chunk-O4XLMI2P-nDhi_cVu.js} +1 -1
  35. package/dist/web/assets/{chunk-OZEHJAEY-rG0P22U9.js → chunk-OZEHJAEY-BXhYx3nO.js} +1 -1
  36. package/dist/web/assets/{chunk-PQ6SQG4A-DX0xW7kO.js → chunk-PQ6SQG4A-TF58UVMU.js} +1 -1
  37. package/dist/web/assets/{chunk-PU5JKC2W-C7Gry6md.js → chunk-PU5JKC2W-ek7k4QVB.js} +1 -1
  38. package/dist/web/assets/chunk-QZHKN3VN-CYaTbeZf.js +1 -0
  39. package/dist/web/assets/{chunk-R5LLSJPH-CMY0PkRK.js → chunk-R5LLSJPH-CFwSJijQ.js} +1 -1
  40. package/dist/web/assets/{chunk-WL4C6EOR-CXuQvlyu.js → chunk-WL4C6EOR-ByUrSRin.js} +1 -1
  41. package/dist/web/assets/{chunk-XIRO2GV7-DRJEb7Zb.js → chunk-XIRO2GV7-Djlmrely.js} +1 -1
  42. package/dist/web/assets/{chunk-XPW4576I-BPEX8KhL.js → chunk-XPW4576I-BPQQBakK.js} +1 -1
  43. package/dist/web/assets/{chunk-XZSTWKYB-Cb0iqycX.js → chunk-XZSTWKYB-DxAOx4hG.js} +1 -1
  44. package/dist/web/assets/{chunk-YBOYWFTD-av5aeHLq.js → chunk-YBOYWFTD-rQG3QH5s.js} +1 -1
  45. package/dist/web/assets/classDiagram-VBA2DB6C-BA8Nj-_C.js +1 -0
  46. package/dist/web/assets/classDiagram-v2-RAHNMMFH-DjYu-6mn.js +1 -0
  47. package/dist/web/assets/clone-LRxlvnMj.js +1 -0
  48. package/dist/web/assets/code-editor-aQQZUc2m.js +2 -0
  49. package/dist/web/assets/columns-2-cEVJHYd7.js +1 -0
  50. package/dist/web/assets/{cose-bilkent-S5V4N54A-qudEiMCT.js → cose-bilkent-S5V4N54A-B_AWZsOP.js} +1 -1
  51. package/dist/web/assets/createLucideIcon-PuMiQgHl.js +1 -0
  52. package/dist/web/assets/{csv-preview-DUbHtTAS.js → csv-preview-ncSOnJSC.js} +2 -2
  53. package/dist/web/assets/{dagre-BFcnKyBF.js → dagre-DHq9bhnd.js} +1 -1
  54. package/dist/web/assets/{dagre-KLK3FWXG-C3O-MTLf.js → dagre-KLK3FWXG-BdJr7Byp.js} +1 -1
  55. package/dist/web/assets/database-viewer-ChyP1N3c.js +1 -0
  56. package/dist/web/assets/{diagram-E7M64L7V-DxPjK7_c.js → diagram-E7M64L7V-_db4pBVA.js} +1 -1
  57. package/dist/web/assets/{diagram-IFDJBPK2-sqTog_XV.js → diagram-IFDJBPK2-xKoeuiJx.js} +1 -1
  58. package/dist/web/assets/{diagram-P4PSJMXO-hzmp0GHK.js → diagram-P4PSJMXO-C8tjJsev.js} +1 -1
  59. package/dist/web/assets/diff-viewer-ktwO5JbX.js +4 -0
  60. package/dist/web/assets/{dist-CALwEtco.js → dist-DIV6WgAG.js} +1 -1
  61. package/dist/web/assets/{dist-DGDPTxs1.js → dist-ovWkrgO-.js} +1 -1
  62. package/dist/web/assets/{erDiagram-INFDFZHY-DLeYhAAT.js → erDiagram-INFDFZHY-BSh2z9Df.js} +1 -1
  63. package/dist/web/assets/extension-webview-Bx1TlP6q.js +3 -0
  64. package/dist/web/assets/{flowDiagram-PKNHOUZH-CRxlE9Sr.js → flowDiagram-PKNHOUZH-oYaovqyp.js} +1 -1
  65. package/dist/web/assets/{ganttDiagram-A5KZAMGK-BdjmoMLS.js → ganttDiagram-A5KZAMGK-DmL26q2P.js} +1 -1
  66. package/dist/web/assets/git-graph-BIrGMX6e.js +1 -0
  67. package/dist/web/assets/gitGraph-HDMCJU4V-Bwna3and.js +1 -0
  68. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js → gitGraphDiagram-K3NZZRJ6-CMoukSrY.js} +1 -1
  69. package/dist/web/assets/{graphlib-Duh_bWLa.js → graphlib-BcsNnGcW.js} +1 -1
  70. package/dist/web/assets/index-C6KLr58u.js +37 -0
  71. package/dist/web/assets/index-DpBKDbIW.css +2 -0
  72. package/dist/web/assets/info-3K5VOQVL-_vRxVNUm.js +1 -0
  73. package/dist/web/assets/infoDiagram-LFFYTUFH-DWwumDkq.js +2 -0
  74. package/dist/web/assets/{isEmpty-B9L-Ge-H.js → isEmpty-bnrF3Qbc.js} +1 -1
  75. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js → ishikawaDiagram-PHBUUO56-D05_LyL7.js} +1 -1
  76. package/dist/web/assets/{journeyDiagram-4ABVD52K-CgDI-UG4.js → journeyDiagram-4ABVD52K-B_L20qMe.js} +1 -1
  77. package/dist/web/assets/jsx-runtime-kMwlnEGE.js +1 -0
  78. package/dist/web/assets/{kanban-definition-K7BYSVSG-h4g10UHL.js → kanban-definition-K7BYSVSG-CZ535BbZ.js} +1 -1
  79. package/dist/web/assets/keybindings-store-D3Y5c5uS.js +1 -0
  80. package/dist/web/assets/{line-B75-Rx70.js → line-CVvo3dRu.js} +1 -1
  81. package/dist/web/assets/{linear-Bcjv9FQt.js → linear-DP4mkX3m.js} +1 -1
  82. package/dist/web/assets/{markdown-renderer-VIZB1GXE.js → markdown-renderer-A7J2gdKT.js} +5 -5
  83. package/dist/web/assets/{mermaid-parser.core-8u2leTXI.js → mermaid-parser.core-C7UwoIh6.js} +2 -2
  84. package/dist/web/assets/{mindmap-definition-YRQLILUH-BaOBwb-W.js → mindmap-definition-YRQLILUH-x0MTutJp.js} +1 -1
  85. package/dist/web/assets/{ordinal-LFEjVtwQ.js → ordinal-_K3x1fkz.js} +1 -1
  86. package/dist/web/assets/packet-RMMSAZCW-DY5PNnZU.js +1 -0
  87. package/dist/web/assets/pie-UPGHQEXC-BHncZutv.js +1 -0
  88. package/dist/web/assets/{pieDiagram-SKSYHLDU-At5Kz0KK.js → pieDiagram-SKSYHLDU-C1Gjrtzy.js} +1 -1
  89. package/dist/web/assets/postgres-viewer-C9-Acry_.js +1 -0
  90. package/dist/web/assets/{quadrantDiagram-337W2JSQ-CdjGIDfw.js → quadrantDiagram-337W2JSQ-C8bzJCjQ.js} +1 -1
  91. package/dist/web/assets/radar-KQ55EAFF-DH0AOkUy.js +1 -0
  92. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-B9F_Cx_p.js → requirementDiagram-Z7DCOOCP-pQyah6WB.js} +1 -1
  93. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-RolPi8bU.js → sankeyDiagram-WA2Y5GQK-T6RgG-N8.js} +1 -1
  94. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-DM-tMAhx.js → sequenceDiagram-2WXFIKYE-BQDJ4CVs.js} +1 -1
  95. package/dist/web/assets/settings-tab-C17exmRv.js +1 -0
  96. package/dist/web/assets/sqlite-viewer-Dr5oWCWA.js +1 -0
  97. package/dist/web/assets/{stateDiagram-RAJIS63D-C4EMl6jf.js → stateDiagram-RAJIS63D-66vhiIuk.js} +1 -1
  98. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-BGVqj_g9.js +1 -0
  99. package/dist/web/assets/tab-store-BOgTrqRr.js +1 -0
  100. package/dist/web/assets/table-DFevCOMd.js +1 -0
  101. package/dist/web/assets/tag-CXMT0QB6.js +1 -0
  102. package/dist/web/assets/{terminal-tab-XhKfb4ei.js → terminal-tab-CpyKvyfC.js} +1 -1
  103. package/dist/web/assets/{timeline-definition-YZTLITO2-A4PN_Efm.js → timeline-definition-YZTLITO2-DwZqB3nn.js} +1 -1
  104. package/dist/web/assets/treemap-KZPCXAKY-B2Xkyv-K.js +1 -0
  105. package/dist/web/assets/{use-monaco-theme-0p0-84jJ.js → use-monaco-theme-BjPAik5w.js} +1 -1
  106. package/dist/web/assets/{vennDiagram-LZ73GAT5-ywK7LMaH.js → vennDiagram-LZ73GAT5-s9Z71fz-.js} +1 -1
  107. package/dist/web/assets/{xychartDiagram-JWTSCODW-DylHYNtJ.js → xychartDiagram-JWTSCODW-DRa_TH4B.js} +1 -1
  108. package/dist/web/index.html +10 -9
  109. package/dist/web/sw.js +1 -1
  110. package/docs/code-standards.md +128 -1
  111. package/docs/codebase-summary.md +79 -12
  112. package/docs/extension-development-guide.md +532 -0
  113. package/docs/project-changelog.md +51 -1
  114. package/docs/project-roadmap.md +9 -3
  115. package/docs/system-architecture.md +432 -3
  116. package/package.json +6 -3
  117. package/packages/ext-database/package.json +41 -0
  118. package/packages/ext-database/src/connection-tree.ts +142 -0
  119. package/packages/ext-database/src/extension.ts +346 -0
  120. package/packages/ext-database/src/query-panel.ts +120 -0
  121. package/packages/ext-database/src/table-viewer-panel.ts +410 -0
  122. package/packages/ext-database/tsconfig.json +8 -0
  123. package/packages/vscode-compat/package.json +16 -0
  124. package/packages/vscode-compat/src/commands.ts +39 -0
  125. package/packages/vscode-compat/src/context.ts +65 -0
  126. package/packages/vscode-compat/src/disposable.ts +21 -0
  127. package/packages/vscode-compat/src/env.ts +20 -0
  128. package/packages/vscode-compat/src/event-emitter.ts +28 -0
  129. package/packages/vscode-compat/src/index.ts +93 -0
  130. package/packages/vscode-compat/src/not-supported.ts +15 -0
  131. package/packages/vscode-compat/src/types.ts +167 -0
  132. package/packages/vscode-compat/src/uri.ts +65 -0
  133. package/packages/vscode-compat/src/window.ts +229 -0
  134. package/packages/vscode-compat/src/workspace.ts +76 -0
  135. package/packages/vscode-compat/tsconfig.json +10 -0
  136. package/src/cli/commands/autostart.ts +1 -1
  137. package/src/cli/commands/ext-cmd.ts +121 -0
  138. package/src/cli/commands/restart.ts +9 -1
  139. package/src/cli/commands/status.ts +19 -0
  140. package/src/index.ts +5 -3
  141. package/src/providers/claude-agent-sdk.ts +221 -17
  142. package/src/providers/cli-provider-base.ts +6 -0
  143. package/src/server/index.ts +55 -155
  144. package/src/server/routes/chat.ts +81 -11
  145. package/src/server/routes/extensions.ts +81 -0
  146. package/src/server/routes/project-scoped.ts +2 -0
  147. package/src/server/routes/settings.ts +27 -0
  148. package/src/server/routes/workspace.ts +35 -0
  149. package/src/server/ws/chat.ts +9 -3
  150. package/src/server/ws/extensions.ts +175 -0
  151. package/src/services/account-selector.service.ts +14 -5
  152. package/src/services/account.service.ts +7 -7
  153. package/src/services/claude-usage.service.ts +11 -11
  154. package/src/services/cloud-ws.service.ts +228 -0
  155. package/src/services/cloud.service.ts +1 -0
  156. package/src/services/contribution-registry.ts +110 -0
  157. package/src/services/db.service.ts +181 -4
  158. package/src/services/extension-host-worker.ts +160 -0
  159. package/src/services/extension-installer.ts +112 -0
  160. package/src/services/extension-manifest.ts +65 -0
  161. package/src/services/extension-rpc-handlers.ts +235 -0
  162. package/src/services/extension-rpc.ts +105 -0
  163. package/src/services/extension.service.ts +228 -0
  164. package/src/services/mcp-config.service.ts +15 -6
  165. package/src/services/supervisor.ts +271 -25
  166. package/src/types/api.ts +1 -0
  167. package/src/types/chat.ts +4 -0
  168. package/src/types/extension-messages.ts +64 -0
  169. package/src/types/extension.ts +131 -0
  170. package/src/web/app.tsx +69 -48
  171. package/src/web/components/chat/account-rotation-settings.tsx +163 -0
  172. package/src/web/components/chat/chat-history-bar.tsx +106 -10
  173. package/src/web/components/chat/chat-tab.tsx +15 -10
  174. package/src/web/components/chat/chat-welcome.tsx +148 -0
  175. package/src/web/components/chat/message-input.tsx +29 -29
  176. package/src/web/components/chat/message-list.tsx +19 -6
  177. package/src/web/components/chat/session-picker.tsx +80 -32
  178. package/src/web/components/chat/usage-badge.tsx +83 -10
  179. package/src/web/components/extensions/extension-inputbox.tsx +92 -0
  180. package/src/web/components/extensions/extension-quickpick.tsx +194 -0
  181. package/src/web/components/extensions/extension-tree-view.tsx +240 -0
  182. package/src/web/components/extensions/extension-webview.tsx +83 -0
  183. package/src/web/components/layout/command-palette.tsx +22 -2
  184. package/src/web/components/layout/editor-panel.tsx +163 -18
  185. package/src/web/components/layout/mobile-nav.tsx +2 -1
  186. package/src/web/components/layout/sidebar.tsx +21 -3
  187. package/src/web/components/layout/status-bar.tsx +64 -0
  188. package/src/web/components/layout/tab-bar.tsx +2 -0
  189. package/src/web/components/layout/tab-content.tsx +5 -0
  190. package/src/web/components/layout/upgrade-banner.tsx +15 -5
  191. package/src/web/components/settings/change-password-section.tsx +128 -0
  192. package/src/web/components/settings/extension-manager-section.tsx +214 -0
  193. package/src/web/components/settings/settings-tab.tsx +9 -2
  194. package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
  195. package/src/web/hooks/use-chat.ts +28 -0
  196. package/src/web/hooks/use-extension-ws.ts +181 -0
  197. package/src/web/hooks/use-global-keybindings.ts +18 -2
  198. package/src/web/hooks/use-server-reload.ts +9 -0
  199. package/src/web/hooks/use-url-sync.ts +173 -21
  200. package/src/web/stores/connection-store.ts +39 -0
  201. package/src/web/stores/extension-store.ts +204 -0
  202. package/src/web/stores/panel-store.ts +63 -9
  203. package/src/web/stores/panel-utils.ts +145 -3
  204. package/src/web/stores/settings-store.ts +7 -2
  205. package/src/web/stores/tab-store.ts +2 -1
  206. package/tsconfig.json +3 -1
  207. package/dist/web/assets/api-settings-CEMxVMCV.js +0 -1
  208. package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +0 -1
  209. package/dist/web/assets/arrow-up--LjUXLEt.js +0 -1
  210. package/dist/web/assets/browser-tab-D1Zua62g.js +0 -1
  211. package/dist/web/assets/channel-C2fMafck.js +0 -1
  212. package/dist/web/assets/chat-tab-BnD27Vp9.js +0 -7
  213. package/dist/web/assets/chevron-right-CHnjJt4E.js +0 -1
  214. package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +0 -2
  215. package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +0 -1
  216. package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +0 -1
  217. package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +0 -1
  218. package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +0 -1
  219. package/dist/web/assets/clone-B2hUek6n.js +0 -1
  220. package/dist/web/assets/code-editor-DGRg8stf.js +0 -2
  221. package/dist/web/assets/columns-2-DbesTfa7.js +0 -1
  222. package/dist/web/assets/database-viewer-DxCXZQcE.js +0 -1
  223. package/dist/web/assets/diff-viewer-C1sDJG35.js +0 -4
  224. package/dist/web/assets/git-graph-BDn-EiGE.js +0 -1
  225. package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +0 -1
  226. package/dist/web/assets/index-Bun94AK3.js +0 -37
  227. package/dist/web/assets/index-Db8uky1a.css +0 -2
  228. package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +0 -1
  229. package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +0 -2
  230. package/dist/web/assets/jsx-runtime-BRW_vwa9.js +0 -1
  231. package/dist/web/assets/keybindings-store-COmK4Dte.js +0 -1
  232. package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +0 -1
  233. package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +0 -1
  234. package/dist/web/assets/postgres-viewer-CvQZ8gkh.js +0 -1
  235. package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +0 -1
  236. package/dist/web/assets/settings-tab-RCnvZ29H.js +0 -1
  237. package/dist/web/assets/sqlite-viewer-CEEm2W4C.js +0 -1
  238. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +0 -1
  239. package/dist/web/assets/tab-store-Bjh6bXFP.js +0 -1
  240. package/dist/web/assets/table-CQVQM2SB.js +0 -1
  241. package/dist/web/assets/tag-Q2dZiSPX.js +0 -1
  242. package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +0 -1
  243. /package/dist/web/assets/{api-client-BKIT_Qeg.js → api-client-BfBM3I7n.js} +0 -0
  244. /package/dist/web/assets/{array-DqLCdDFv.js → array-B9UHiPd-.js} +0 -0
  245. /package/dist/web/assets/{cytoscape.esm-CWPXKqbJ.js → cytoscape.esm-BW-DbntU.js} +0 -0
  246. /package/dist/web/assets/{defaultLocale-CrJzLgRD.js → defaultLocale-5eAKkKJC.js} +0 -0
  247. /package/dist/web/assets/{dist-Cep75xXf.js → dist-CSJdAyA9.js} +0 -0
  248. /package/dist/web/assets/{init-C0r9Gk5G.js → init-DlZdxViB.js} +0 -0
  249. /package/dist/web/assets/{isArrayLikeObject-CGBoxvCD.js → isArrayLikeObject-B_v2FtYn.js} +0 -0
  250. /package/dist/web/assets/{katex-DzXRfQ_m.js → katex-Bqvo_ZG0.js} +0 -0
  251. /package/dist/web/assets/{lib-BeaDXEkP.js → lib-BQ34Db2e.js} +0 -0
  252. /package/dist/web/assets/{math-y9zN1W-N.js → math-069Z4SuC.js} +0 -0
  253. /package/dist/web/assets/{path-DIKpVbHL.js → path-6uRLdFF7.js} +0 -0
  254. /package/dist/web/assets/{rough.esm-nHaDi0Kw.js → rough.esm-JX0wREDd.js} +0 -0
  255. /package/dist/web/assets/{src-Dw4QhedI.js → src-BqX54PbV.js} +0 -0
  256. /package/dist/web/assets/{utils-DMiycH3O.js → utils-BNytJOb1.js} +0 -0
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
4
4
  import { mkdirSync, existsSync } from "node:fs";
5
5
 
6
6
  const PPM_DIR = process.env.PPM_HOME || resolve(homedir(), ".ppm");
7
- const CURRENT_SCHEMA_VERSION = 8;
7
+ const CURRENT_SCHEMA_VERSION = 12;
8
8
 
9
9
  let db: Database | null = null;
10
10
  let dbProfile: string | null = null;
@@ -248,6 +248,95 @@ function runMigrations(database: Database): void {
248
248
  PRAGMA user_version = 8;
249
249
  `);
250
250
  }
251
+
252
+ if (current < 9) {
253
+ database.exec(`
254
+ CREATE TABLE IF NOT EXISTS session_pins (
255
+ session_id TEXT PRIMARY KEY,
256
+ pinned_at TEXT DEFAULT (datetime('now'))
257
+ );
258
+
259
+ PRAGMA user_version = 9;
260
+ `);
261
+ }
262
+
263
+ if (current < 10) {
264
+ database.exec(`
265
+ CREATE TABLE IF NOT EXISTS workspace_state (
266
+ project_name TEXT PRIMARY KEY,
267
+ layout_json TEXT NOT NULL,
268
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
269
+ );
270
+
271
+ PRAGMA user_version = 10;
272
+ `);
273
+ }
274
+
275
+ if (current < 11) {
276
+ try {
277
+ database.exec(`ALTER TABLE session_map ADD COLUMN project_path TEXT`);
278
+ } catch {
279
+ // Column may already exist
280
+ }
281
+ // Backfill project_path from projects table where project_name matches
282
+ database.exec(`
283
+ UPDATE session_map SET project_path = (
284
+ SELECT path FROM projects WHERE projects.name = session_map.project_name
285
+ ) WHERE project_path IS NULL AND project_name IS NOT NULL
286
+ `);
287
+ database.exec(`PRAGMA user_version = 11`);
288
+ }
289
+
290
+ if (current < 12) {
291
+ database.exec(`
292
+ CREATE TABLE IF NOT EXISTS extensions (
293
+ id TEXT PRIMARY KEY,
294
+ version TEXT NOT NULL,
295
+ display_name TEXT,
296
+ description TEXT,
297
+ icon TEXT,
298
+ enabled INTEGER DEFAULT 1,
299
+ manifest TEXT NOT NULL,
300
+ installed_at TEXT DEFAULT (datetime('now')),
301
+ updated_at TEXT DEFAULT (datetime('now'))
302
+ );
303
+
304
+ CREATE TABLE IF NOT EXISTS extension_storage (
305
+ ext_id TEXT NOT NULL,
306
+ scope TEXT NOT NULL,
307
+ key TEXT NOT NULL,
308
+ value TEXT,
309
+ PRIMARY KEY (ext_id, scope, key),
310
+ FOREIGN KEY (ext_id) REFERENCES extensions(id) ON DELETE CASCADE
311
+ );
312
+
313
+ PRAGMA user_version = 12;
314
+ `);
315
+ }
316
+ }
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // Workspace helpers
320
+ // ---------------------------------------------------------------------------
321
+
322
+ export interface WorkspaceRow {
323
+ project_name: string;
324
+ layout_json: string;
325
+ updated_at: string;
326
+ }
327
+
328
+ export function getWorkspace(projectName: string): WorkspaceRow | null {
329
+ return getDb().query(
330
+ "SELECT project_name, layout_json, updated_at FROM workspace_state WHERE project_name = ?",
331
+ ).get(projectName) as WorkspaceRow | null;
332
+ }
333
+
334
+ export function setWorkspace(projectName: string, layoutJson: string): string {
335
+ const now = new Date().toISOString();
336
+ getDb().query(
337
+ "INSERT INTO workspace_state (project_name, layout_json, updated_at) VALUES (?, ?, ?) ON CONFLICT(project_name) DO UPDATE SET layout_json = excluded.layout_json, updated_at = excluded.updated_at",
338
+ ).run(projectName, layoutJson, now);
339
+ return now;
251
340
  }
252
341
 
253
342
  // ---------------------------------------------------------------------------
@@ -318,10 +407,15 @@ export function getSessionMapping(ppmId: string): string | null {
318
407
  return row?.sdk_id ?? null;
319
408
  }
320
409
 
321
- export function setSessionMapping(ppmId: string, sdkId: string, projectName?: string): void {
410
+ export function getSessionProjectPath(ppmId: string): string | null {
411
+ const row = getDb().query("SELECT project_path FROM session_map WHERE ppm_id = ?").get(ppmId) as { project_path: string } | null;
412
+ return row?.project_path ?? null;
413
+ }
414
+
415
+ export function setSessionMapping(ppmId: string, sdkId: string, projectName?: string, projectPath?: string): void {
322
416
  getDb().query(
323
- "INSERT INTO session_map (ppm_id, sdk_id, project_name) VALUES (?, ?, ?) ON CONFLICT(ppm_id) DO UPDATE SET sdk_id = excluded.sdk_id, project_name = excluded.project_name",
324
- ).run(ppmId, sdkId, projectName ?? null);
417
+ "INSERT INTO session_map (ppm_id, sdk_id, project_name, project_path) VALUES (?, ?, ?, ?) ON CONFLICT(ppm_id) DO UPDATE SET sdk_id = excluded.sdk_id, project_name = COALESCE(excluded.project_name, session_map.project_name), project_path = COALESCE(excluded.project_path, session_map.project_path)",
418
+ ).run(ppmId, sdkId, projectName ?? null, projectPath ?? null);
325
419
  }
326
420
 
327
421
  export function getAllSessionMappings(): Record<string, string> {
@@ -358,6 +452,33 @@ export function getSessionTitles(sessionIds: string[]): Record<string, string> {
358
452
  return result;
359
453
  }
360
454
 
455
+ // ---------------------------------------------------------------------------
456
+ // Session pin helpers
457
+ // ---------------------------------------------------------------------------
458
+
459
+ export function pinSession(sessionId: string): void {
460
+ getDb().query(
461
+ "INSERT INTO session_pins (session_id, pinned_at) VALUES (?, datetime('now')) ON CONFLICT(session_id) DO UPDATE SET pinned_at = datetime('now')",
462
+ ).run(sessionId);
463
+ }
464
+
465
+ export function unpinSession(sessionId: string): void {
466
+ getDb().query("DELETE FROM session_pins WHERE session_id = ?").run(sessionId);
467
+ }
468
+
469
+ export function getPinnedSessionIds(): Set<string> {
470
+ const rows = getDb().query("SELECT session_id FROM session_pins ORDER BY pinned_at DESC").all() as { session_id: string }[];
471
+ return new Set(rows.map((r) => r.session_id));
472
+ }
473
+
474
+ export function deleteSessionMapping(ppmId: string): void {
475
+ getDb().query("DELETE FROM session_map WHERE ppm_id = ?").run(ppmId);
476
+ }
477
+
478
+ export function deleteSessionTitle(sessionId: string): void {
479
+ getDb().query("DELETE FROM session_titles WHERE session_id = ?").run(sessionId);
480
+ }
481
+
361
482
  // ---------------------------------------------------------------------------
362
483
  // Push subscription helpers
363
484
  // ---------------------------------------------------------------------------
@@ -717,5 +838,61 @@ export function incrementAccountRequests(id: string): void {
717
838
  getDb().query("UPDATE accounts SET total_requests = total_requests + 1 WHERE id = ?").run(id);
718
839
  }
719
840
 
841
+ // ---------------------------------------------------------------------------
842
+ // Extension helpers
843
+ // ---------------------------------------------------------------------------
844
+
845
+ import type { ExtensionRow, ExtensionStorageRow } from "../types/extension.ts";
846
+
847
+ export function getExtensions(): ExtensionRow[] {
848
+ return getDb().query("SELECT * FROM extensions ORDER BY display_name, id").all() as ExtensionRow[];
849
+ }
850
+
851
+ export function getExtensionById(id: string): ExtensionRow | null {
852
+ return getDb().query("SELECT * FROM extensions WHERE id = ?").get(id) as ExtensionRow | null;
853
+ }
854
+
855
+ export function insertExtension(row: Omit<ExtensionRow, "installed_at" | "updated_at">): void {
856
+ getDb().query(
857
+ `INSERT INTO extensions (id, version, display_name, description, icon, enabled, manifest)
858
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
859
+ ).run(row.id, row.version, row.display_name, row.description, row.icon, row.enabled, row.manifest);
860
+ }
861
+
862
+ export function updateExtension(id: string, updates: Partial<Pick<ExtensionRow, "version" | "display_name" | "description" | "icon" | "enabled" | "manifest">>): void {
863
+ const sets: string[] = [];
864
+ const vals: unknown[] = [];
865
+ if (updates.version !== undefined) { sets.push("version = ?"); vals.push(updates.version); }
866
+ if (updates.display_name !== undefined) { sets.push("display_name = ?"); vals.push(updates.display_name); }
867
+ if (updates.description !== undefined) { sets.push("description = ?"); vals.push(updates.description); }
868
+ if (updates.icon !== undefined) { sets.push("icon = ?"); vals.push(updates.icon); }
869
+ if (updates.enabled !== undefined) { sets.push("enabled = ?"); vals.push(updates.enabled); }
870
+ if (updates.manifest !== undefined) { sets.push("manifest = ?"); vals.push(updates.manifest); }
871
+ if (sets.length === 0) return;
872
+ sets.push("updated_at = datetime('now')");
873
+ vals.push(id);
874
+ getDb().query(`UPDATE extensions SET ${sets.join(", ")} WHERE id = ?`).run(...(vals as SQLQueryBindings[]));
875
+ }
876
+
877
+ export function deleteExtension(id: string): void {
878
+ getDb().query("DELETE FROM extensions WHERE id = ?").run(id);
879
+ }
880
+
881
+ export function getExtensionStorage(extId: string, scope: string): ExtensionStorageRow[] {
882
+ return getDb().query("SELECT * FROM extension_storage WHERE ext_id = ? AND scope = ?").all(extId, scope) as ExtensionStorageRow[];
883
+ }
884
+
885
+ export function setExtensionStorageValue(extId: string, scope: string, key: string, value: string | null): void {
886
+ getDb().query(
887
+ `INSERT INTO extension_storage (ext_id, scope, key, value)
888
+ VALUES (?, ?, ?, ?)
889
+ ON CONFLICT(ext_id, scope, key) DO UPDATE SET value = excluded.value`,
890
+ ).run(extId, scope, key, value);
891
+ }
892
+
893
+ export function deleteExtensionStorage(extId: string): void {
894
+ getDb().query("DELETE FROM extension_storage WHERE ext_id = ?").run(extId);
895
+ }
896
+
720
897
  // Auto-close on process exit
721
898
  process.on("beforeExit", closeDb);
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Extension Host Worker — runs inside a Bun Worker thread.
3
+ * Loads, activates, and deactivates extensions in isolation.
4
+ * Communicates with the main process via typed RPC (postMessage).
5
+ */
6
+ import { RpcChannel } from "./extension-rpc.ts";
7
+ import { createVscodeCompat } from "@ppm/vscode-compat";
8
+ import type { WindowService } from "@ppm/vscode-compat/src/window.ts";
9
+ import type { CommandService } from "@ppm/vscode-compat/src/commands.ts";
10
+ import type { Disposable, RpcMessage } from "../types/extension.ts";
11
+
12
+ // Active extension instances: id → { module, context, deactivate, services }
13
+ const activeExtensions = new Map<string, {
14
+ deactivate?: () => void | Promise<void>;
15
+ context: { subscriptions: Disposable[] };
16
+ window?: WindowService;
17
+ commands?: CommandService;
18
+ }>();
19
+
20
+ const rpc = new RpcChannel((msg) => postMessage(msg));
21
+
22
+ // Listen for messages from main process
23
+ declare const self: Worker;
24
+ self.addEventListener("message", (event: MessageEvent<RpcMessage>) => {
25
+ rpc.handleMessage(event.data);
26
+ });
27
+
28
+ // --- RPC handlers ---
29
+
30
+ rpc.onRequest("ext:activate", async (params) => {
31
+ const [extId, entryPath, extensionPath, storedState, baseUrl] = params as [string, string, string, Record<string, Record<string, string | null>>?, string?];
32
+ if (activeExtensions.has(extId)) return { ok: true, already: true };
33
+
34
+ // Expose server base URL so extensions can use fetch() with absolute URLs
35
+ if (baseUrl) (globalThis as any).__PPM_BASE_URL__ = baseUrl;
36
+
37
+ // Create RpcClient adapter for vscode-compat (Worker's RPC → vscode-compat interface)
38
+ const rpcClient = {
39
+ request: <T = unknown>(method: string, ...p: unknown[]) => rpc.sendRequest<T>(method, ...p),
40
+ notify: (event: string, data: unknown) => rpc.sendEvent(event, data),
41
+ };
42
+
43
+ // Create vscode-compat API scoped to this extension
44
+ const api = createVscodeCompat({
45
+ extensionId: extId,
46
+ extensionPath,
47
+ storagePath: `${extensionPath}/.storage`,
48
+ rpc: rpcClient,
49
+ storedState: storedState as { global?: Record<string, string | null>; workspace?: Record<string, string | null> },
50
+ });
51
+
52
+ const context = api._createContext();
53
+
54
+ try {
55
+ const mod = await import(entryPath);
56
+ const activateFn = mod.activate || mod.default?.activate;
57
+ if (typeof activateFn === "function") {
58
+ // Activation timeout: 10s max to prevent hanging extensions
59
+ const activatePromise = Promise.resolve(activateFn(context, api));
60
+ const timeoutPromise = new Promise((_, reject) =>
61
+ setTimeout(() => reject(new Error(`Activation timeout (10s) for ${extId}`)), 10_000),
62
+ );
63
+ await Promise.race([activatePromise, timeoutPromise]);
64
+ }
65
+ activeExtensions.set(extId, {
66
+ deactivate: mod.deactivate || mod.default?.deactivate,
67
+ context,
68
+ window: api.window as WindowService,
69
+ commands: api.commands as CommandService,
70
+ });
71
+ return { ok: true };
72
+ } catch (e) {
73
+ const msg = e instanceof Error ? e.message : String(e);
74
+ console.error(`[ExtHost] Failed to activate ${extId}:`, msg);
75
+ return { ok: false, error: msg };
76
+ }
77
+ });
78
+
79
+ rpc.onRequest("ext:deactivate", async (params) => {
80
+ const [extId] = params as [string];
81
+ const ext = activeExtensions.get(extId);
82
+ if (!ext) return { ok: true, already: true };
83
+
84
+ try {
85
+ if (typeof ext.deactivate === "function") {
86
+ await ext.deactivate();
87
+ }
88
+ // Dispose all subscriptions
89
+ for (const sub of ext.context.subscriptions) {
90
+ try { (sub as Disposable).dispose(); } catch {}
91
+ }
92
+ activeExtensions.delete(extId);
93
+ return { ok: true };
94
+ } catch (e) {
95
+ const msg = e instanceof Error ? e.message : String(e);
96
+ console.error(`[ExtHost] Failed to deactivate ${extId}:`, msg);
97
+ activeExtensions.delete(extId);
98
+ return { ok: false, error: msg };
99
+ }
100
+ });
101
+
102
+ rpc.onRequest("ext:command:execute", async (params) => {
103
+ const [command, ...args] = params as [string, ...unknown[]];
104
+ for (const [, ext] of activeExtensions) {
105
+ if (ext.commands) {
106
+ try {
107
+ const result = await (ext.commands as any).executeCommand(command, ...args);
108
+ return { ok: true, result };
109
+ } catch {
110
+ // Command not found in this extension, try next
111
+ }
112
+ }
113
+ }
114
+ return { ok: false, error: `Command not found: ${command}` };
115
+ });
116
+
117
+ // Deliver webview messages from browser → extension's onDidReceiveMessage
118
+ rpc.onRequest("ext:webview:message", async (params) => {
119
+ const [panelId, message] = params as [string, unknown];
120
+ for (const [, ext] of activeExtensions) {
121
+ if (!ext.window) continue;
122
+ try {
123
+ if ((ext.window as any)._deliverWebviewMessage(panelId, message)) {
124
+ return { ok: true };
125
+ }
126
+ } catch (e) {
127
+ console.error(`[ExtHost] webview:message error (${panelId}):`, e);
128
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
129
+ }
130
+ }
131
+ return { ok: false, error: `No handler for panel ${panelId}` };
132
+ });
133
+
134
+ // Handle tree:expand — get children for a tree node
135
+ rpc.onRequest("ext:tree:expand", async (params) => {
136
+ const [viewId, itemId] = params as [string, string | undefined];
137
+ for (const [, ext] of activeExtensions) {
138
+ if (ext.window) {
139
+ try {
140
+ const items = await (ext.window as any)._getTreeChildren(viewId, itemId);
141
+ if (items.length > 0) return { ok: true, items };
142
+ } catch (e) {
143
+ console.error(`[ExtHost] tree:expand error (${viewId}):`, e);
144
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
145
+ }
146
+ }
147
+ }
148
+ return { ok: true, items: [] };
149
+ });
150
+
151
+ rpc.onRequest("ext:list-active", () => {
152
+ return [...activeExtensions.keys()];
153
+ });
154
+
155
+ rpc.onRequest("ext:ping", () => "pong");
156
+
157
+ // ExtensionContext is now created by @ppm/vscode-compat's createVscodeCompat()._createContext()
158
+
159
+ // Notify main process that worker is ready
160
+ rpc.sendEvent("worker:ready", {});
@@ -0,0 +1,112 @@
1
+ import { resolve } from "node:path";
2
+ import { existsSync, mkdirSync, writeFileSync, rmSync, symlinkSync } from "node:fs";
3
+ import type { ExtensionManifest } from "../types/extension.ts";
4
+ import { getExtensionById, insertExtension, updateExtension, deleteExtension, deleteExtensionStorage } from "./db.service.ts";
5
+ import { readManifestAt } from "./extension-manifest.ts";
6
+
7
+ const INSTALL_TIMEOUT = 60_000;
8
+ const NPM_PACKAGE_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*(@[^@]+)?$/;
9
+
10
+ /** Ensure ~/.ppm/extensions/ dir + isolated package.json exist */
11
+ export function ensureExtensionsDir(extensionsDir: string): void {
12
+ if (!existsSync(extensionsDir)) {
13
+ mkdirSync(extensionsDir, { recursive: true });
14
+ }
15
+ const pkgJsonPath = resolve(extensionsDir, "package.json");
16
+ if (!existsSync(pkgJsonPath)) {
17
+ writeFileSync(pkgJsonPath, JSON.stringify({ name: "ppm-extensions", private: true, dependencies: {} }, null, 2));
18
+ }
19
+ }
20
+
21
+ /** Install an npm package into the extensions directory and persist to DB */
22
+ export async function installExtension(name: string, extensionsDir: string): Promise<ExtensionManifest> {
23
+ if (!NPM_PACKAGE_RE.test(name)) throw new Error(`Invalid package name: ${name}`);
24
+ ensureExtensionsDir(extensionsDir);
25
+
26
+ const proc = Bun.spawn(["bun", "add", name], {
27
+ cwd: extensionsDir,
28
+ stdout: "pipe",
29
+ stderr: "pipe",
30
+ });
31
+
32
+ const timeout = setTimeout(() => proc.kill(), INSTALL_TIMEOUT);
33
+ const exitCode = await proc.exited;
34
+ clearTimeout(timeout);
35
+
36
+ if (exitCode !== 0) {
37
+ const stderr = await new Response(proc.stderr).text();
38
+ throw new Error(`Install failed (exit ${exitCode}): ${stderr.slice(0, 500)}`);
39
+ }
40
+
41
+ const pkgDir = resolve(extensionsDir, "node_modules", name);
42
+ const manifest = readManifestAt(pkgDir);
43
+ if (!manifest) throw new Error(`Installed ${name} but no valid manifest found`);
44
+
45
+ upsertExtensionInDb(manifest);
46
+ console.log(`[ExtService] Installed ${manifest.id}@${manifest.version}`);
47
+ return manifest;
48
+ }
49
+
50
+ /** Remove an extension from disk + DB */
51
+ export async function removeExtension(id: string, extensionsDir: string): Promise<void> {
52
+ try {
53
+ const proc = Bun.spawn(["bun", "remove", id], {
54
+ cwd: extensionsDir,
55
+ stdout: "pipe",
56
+ stderr: "pipe",
57
+ });
58
+ await proc.exited;
59
+ } catch (e) {
60
+ console.error(`[ExtService] npm remove ${id} failed (DB record still removed):`, e);
61
+ }
62
+
63
+ deleteExtensionStorage(id);
64
+ deleteExtension(id);
65
+ console.log(`[ExtService] Removed ${id}`);
66
+ }
67
+
68
+ /** Symlink a local extension path for development */
69
+ export function devLinkExtension(localPath: string, extensionsDir: string): ExtensionManifest {
70
+ const absPath = resolve(localPath);
71
+ const manifest = readManifestAt(absPath);
72
+ if (!manifest) throw new Error(`No valid package.json at ${absPath}`);
73
+
74
+ ensureExtensionsDir(extensionsDir);
75
+ const nodeModules = resolve(extensionsDir, "node_modules");
76
+ if (!existsSync(nodeModules)) mkdirSync(nodeModules, { recursive: true });
77
+
78
+ const targetDir = resolve(nodeModules, manifest.id);
79
+ const parentDir = resolve(targetDir, "..");
80
+ if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });
81
+
82
+ if (existsSync(targetDir)) rmSync(targetDir, { recursive: true, force: true });
83
+ symlinkSync(absPath, targetDir, "dir");
84
+
85
+ upsertExtensionInDb(manifest);
86
+ console.log(`[ExtService] Dev-linked ${manifest.id} → ${absPath}`);
87
+ return manifest;
88
+ }
89
+
90
+ /** Insert or update extension record in DB */
91
+ function upsertExtensionInDb(manifest: ExtensionManifest): void {
92
+ const existing = getExtensionById(manifest.id);
93
+ if (existing) {
94
+ updateExtension(manifest.id, {
95
+ version: manifest.version,
96
+ display_name: manifest.displayName ?? null,
97
+ description: manifest.description ?? null,
98
+ icon: manifest.icon ?? null,
99
+ manifest: JSON.stringify(manifest),
100
+ });
101
+ } else {
102
+ insertExtension({
103
+ id: manifest.id,
104
+ version: manifest.version,
105
+ display_name: manifest.displayName ?? null,
106
+ description: manifest.description ?? null,
107
+ icon: manifest.icon ?? null,
108
+ enabled: 1,
109
+ manifest: JSON.stringify(manifest),
110
+ });
111
+ }
112
+ }
@@ -0,0 +1,65 @@
1
+ import { resolve } from "node:path";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { readdir } from "node:fs/promises";
4
+ import type { ExtensionManifest } from "../types/extension.ts";
5
+
6
+ /** Parse a package.json object into an ExtensionManifest (or null if invalid) */
7
+ export function parseManifest(pkg: Record<string, unknown>): ExtensionManifest | null {
8
+ const name = pkg.name as string | undefined;
9
+ const version = pkg.version as string | undefined;
10
+ const main = pkg.main as string | undefined;
11
+ if (!name || !version || !main) return null;
12
+
13
+ const ppmField = pkg.ppm as Record<string, unknown> | undefined;
14
+ return {
15
+ id: name,
16
+ version,
17
+ main,
18
+ displayName: (ppmField?.displayName as string) || (pkg.displayName as string) || name,
19
+ description: pkg.description as string | undefined,
20
+ icon: (ppmField?.icon as string) || undefined,
21
+ engines: pkg.engines as ExtensionManifest["engines"],
22
+ activationEvents: pkg.activationEvents as string[] | undefined,
23
+ contributes: pkg.contributes as ExtensionManifest["contributes"],
24
+ ppm: ppmField as ExtensionManifest["ppm"],
25
+ permissions: pkg.permissions as string[] | undefined,
26
+ };
27
+ }
28
+
29
+ /** Read and parse manifest from a directory containing package.json */
30
+ export function readManifestAt(dir: string): ExtensionManifest | null {
31
+ const pkgPath = resolve(dir, "package.json");
32
+ if (!existsSync(pkgPath)) return null;
33
+ try {
34
+ const raw = JSON.parse(readFileSync(pkgPath, "utf-8"));
35
+ return parseManifest(raw);
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /** Scan extensions directory for all valid manifests */
42
+ export async function discoverManifests(extensionsDir: string): Promise<ExtensionManifest[]> {
43
+ const manifests: ExtensionManifest[] = [];
44
+ if (!existsSync(extensionsDir)) return manifests;
45
+
46
+ const entries = await readdir(extensionsDir, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
49
+ if (entry.name === "node_modules" || entry.name === "package.json") continue;
50
+
51
+ // Handle scoped packages (@scope/name)
52
+ const entryPath = resolve(extensionsDir, entry.name);
53
+ if (entry.name.startsWith("@")) {
54
+ const scopedEntries = await readdir(entryPath, { withFileTypes: true });
55
+ for (const scoped of scopedEntries) {
56
+ const manifest = readManifestAt(resolve(entryPath, scoped.name));
57
+ if (manifest) manifests.push(manifest);
58
+ }
59
+ } else {
60
+ const manifest = readManifestAt(entryPath);
61
+ if (manifest) manifests.push(manifest);
62
+ }
63
+ }
64
+ return manifests;
65
+ }