@hienlh/ppm 0.8.86 → 0.8.88

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 (237) hide show
  1. package/CHANGELOG.md +216 -4
  2. package/bun.lock +5 -0
  3. package/dist/web/assets/{_basePickBy-5eBmZ_lt.js → _basePickBy-3Xe18azI.js} +1 -1
  4. package/dist/web/assets/{_baseUniq-DimLlN0y.js → _baseUniq-Yy35llnn.js} +1 -1
  5. package/dist/web/assets/api-settings-Dh4oFOpX.js +1 -0
  6. package/dist/web/assets/{arc-D4SasZrA.js → arc-B9n1Gvb5.js} +1 -1
  7. package/dist/web/assets/architecture-PBZL5I3N-CFzkFKEL.js +1 -0
  8. package/dist/web/assets/{architectureDiagram-2XIMDMQ5-nv0WbM7d.js → architectureDiagram-2XIMDMQ5-DqAZP_F6.js} +1 -1
  9. package/dist/web/assets/arrow-up--LjUXLEt.js +1 -0
  10. package/dist/web/assets/{blockDiagram-WCTKOSBZ-C1XvYrb8.js → blockDiagram-WCTKOSBZ-h3cDF2vI.js} +1 -1
  11. package/dist/web/assets/browser-tab-DJLH0eDY.js +1 -0
  12. package/dist/web/assets/{c4Diagram-IC4MRINW-CygDrbWJ.js → c4Diagram-IC4MRINW--pF1r5lr.js} +1 -1
  13. package/dist/web/assets/channel-C2fMafck.js +1 -0
  14. package/dist/web/assets/chat-tab-C8HFXqGS.js +8 -0
  15. package/dist/web/assets/chevron-right-CHnjJt4E.js +1 -0
  16. package/dist/web/assets/{chunk-4BX2VUAB-C2FDgsgT.js → chunk-4BX2VUAB-C3aZvW7B.js} +1 -1
  17. package/dist/web/assets/{chunk-55IACEB6-jF4w6cat.js → chunk-55IACEB6-D5cABeB9.js} +1 -1
  18. package/dist/web/assets/{chunk-7E7YKBS2-BVCECZFi.js → chunk-7E7YKBS2-CkFGv6Zs.js} +1 -1
  19. package/dist/web/assets/{chunk-7R4GIKGN-DXTbeu5d.js → chunk-7R4GIKGN-Dvbyu4Zw.js} +2 -2
  20. package/dist/web/assets/{chunk-C72U2L5F-BaZqOsTs.js → chunk-C72U2L5F-CtqKiH4q.js} +1 -1
  21. package/dist/web/assets/{chunk-EGIJ26TM-Bky2tcH7.js → chunk-EGIJ26TM-Cpr87sBR.js} +1 -1
  22. package/dist/web/assets/{chunk-FMBD7UC4-Cp4BK9A8.js → chunk-FMBD7UC4-D23YVTOU.js} +1 -1
  23. package/dist/web/assets/{chunk-GEFDOKGD-BosFEH7G.js → chunk-GEFDOKGD-tDjHsAUs.js} +1 -1
  24. package/dist/web/assets/chunk-GLR3WWYH-DBdWQ3zy.js +2 -0
  25. package/dist/web/assets/chunk-HHEYEP7N-BBw_z0fW.js +1 -0
  26. package/dist/web/assets/{chunk-JSJVCQXG-H5Gbjsbr.js → chunk-JSJVCQXG-BBmymCjA.js} +1 -1
  27. package/dist/web/assets/{chunk-KX2RTZJC-CWerSUwS.js → chunk-KX2RTZJC-DP36BDiU.js} +1 -1
  28. package/dist/web/assets/{chunk-KYZI473N-FvwP7jUy.js → chunk-KYZI473N-Djw13C-3.js} +1 -1
  29. package/dist/web/assets/{chunk-L3YUKLVL-D1PI_ORP.js → chunk-L3YUKLVL-HG_eMj_C.js} +1 -1
  30. package/dist/web/assets/{chunk-MX3YWQON-C7Vzk_AI.js → chunk-MX3YWQON-C2UEioMs.js} +1 -1
  31. package/dist/web/assets/{chunk-NQ4KR5QH-BceYBGYX.js → chunk-NQ4KR5QH-DXUTQ-BL.js} +1 -1
  32. package/dist/web/assets/{chunk-O4XLMI2P-WPtzgxql.js → chunk-O4XLMI2P-BsUWb9d0.js} +1 -1
  33. package/dist/web/assets/{chunk-OZEHJAEY-DlHXDeLY.js → chunk-OZEHJAEY-rG0P22U9.js} +1 -1
  34. package/dist/web/assets/{chunk-PQ6SQG4A-Ci_Prygb.js → chunk-PQ6SQG4A-DX0xW7kO.js} +1 -1
  35. package/dist/web/assets/{chunk-PU5JKC2W-CO0zMN-z.js → chunk-PU5JKC2W-C7Gry6md.js} +1 -1
  36. package/dist/web/assets/chunk-QZHKN3VN-DFKFM_C1.js +1 -0
  37. package/dist/web/assets/{chunk-R5LLSJPH-IAEEzfpM.js → chunk-R5LLSJPH-CMY0PkRK.js} +1 -1
  38. package/dist/web/assets/{chunk-WL4C6EOR-BLXalOgc.js → chunk-WL4C6EOR-CXuQvlyu.js} +1 -1
  39. package/dist/web/assets/{chunk-XIRO2GV7-Dx1Ri_p2.js → chunk-XIRO2GV7-DRJEb7Zb.js} +1 -1
  40. package/dist/web/assets/{chunk-XPW4576I-m9pPGKn7.js → chunk-XPW4576I-BPEX8KhL.js} +1 -1
  41. package/dist/web/assets/{chunk-XZSTWKYB-B_08ExbI.js → chunk-XZSTWKYB-Cb0iqycX.js} +1 -1
  42. package/dist/web/assets/{chunk-YBOYWFTD-DqSOVcYe.js → chunk-YBOYWFTD-av5aeHLq.js} +1 -1
  43. package/dist/web/assets/classDiagram-VBA2DB6C-Dp4Kk3Yb.js +1 -0
  44. package/dist/web/assets/classDiagram-v2-RAHNMMFH-D8IvcV_B.js +1 -0
  45. package/dist/web/assets/clone-B2hUek6n.js +1 -0
  46. package/dist/web/assets/code-editor-CaGdx-lS.js +2 -0
  47. package/dist/web/assets/{cose-bilkent-S5V4N54A-DlL82QHu.js → cose-bilkent-S5V4N54A-qudEiMCT.js} +1 -1
  48. package/dist/web/assets/csv-preview-DUbHtTAS.js +10 -0
  49. package/dist/web/assets/{dagre-BmVoh2At.js → dagre-BFcnKyBF.js} +1 -1
  50. package/dist/web/assets/{dagre-KLK3FWXG-sDrRW9MQ.js → dagre-KLK3FWXG-C3O-MTLf.js} +1 -1
  51. package/dist/web/assets/database-viewer-i4Ddk6mO.js +1 -0
  52. package/dist/web/assets/{diagram-E7M64L7V-ChnAhgni.js → diagram-E7M64L7V-DxPjK7_c.js} +1 -1
  53. package/dist/web/assets/{diagram-IFDJBPK2-DW1J1uJd.js → diagram-IFDJBPK2-sqTog_XV.js} +1 -1
  54. package/dist/web/assets/{diagram-P4PSJMXO-CQ32hyG_.js → diagram-P4PSJMXO-hzmp0GHK.js} +1 -1
  55. package/dist/web/assets/diff-viewer-DQDS7yjv.js +4 -0
  56. package/dist/web/assets/dist-CALwEtco.js +41 -0
  57. package/dist/web/assets/dist-DGDPTxs1.js +13 -0
  58. package/dist/web/assets/{erDiagram-INFDFZHY-6CHo6nOw.js → erDiagram-INFDFZHY-DLeYhAAT.js} +1 -1
  59. package/dist/web/assets/{flowDiagram-PKNHOUZH-DroDiNT0.js → flowDiagram-PKNHOUZH-CRxlE9Sr.js} +1 -1
  60. package/dist/web/assets/{ganttDiagram-A5KZAMGK-DP0QBh8w.js → ganttDiagram-A5KZAMGK-BdjmoMLS.js} +1 -1
  61. package/dist/web/assets/git-graph-DUs-TN1u.js +1 -0
  62. package/dist/web/assets/gitGraph-HDMCJU4V-CNlas3Rz.js +1 -0
  63. package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-DvU3JGZn.js → gitGraphDiagram-K3NZZRJ6-BeHSX7kk.js} +1 -1
  64. package/dist/web/assets/{graphlib-CQBb2thr.js → graphlib-Duh_bWLa.js} +1 -1
  65. package/dist/web/assets/index-DhtLEnPD.css +2 -0
  66. package/dist/web/assets/index-Dm6RN1A1.js +37 -0
  67. package/dist/web/assets/info-3K5VOQVL-BDzTLc11.js +1 -0
  68. package/dist/web/assets/infoDiagram-LFFYTUFH-ZZmpgc6t.js +2 -0
  69. package/dist/web/assets/{isEmpty-B4kqZBtn.js → isEmpty-B9L-Ge-H.js} +1 -1
  70. package/dist/web/assets/{ishikawaDiagram-PHBUUO56-46yibrV5.js → ishikawaDiagram-PHBUUO56-Cu0Rt1Ok.js} +1 -1
  71. package/dist/web/assets/{journeyDiagram-4ABVD52K-BcmRwjK-.js → journeyDiagram-4ABVD52K-CgDI-UG4.js} +1 -1
  72. package/dist/web/assets/{kanban-definition-K7BYSVSG-B619K53y.js → kanban-definition-K7BYSVSG-h4g10UHL.js} +1 -1
  73. package/dist/web/assets/keybindings-store-qVLDZz97.js +1 -0
  74. package/dist/web/assets/lib-BeaDXEkP.js +4 -0
  75. package/dist/web/assets/{line-1gcO63_w.js → line-B75-Rx70.js} +1 -1
  76. package/dist/web/assets/{linear-DfRqDoVd.js → linear-Bcjv9FQt.js} +1 -1
  77. package/dist/web/assets/markdown-renderer-L1NgC2Rw.js +69 -0
  78. package/dist/web/assets/{mermaid-parser.core-XtjZQOeM.js → mermaid-parser.core-8u2leTXI.js} +2 -2
  79. package/dist/web/assets/{mindmap-definition-YRQLILUH-CifOFo_q.js → mindmap-definition-YRQLILUH-BaOBwb-W.js} +1 -1
  80. package/dist/web/assets/{ordinal-BJYw-iDX.js → ordinal-LFEjVtwQ.js} +1 -1
  81. package/dist/web/assets/packet-RMMSAZCW-IVa5F-go.js +1 -0
  82. package/dist/web/assets/pie-UPGHQEXC-CvXHKAzp.js +1 -0
  83. package/dist/web/assets/{pieDiagram-SKSYHLDU-BuHUh_fO.js → pieDiagram-SKSYHLDU-At5Kz0KK.js} +1 -1
  84. package/dist/web/assets/postgres-viewer-_uDispGW.js +1 -0
  85. package/dist/web/assets/{quadrantDiagram-337W2JSQ-Bau_hj6Z.js → quadrantDiagram-337W2JSQ-CdjGIDfw.js} +1 -1
  86. package/dist/web/assets/radar-KQ55EAFF-Z-Tr5wtS.js +1 -0
  87. package/dist/web/assets/react-dom-Bpkvzu3U.js +1 -0
  88. package/dist/web/assets/{requirementDiagram-Z7DCOOCP-Cq2b-uwp.js → requirementDiagram-Z7DCOOCP-B9F_Cx_p.js} +1 -1
  89. package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DrdGQxWQ.js → sankeyDiagram-WA2Y5GQK-RolPi8bU.js} +1 -1
  90. package/dist/web/assets/{sequenceDiagram-2WXFIKYE-qPxiTUcS.js → sequenceDiagram-2WXFIKYE-DM-tMAhx.js} +1 -1
  91. package/dist/web/assets/settings-tab-Bp4041i6.js +1 -0
  92. package/dist/web/assets/sqlite-viewer-GW-QCjHn.js +1 -0
  93. package/dist/web/assets/{stateDiagram-RAJIS63D-Dulj2oa8.js → stateDiagram-RAJIS63D-C4EMl6jf.js} +1 -1
  94. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-B-UjZch3.js +1 -0
  95. package/dist/web/assets/tab-store--SlERlDs.js +1 -0
  96. package/dist/web/assets/{terminal-tab-wKgpSPAT.js → terminal-tab-E4cWujj4.js} +2 -2
  97. package/dist/web/assets/{timeline-definition-YZTLITO2-BWyDnCYq.js → timeline-definition-YZTLITO2-A4PN_Efm.js} +1 -1
  98. package/dist/web/assets/treemap-KZPCXAKY-C9TYRE0k.js +1 -0
  99. package/dist/web/assets/use-monaco-theme-zABXAAla.js +11 -0
  100. package/dist/web/assets/{vennDiagram-LZ73GAT5-B9Iv2bNV.js → vennDiagram-LZ73GAT5-ywK7LMaH.js} +1 -1
  101. package/dist/web/assets/{xychartDiagram-JWTSCODW-ChXcMzBQ.js → xychartDiagram-JWTSCODW-DylHYNtJ.js} +1 -1
  102. package/dist/web/index.html +11 -11
  103. package/dist/web/sw.js +1 -1
  104. package/docs/code-standards.md +386 -6
  105. package/docs/codebase-summary.md +270 -98
  106. package/docs/design-guidelines.md +21 -0
  107. package/docs/project-changelog.md +150 -1
  108. package/docs/project-roadmap.md +41 -19
  109. package/docs/system-architecture.md +363 -15
  110. package/package.json +3 -2
  111. package/src/cli/commands/autostart.ts +1 -1
  112. package/src/cli/commands/restart.ts +9 -1
  113. package/src/cli/commands/status.ts +19 -0
  114. package/src/index.ts +2 -3
  115. package/src/providers/claude-agent-sdk.ts +316 -107
  116. package/src/providers/cli-provider-base.ts +238 -0
  117. package/src/providers/cursor-cli/cursor-event-mapper.ts +85 -0
  118. package/src/providers/cursor-cli/cursor-history.ts +207 -0
  119. package/src/providers/cursor-cli/cursor-provider.ts +146 -0
  120. package/src/providers/mock-provider.ts +7 -2
  121. package/src/providers/provider.interface.ts +1 -0
  122. package/src/providers/registry.ts +43 -4
  123. package/src/server/index.ts +44 -166
  124. package/src/server/routes/browser-preview.ts +159 -0
  125. package/src/server/routes/chat.ts +66 -6
  126. package/src/server/routes/mcp.ts +84 -0
  127. package/src/server/routes/project-scoped.ts +2 -0
  128. package/src/server/routes/proxy.ts +46 -53
  129. package/src/server/routes/settings.ts +14 -0
  130. package/src/server/routes/tunnel.ts +0 -32
  131. package/src/server/routes/workspace.ts +35 -0
  132. package/src/server/ws/chat.ts +302 -195
  133. package/src/services/account-selector.service.ts +16 -8
  134. package/src/services/account.service.ts +19 -13
  135. package/src/services/chat.service.ts +10 -15
  136. package/src/services/claude-usage.service.ts +48 -11
  137. package/src/services/cloud-ws.service.ts +227 -0
  138. package/src/services/cloud.service.ts +10 -6
  139. package/src/services/db.service.ts +119 -6
  140. package/src/services/mcp-config.service.ts +102 -0
  141. package/src/services/proxy.service.ts +4 -19
  142. package/src/services/supervisor.ts +285 -25
  143. package/src/types/api.ts +10 -2
  144. package/src/types/chat.ts +25 -2
  145. package/src/types/config.ts +33 -11
  146. package/src/types/mcp.ts +47 -0
  147. package/src/utils/ndjson-line-parser.ts +36 -0
  148. package/src/web/app.tsx +41 -35
  149. package/src/web/components/browser/browser-tab.tsx +106 -97
  150. package/src/web/components/chat/account-rotation-settings.tsx +163 -0
  151. package/src/web/components/chat/chat-history-bar.tsx +116 -31
  152. package/src/web/components/chat/chat-tab.tsx +31 -10
  153. package/src/web/components/chat/chat-welcome.tsx +148 -0
  154. package/src/web/components/chat/message-input.tsx +169 -16
  155. package/src/web/components/chat/message-list.tsx +27 -15
  156. package/src/web/components/chat/provider-selector.tsx +150 -0
  157. package/src/web/components/chat/session-picker.tsx +80 -31
  158. package/src/web/components/chat/usage-badge.tsx +11 -1
  159. package/src/web/components/editor/code-editor.tsx +36 -26
  160. package/src/web/components/editor/csv-preview.tsx +228 -0
  161. package/src/web/components/editor/editor-breadcrumb.tsx +216 -0
  162. package/src/web/components/editor/editor-toolbar.tsx +74 -0
  163. package/src/web/components/layout/command-palette.tsx +3 -1
  164. package/src/web/components/layout/editor-panel.tsx +162 -18
  165. package/src/web/components/layout/panel-layout.tsx +17 -1
  166. package/src/web/components/settings/ai-settings-section.tsx +196 -137
  167. package/src/web/components/settings/mcp-server-dialog.tsx +208 -0
  168. package/src/web/components/settings/mcp-settings-section.tsx +143 -0
  169. package/src/web/components/settings/proxy-settings-section.tsx +40 -42
  170. package/src/web/components/settings/settings-tab.tsx +5 -2
  171. package/src/web/components/shared/connection-lost-overlay.tsx +89 -0
  172. package/src/web/hooks/use-chat.ts +234 -207
  173. package/src/web/hooks/use-global-keybindings.ts +25 -2
  174. package/src/web/hooks/use-server-reload.ts +9 -0
  175. package/src/web/hooks/use-url-sync.ts +173 -21
  176. package/src/web/hooks/use-voice-input.ts +111 -0
  177. package/src/web/lib/api-mcp.ts +38 -0
  178. package/src/web/lib/csv-parser.ts +134 -0
  179. package/src/web/stores/connection-store.ts +39 -0
  180. package/src/web/stores/keybindings-store.ts +1 -0
  181. package/src/web/stores/panel-store.ts +73 -19
  182. package/src/web/stores/panel-utils.ts +145 -3
  183. package/dist/web/assets/api-settings-CFw-lh5k.js +0 -1
  184. package/dist/web/assets/architecture-PBZL5I3N-CJupe6q_.js +0 -1
  185. package/dist/web/assets/browser-tab-CmsL5eny.js +0 -1
  186. package/dist/web/assets/channel-DmKoFTd_.js +0 -1
  187. package/dist/web/assets/chat-tab-CFWsf13Z.js +0 -7
  188. package/dist/web/assets/chunk-GLR3WWYH-BnP-hOp6.js +0 -2
  189. package/dist/web/assets/chunk-HHEYEP7N-DKDPTPEZ.js +0 -1
  190. package/dist/web/assets/chunk-QZHKN3VN-C_wpI9wz.js +0 -1
  191. package/dist/web/assets/classDiagram-VBA2DB6C-B1T5uY-F.js +0 -1
  192. package/dist/web/assets/classDiagram-v2-RAHNMMFH-xs5vI3xC.js +0 -1
  193. package/dist/web/assets/clone-CijCFRT5.js +0 -1
  194. package/dist/web/assets/code-editor-H_dAh_fJ.js +0 -1
  195. package/dist/web/assets/database-viewer-DBzsgEJ8.js +0 -1
  196. package/dist/web/assets/diff-viewer-DzS-OnAR.js +0 -4
  197. package/dist/web/assets/dist-0Va_2L7G.js +0 -16
  198. package/dist/web/assets/dist-D9irYETY.js +0 -41
  199. package/dist/web/assets/git-graph-D3C7F8o3.js +0 -1
  200. package/dist/web/assets/gitGraph-HDMCJU4V-B0KvGQG8.js +0 -1
  201. package/dist/web/assets/index-CIkjfera.js +0 -31
  202. package/dist/web/assets/index-WKLuYsBY.css +0 -2
  203. package/dist/web/assets/info-3K5VOQVL-1uJ6_hCm.js +0 -1
  204. package/dist/web/assets/infoDiagram-LFFYTUFH-DLA5Q-3y.js +0 -2
  205. package/dist/web/assets/input-CGp1nFIg.js +0 -1
  206. package/dist/web/assets/keybindings-store-BdaoLwSo.js +0 -1
  207. package/dist/web/assets/markdown-renderer-DH49Zag7.js +0 -69
  208. package/dist/web/assets/packet-RMMSAZCW-34C4o9yj.js +0 -1
  209. package/dist/web/assets/pie-UPGHQEXC-D9ekKlh9.js +0 -1
  210. package/dist/web/assets/postgres-viewer-B9FYk8sD.js +0 -1
  211. package/dist/web/assets/radar-KQ55EAFF-DEuXOXSD.js +0 -1
  212. package/dist/web/assets/settings-store-DWXGVHsE.js +0 -2
  213. package/dist/web/assets/settings-tab-D-q8pd-5.js +0 -1
  214. package/dist/web/assets/sqlite-viewer-CDqcTePw.js +0 -1
  215. package/dist/web/assets/stateDiagram-v2-FVOUBMTO-CAkzLlhk.js +0 -1
  216. package/dist/web/assets/tab-store-BPeiymiH.js +0 -1
  217. package/dist/web/assets/treemap-KZPCXAKY-nc7a1Ia1.js +0 -1
  218. package/dist/web/assets/use-monaco-theme-CCBTQ0S3.js +0 -11
  219. package/src/services/port-tunnel.service.ts +0 -97
  220. /package/dist/web/assets/{api-client-DOElml5u.js → api-client-BKIT_Qeg.js} +0 -0
  221. /package/dist/web/assets/{array-CYkMkqnU.js → array-DqLCdDFv.js} +0 -0
  222. /package/dist/web/assets/{columns-2-ChOTgl3e.js → columns-2-DbesTfa7.js} +0 -0
  223. /package/dist/web/assets/{cytoscape.esm-HeHO0VhB.js → cytoscape.esm-CWPXKqbJ.js} +0 -0
  224. /package/dist/web/assets/{defaultLocale-Beh6XjaL.js → defaultLocale-CrJzLgRD.js} +0 -0
  225. /package/dist/web/assets/{dist-BUYzeuKe.js → dist-Cep75xXf.js} +0 -0
  226. /package/dist/web/assets/{init-Rr1s_RiX.js → init-C0r9Gk5G.js} +0 -0
  227. /package/dist/web/assets/{isArrayLikeObject-BB-mzMLb.js → isArrayLikeObject-CGBoxvCD.js} +0 -0
  228. /package/dist/web/assets/{katex-CKoArbIw.js → katex-DzXRfQ_m.js} +0 -0
  229. /package/dist/web/assets/{math-B7b0HgJF.js → math-y9zN1W-N.js} +0 -0
  230. /package/dist/web/assets/{path-BAQ3hXlG.js → path-DIKpVbHL.js} +0 -0
  231. /package/dist/web/assets/{preload-helper-DeiOTZKJ.js → preload-helper-Bf_JiD2A.js} +0 -0
  232. /package/dist/web/assets/{react-Dev-wu-s.js → react-SKk5z-bm.js} +0 -0
  233. /package/dist/web/assets/{rough.esm-Dwml_la6.js → rough.esm-nHaDi0Kw.js} +0 -0
  234. /package/dist/web/assets/{src-B_cC68fH.js → src-Dw4QhedI.js} +0 -0
  235. /package/dist/web/assets/{table-COiJDPRA.js → table-CQVQM2SB.js} +0 -0
  236. /package/dist/web/assets/{tag-LMq02LfE.js → tag-Q2dZiSPX.js} +0 -0
  237. /package/dist/web/assets/{utils-btZ8C8-R.js → utils-DMiycH3O.js} +0 -0
@@ -0,0 +1,143 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { Plus, Download, Trash2, Pencil, Server } from "lucide-react";
3
+ import { Button } from "@/components/ui/button";
4
+ import {
5
+ getMcpServers, deleteMcpServer, importMcpServers,
6
+ type McpServerEntry,
7
+ } from "@/lib/api-mcp";
8
+ import { McpServerDialog } from "./mcp-server-dialog";
9
+
10
+ export function McpSettingsSection() {
11
+ const [servers, setServers] = useState<McpServerEntry[]>([]);
12
+ const [loading, setLoading] = useState(true);
13
+ const [dialogOpen, setDialogOpen] = useState(false);
14
+ const [editingServer, setEditingServer] = useState<McpServerEntry | null>(null);
15
+ const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
16
+ const [importing, setImporting] = useState(false);
17
+ const [importMsg, setImportMsg] = useState<string | null>(null);
18
+
19
+ const fetchServers = useCallback(async () => {
20
+ try {
21
+ const data = await getMcpServers();
22
+ setServers(data);
23
+ } catch (e) {
24
+ console.error("Failed to load MCP servers:", e);
25
+ } finally {
26
+ setLoading(false);
27
+ }
28
+ }, []);
29
+
30
+ useEffect(() => { fetchServers(); }, [fetchServers]);
31
+
32
+ const handleDelete = async (name: string) => {
33
+ try {
34
+ await deleteMcpServer(name);
35
+ setDeleteConfirm(null);
36
+ fetchServers();
37
+ } catch (e) {
38
+ console.error("Failed to delete:", e);
39
+ }
40
+ };
41
+
42
+ const handleImport = async () => {
43
+ setImporting(true);
44
+ setImportMsg(null);
45
+ try {
46
+ const result = await importMcpServers();
47
+ setImportMsg(`Imported ${result.imported}, skipped ${result.skipped}`);
48
+ fetchServers();
49
+ } catch (e: any) {
50
+ setImportMsg(e.message || "Import failed");
51
+ } finally {
52
+ setImporting(false);
53
+ }
54
+ };
55
+
56
+ const handleDialogClose = (saved?: boolean) => {
57
+ setDialogOpen(false);
58
+ setEditingServer(null);
59
+ if (saved) fetchServers();
60
+ };
61
+
62
+ const openAdd = () => { setEditingServer(null); setDialogOpen(true); };
63
+ const openEdit = (s: McpServerEntry) => { setEditingServer(s); setDialogOpen(true); };
64
+
65
+ if (loading) {
66
+ return (
67
+ <div className="space-y-3">
68
+ {[1, 2, 3].map((i) => (
69
+ <div key={i} className="h-16 rounded-lg bg-muted animate-pulse" />
70
+ ))}
71
+ </div>
72
+ );
73
+ }
74
+
75
+ return (
76
+ <div className="space-y-3">
77
+ {servers.length === 0 ? (
78
+ <div className="text-center py-8 space-y-2">
79
+ <Server className="size-8 mx-auto text-muted-foreground" />
80
+ <p className="text-xs text-muted-foreground">
81
+ No MCP servers configured. Add one or import from Claude Code.
82
+ </p>
83
+ </div>
84
+ ) : (
85
+ <div className="space-y-2">
86
+ {servers.map((s) => (
87
+ <div key={s.name} className="rounded-lg border bg-card p-3 space-y-1.5">
88
+ <div className="flex items-center gap-2">
89
+ <span className="text-xs font-medium truncate flex-1">{s.name}</span>
90
+ <span className="text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground shrink-0">
91
+ {s.transport}
92
+ </span>
93
+ </div>
94
+ <p className="text-[11px] text-muted-foreground truncate">
95
+ {serverPreview(s)}
96
+ </p>
97
+ <div className="flex justify-end gap-1.5">
98
+ {deleteConfirm === s.name ? (
99
+ <>
100
+ <span className="text-[11px] text-destructive self-center mr-1">Delete?</span>
101
+ <Button variant="destructive" size="sm" className="h-8 min-w-[44px] text-xs cursor-pointer" onClick={() => handleDelete(s.name)}>Yes</Button>
102
+ <Button variant="outline" size="sm" className="h-8 min-w-[44px] text-xs cursor-pointer" onClick={() => setDeleteConfirm(null)}>No</Button>
103
+ </>
104
+ ) : (
105
+ <>
106
+ <Button variant="ghost" size="sm" className="h-7 text-xs gap-1 cursor-pointer" onClick={() => openEdit(s)}>
107
+ <Pencil className="size-3" /> Edit
108
+ </Button>
109
+ <Button variant="ghost" size="sm" className="h-7 text-xs gap-1 text-destructive hover:text-destructive cursor-pointer" onClick={() => setDeleteConfirm(s.name)}>
110
+ <Trash2 className="size-3" /> Delete
111
+ </Button>
112
+ </>
113
+ )}
114
+ </div>
115
+ </div>
116
+ ))}
117
+ </div>
118
+ )}
119
+
120
+ {/* Actions — thumb zone */}
121
+ <div className="space-y-2 pt-1">
122
+ <Button className="w-full h-10 text-xs gap-1.5 cursor-pointer" onClick={openAdd}>
123
+ <Plus className="size-3.5" /> Add MCP Server
124
+ </Button>
125
+ <Button variant="outline" className="w-full h-10 text-xs gap-1.5 cursor-pointer" onClick={handleImport} disabled={importing}>
126
+ <Download className="size-3.5" /> {importing ? "Importing..." : "Import from Claude Code"}
127
+ </Button>
128
+ {importMsg && <p className="text-[11px] text-muted-foreground text-center">{importMsg}</p>}
129
+ </div>
130
+
131
+ <McpServerDialog open={dialogOpen} onClose={handleDialogClose} editServer={editingServer} />
132
+ </div>
133
+ );
134
+ }
135
+
136
+ function serverPreview(s: McpServerEntry): string {
137
+ const c = s.config;
138
+ if (s.transport === "stdio" || !("url" in c)) {
139
+ const stdio = c as { command?: string; args?: string[] };
140
+ return [stdio.command, ...(stdio.args ?? [])].join(" ");
141
+ }
142
+ return (c as { url?: string }).url ?? "";
143
+ }
@@ -127,78 +127,76 @@ export function ProxySettingsSection() {
127
127
  <div className="space-y-2 rounded-md border p-3 bg-muted/30">
128
128
  <h4 className="text-[11px] font-medium">Connection Info</h4>
129
129
 
130
- {/* Base URL */}
130
+ {/* Local endpoint */}
131
131
  <div className="space-y-1">
132
- <Label className="text-[10px] text-muted-foreground">Base URL</Label>
132
+ <Label className="text-[10px] text-muted-foreground">Local Endpoint</Label>
133
133
  <div className="flex gap-1.5 items-center">
134
134
  <code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
135
- {hasTunnel && settings.tunnelUrl
136
- ? `${settings.tunnelUrl}/proxy`
137
- : `${window.location.origin}/proxy`}
135
+ {`${window.location.origin}/proxy/v1/messages`}
138
136
  </code>
139
137
  <Button
140
138
  variant="ghost"
141
139
  size="sm"
142
140
  className="h-6 px-1.5 cursor-pointer shrink-0"
143
- onClick={() => copyToClipboard(
144
- hasTunnel && settings.tunnelUrl
145
- ? `${settings.tunnelUrl}/proxy`
146
- : `${window.location.origin}/proxy`,
147
- "baseurl",
148
- )}
141
+ onClick={() => copyToClipboard(`${window.location.origin}/proxy/v1/messages`, "local")}
149
142
  >
150
- {copied === "baseurl" ? "Copied!" : <Copy className="size-3" />}
143
+ {copied === "local" ? "Copied!" : <Copy className="size-3" />}
151
144
  </Button>
152
145
  </div>
153
146
  </div>
154
147
 
148
+ {/* Tunnel endpoint */}
149
+ {hasTunnel && settings.proxyEndpoint && (
150
+ <div className="space-y-1">
151
+ <Label className="text-[10px] text-muted-foreground">Public Endpoint (Tunnel)</Label>
152
+ <div className="flex gap-1.5 items-center">
153
+ <code className="text-[10px] font-mono bg-muted px-1.5 py-0.5 rounded flex-1 truncate">
154
+ {settings.proxyEndpoint}
155
+ </code>
156
+ <Button
157
+ variant="ghost"
158
+ size="sm"
159
+ className="h-6 px-1.5 cursor-pointer shrink-0"
160
+ onClick={() => copyToClipboard(settings.proxyEndpoint!, "tunnel")}
161
+ >
162
+ {copied === "tunnel" ? "Copied!" : <Copy className="size-3" />}
163
+ </Button>
164
+ </div>
165
+ </div>
166
+ )}
167
+
155
168
  {!hasTunnel && (
156
169
  <p className="text-[10px] text-muted-foreground">
157
170
  Start a Cloudflare tunnel (Share) to get a public URL.
158
171
  </p>
159
172
  )}
160
173
 
161
- {/* Claude Code CLI usage */}
162
- <div className="space-y-1 pt-1">
163
- <Label className="text-[10px] text-muted-foreground">Claude Code CLI</Label>
164
- <div className="relative">
165
- <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
166
- {`ANTHROPIC_BASE_URL=${hasTunnel && settings.tunnelUrl ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"} \\
167
- ANTHROPIC_API_KEY=${settings.authKey} \\
168
- claude`}
169
- </pre>
170
- <Button
171
- variant="ghost"
172
- size="sm"
173
- className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
174
- onClick={() => copyToClipboard(
175
- `ANTHROPIC_BASE_URL=${hasTunnel && settings.tunnelUrl ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"} ANTHROPIC_API_KEY=${settings.authKey} claude`,
176
- "claude-cli",
177
- )}
178
- >
179
- {copied === "claude-cli" ? "Copied!" : <Copy className="size-2.5" />}
180
- </Button>
181
- </div>
182
- </div>
183
-
184
- {/* Generic env vars */}
174
+ {/* Usage example */}
185
175
  <div className="space-y-1 pt-1">
186
- <Label className="text-[10px] text-muted-foreground">Environment Variables</Label>
176
+ <Label className="text-[10px] text-muted-foreground">Usage Example</Label>
187
177
  <div className="relative">
188
178
  <pre className="text-[9px] font-mono bg-muted p-2 rounded overflow-x-auto whitespace-pre">
189
- {`export ANTHROPIC_BASE_URL=${hasTunnel && settings.tunnelUrl ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"}
190
- export ANTHROPIC_API_KEY=${settings.authKey}`}
179
+ {`# Set as base URL in your tool
180
+ ANTHROPIC_BASE_URL=${hasTunnel && settings.proxyEndpoint ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"}
181
+ ANTHROPIC_API_KEY=${settings.authKey}
182
+
183
+ # Or use curl
184
+ curl ${hasTunnel && settings.proxyEndpoint ? settings.proxyEndpoint : window.location.origin + "/proxy/v1/messages"} \\
185
+ -H "x-api-key: ${settings.authKey}" \\
186
+ -H "content-type: application/json" \\
187
+ -H "anthropic-version: 2023-06-01" \\
188
+ -d '{"model":"claude-sonnet-4-6","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'`}
191
189
  </pre>
192
190
  <Button
193
191
  variant="ghost"
194
192
  size="sm"
195
193
  className="absolute top-1 right-1 h-5 px-1 cursor-pointer"
196
194
  onClick={() => copyToClipboard(
197
- `export ANTHROPIC_BASE_URL=${hasTunnel && settings.tunnelUrl ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"}\nexport ANTHROPIC_API_KEY=${settings.authKey}`,
198
- "envvars",
195
+ `ANTHROPIC_BASE_URL=${hasTunnel ? settings.tunnelUrl + "/proxy" : window.location.origin + "/proxy"}\nANTHROPIC_API_KEY=${settings.authKey}`,
196
+ "example",
199
197
  )}
200
198
  >
201
- {copied === "envvars" ? "Copied!" : <Copy className="size-2.5" />}
199
+ {copied === "example" ? "Copied!" : <Copy className="size-2.5" />}
202
200
  </Button>
203
201
  </div>
204
202
  </div>
@@ -1,7 +1,7 @@
1
1
  import { useState, useCallback, useRef } from "react";
2
2
  import {
3
3
  Moon, Sun, Monitor, Bell, BellOff, Check, ChevronRight, ArrowLeft,
4
- Bot, BellRing, Keyboard, Globe,
4
+ Bot, BellRing, Keyboard, Globe, Plug,
5
5
  } from "lucide-react";
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { Input } from "@/components/ui/input";
@@ -13,6 +13,7 @@ import { AISettingsSection } from "./ai-settings-section";
13
13
  import { KeyboardShortcutsSection } from "./keyboard-shortcuts-section";
14
14
  import { TelegramSettingsSection } from "./telegram-settings-section";
15
15
  import { ProxySettingsSection } from "./proxy-settings-section";
16
+ import { McpSettingsSection } from "./mcp-settings-section";
16
17
  import { usePushNotification } from "@/hooks/use-push-notification";
17
18
 
18
19
  const THEME_OPTIONS: { value: Theme; label: string; icon: React.ElementType }[] = [
@@ -25,13 +26,14 @@ const pushSupported = "PushManager" in window && "serviceWorker" in navigator;
25
26
  const isIosNonPwa = /iPhone|iPad/.test(navigator.userAgent) &&
26
27
  !window.matchMedia("(display-mode: standalone)").matches;
27
28
 
28
- type SettingsCategory = "ai" | "notifications" | "proxy" | "shortcuts";
29
+ type SettingsCategory = "ai" | "notifications" | "proxy" | "shortcuts" | "mcp";
29
30
 
30
31
  const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; icon: React.ElementType }[] = [
31
32
  { value: "ai", label: "AI Provider", subtitle: "Model, execution mode, limits", icon: Bot },
32
33
  { value: "notifications", label: "Notifications", subtitle: "Push & Telegram alerts", icon: BellRing },
33
34
  { value: "proxy", label: "API Proxy", subtitle: "Expose accounts as Anthropic API", icon: Globe },
34
35
  { value: "shortcuts", label: "Keyboard Shortcuts", subtitle: "Customize key bindings", icon: Keyboard },
36
+ { value: "mcp", label: "MCP Servers", subtitle: "Model Context Protocol tools", icon: Plug },
35
37
  ];
36
38
 
37
39
  export function SettingsTab() {
@@ -84,6 +86,7 @@ export function SettingsTab() {
84
86
  {activeCategory === "notifications" && <NotificationsContent isSubscribed={isSubscribed} loading={loading} permission={permission} pushError={pushError} subscribe={subscribe} unsubscribe={unsubscribe} />}
85
87
  {activeCategory === "proxy" && <ProxySettingsSection />}
86
88
  {activeCategory === "shortcuts" && <KeyboardShortcutsSection />}
89
+ {activeCategory === "mcp" && <McpSettingsSection />}
87
90
  </div>
88
91
  </ScrollArea>
89
92
  </div>
@@ -0,0 +1,89 @@
1
+ import { WifiOff, ServerOff, RefreshCw } from "lucide-react";
2
+ import { useState } from "react";
3
+ import { useConnectionStore } from "@/stores/connection-store";
4
+
5
+ const CLOUD_URL = "https://ppm.hienle.tech";
6
+
7
+ function isTunnelDomain(): boolean {
8
+ return window.location.hostname.endsWith(".trycloudflare.com");
9
+ }
10
+
11
+ export function ConnectionLostOverlay() {
12
+ const showOverlay = useConnectionStore((s) => s.showOverlay);
13
+ const [retrying, setRetrying] = useState(false);
14
+
15
+ if (!showOverlay) return null;
16
+
17
+ const isTunnel = isTunnelDomain();
18
+
19
+ async function handleRetry() {
20
+ setRetrying(true);
21
+ try {
22
+ const res = await fetch("/api/health", { cache: "no-store" });
23
+ if (res.ok) {
24
+ useConnectionStore.getState().markUp();
25
+ if ("caches" in window) {
26
+ const keys = await caches.keys();
27
+ await Promise.all(keys.map((k) => caches.delete(k)));
28
+ }
29
+ window.location.reload();
30
+ return;
31
+ }
32
+ } catch {
33
+ // still down
34
+ }
35
+ setRetrying(false);
36
+ }
37
+
38
+ const Icon = isTunnel ? WifiOff : ServerOff;
39
+
40
+ return (
41
+ <div className="fixed inset-0 z-[200] bg-background/95 backdrop-blur-sm flex items-center justify-center p-4">
42
+ <div className="max-w-sm w-full text-center space-y-6">
43
+ <div className="flex justify-center">
44
+ <div className="rounded-full bg-destructive/10 p-4">
45
+ <Icon className="h-10 w-10 text-destructive" />
46
+ </div>
47
+ </div>
48
+
49
+ <div className="space-y-2">
50
+ <h2 className="text-xl font-semibold text-foreground">
51
+ {isTunnel ? "Connection Lost" : "Server Unreachable"}
52
+ </h2>
53
+ <p className="text-sm text-muted-foreground leading-relaxed">
54
+ {isTunnel
55
+ ? "The tunnel appears to have closed. The server may have restarted with a new URL."
56
+ : "Cannot connect to the PPM server. It may have stopped or is restarting."}
57
+ </p>
58
+ </div>
59
+
60
+ <div className="flex flex-col gap-3">
61
+ {isTunnel && (
62
+ <a
63
+ href={CLOUD_URL}
64
+ target="_blank"
65
+ rel="noopener noreferrer"
66
+ className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
67
+ >
68
+ Open PPM Cloud
69
+ </a>
70
+ )}
71
+ <button
72
+ onClick={handleRetry}
73
+ disabled={retrying}
74
+ className="inline-flex items-center justify-center gap-2 rounded-md border border-border px-4 py-2.5 text-sm font-medium text-foreground hover:bg-accent transition-colors disabled:opacity-50"
75
+ >
76
+ <RefreshCw className={`h-4 w-4 ${retrying ? "animate-spin" : ""}`} />
77
+ {retrying ? "Retrying…" : "Retry Connection"}
78
+ </button>
79
+ </div>
80
+
81
+ {!isTunnel && (
82
+ <p className="text-xs text-muted-foreground">
83
+ If the server was stopped, run <code className="bg-muted px-1 py-0.5 rounded text-[11px]">ppm start</code> to restart it.
84
+ </p>
85
+ )}
86
+ </div>
87
+ </div>
88
+ );
89
+ }