upfynai-code 3.0.4 → 3.2.0

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 (258) hide show
  1. package/README.md +69 -92
  2. package/bin/cli.js +191 -0
  3. package/dist/client/assets/AppContent-M14Au3SB.js +542 -0
  4. package/{client/dist/assets/BrowserPanel-0TLEl-IC.js → dist/client/assets/BrowserPanel-TFKm2NDJ.js} +2 -2
  5. package/dist/client/assets/DashboardPanel-C88HjsCh.js +1 -0
  6. package/dist/client/assets/FileTree-DvO1xnDE.js +1 -0
  7. package/{client/dist/assets/GitPanel-C_xFM-N2.js → dist/client/assets/GitPanel-D-slVlyy.js} +2 -2
  8. package/dist/client/assets/LoginModal-Chi4SYcr.js +21 -0
  9. package/{client/dist/assets/MarkdownPreview-CESjI261.js → dist/client/assets/MarkdownPreview-CuIix2u9.js} +1 -1
  10. package/dist/client/assets/MermaidBlock-Dq9uFv82.js +2 -0
  11. package/dist/client/assets/Onboarding-QYXx24dX.js +1 -0
  12. package/{client/dist/assets/PreviewPanel-CqCa92Tf.js → dist/client/assets/PreviewPanel-Dd8q-jo0.js} +1 -1
  13. package/dist/client/assets/SetupForm-CrspaUva.js +1 -0
  14. package/dist/client/assets/WorkflowsPanel-DIlYAdhB.js +1 -0
  15. package/dist/client/assets/index-CnNNzw9A.css +1 -0
  16. package/{client/dist/assets/index-HaY-3pK1.js → dist/client/assets/index-rUkK9FDP.js} +26 -26
  17. package/{client/dist/assets/vendor-codemirror-D2ALgpaX.js → dist/client/assets/vendor-codemirror-jc6nyJQg.js} +1 -1
  18. package/{client/dist/assets/vendor-diff-DNQpbhrT.js → dist/client/assets/vendor-diff-THJmAcEI.js} +1 -1
  19. package/{client/dist/assets/vendor-icons-GyYE35HP.js → dist/client/assets/vendor-icons-CfjIpdrD.js} +145 -155
  20. package/{client/dist/assets/vendor-markdown-CimbIo6Y.js → dist/client/assets/vendor-markdown-Cdm6NEGf.js} +1 -1
  21. package/dist/client/assets/vendor-mermaid-DTPaBx-U.js +2559 -0
  22. package/{client/dist/assets/vendor-react-96lCPsRK.js → dist/client/assets/vendor-react-wFkb6mSf.js} +1 -1
  23. package/{client/dist/assets/vendor-syntax-LS_Nt30I.js → dist/client/assets/vendor-syntax-C_UZR7tc.js} +1 -1
  24. package/dist/client/favicon.png +0 -0
  25. package/dist/client/icons/icon-128x128.png +0 -0
  26. package/dist/client/icons/icon-144x144.png +0 -0
  27. package/dist/client/icons/icon-152x152.png +0 -0
  28. package/dist/client/icons/icon-192x192.png +0 -0
  29. package/dist/client/icons/icon-384x384.png +0 -0
  30. package/dist/client/icons/icon-512x512.png +0 -0
  31. package/dist/client/icons/icon-72x72.png +0 -0
  32. package/dist/client/icons/icon-96x96.png +0 -0
  33. package/{client/dist → dist/client}/index.html +37 -36
  34. package/dist/client/logo-128.png +0 -0
  35. package/dist/client/logo-256.png +0 -0
  36. package/dist/client/logo-32.png +0 -0
  37. package/dist/client/logo-512.png +0 -0
  38. package/dist/client/logo-64.png +0 -0
  39. package/dist/client/logo.png +0 -0
  40. package/{client/dist → dist/client}/manifest.json +12 -12
  41. package/{client/dist → dist/client}/mcp-docs.html +1 -1
  42. package/{client/dist → dist/client}/sw.js +2 -2
  43. package/package.json +56 -105
  44. package/scripts/postinstall.js +9 -0
  45. package/scripts/prepublish.js +77 -0
  46. package/src/animation.js +228 -0
  47. package/src/auth.js +142 -0
  48. package/src/config.js +40 -0
  49. package/src/connect.js +416 -0
  50. package/src/launch.js +81 -0
  51. package/src/mcp.js +57 -0
  52. package/src/permissions.js +140 -0
  53. package/src/persistent-shell.js +261 -0
  54. package/src/server.js +54 -0
  55. package/client/dist/assets/AppContent-CwrTP6TW.js +0 -545
  56. package/client/dist/assets/CanvasFullScreen-D1GWQsGL.js +0 -1
  57. package/client/dist/assets/CanvasWorkspace-D7ORj358.js +0 -163
  58. package/client/dist/assets/DashboardPanel-BV7ybUDe.js +0 -1
  59. package/client/dist/assets/FileTree-5qfhBqdE.js +0 -1
  60. package/client/dist/assets/LoginModal-CImJHRjX.js +0 -13
  61. package/client/dist/assets/MermaidBlock-BFM21cwe.js +0 -2
  62. package/client/dist/assets/Onboarding-B3cteLu2.js +0 -1
  63. package/client/dist/assets/SetupForm-P6dsYgHO.js +0 -1
  64. package/client/dist/assets/WorkflowsPanel-CBoN80kc.js +0 -1
  65. package/client/dist/assets/index-46kkVu2i.css +0 -1
  66. package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
  67. package/client/dist/assets/vendor-canvas-BZV40eAE.css +0 -1
  68. package/client/dist/assets/vendor-canvas-DvHJ_Pn2.js +0 -49
  69. package/client/dist/assets/vendor-mermaid-DucWyDEe.js +0 -2556
  70. package/client/dist/favicon.png +0 -0
  71. package/client/dist/icons/icon-128x128.png +0 -0
  72. package/client/dist/icons/icon-144x144.png +0 -0
  73. package/client/dist/icons/icon-152x152.png +0 -0
  74. package/client/dist/icons/icon-192x192.png +0 -0
  75. package/client/dist/icons/icon-384x384.png +0 -0
  76. package/client/dist/icons/icon-512x512.png +0 -0
  77. package/client/dist/icons/icon-72x72.png +0 -0
  78. package/client/dist/icons/icon-96x96.png +0 -0
  79. package/client/dist/logo-128.png +0 -0
  80. package/client/dist/logo-256.png +0 -0
  81. package/client/dist/logo-32.png +0 -0
  82. package/client/dist/logo-512.png +0 -0
  83. package/client/dist/logo-64.png +0 -0
  84. package/commands/upfynai-connect.md +0 -59
  85. package/commands/upfynai-disconnect.md +0 -31
  86. package/commands/upfynai-doctor.md +0 -99
  87. package/commands/upfynai-export.md +0 -49
  88. package/commands/upfynai-local.md +0 -82
  89. package/commands/upfynai-status.md +0 -75
  90. package/commands/upfynai-stop.md +0 -49
  91. package/commands/upfynai-uninstall.md +0 -58
  92. package/commands/upfynai.md +0 -69
  93. package/scripts/build-client.js +0 -17
  94. package/scripts/fix-node-pty.js +0 -67
  95. package/scripts/install-commands.js +0 -78
  96. package/server/agent-loop.js +0 -242
  97. package/server/auto-compact.js +0 -99
  98. package/server/browser.js +0 -131
  99. package/server/claude-sdk.js +0 -797
  100. package/server/cli-ui.js +0 -798
  101. package/server/cli.js +0 -751
  102. package/server/constants/config.js +0 -31
  103. package/server/cursor-cli.js +0 -270
  104. package/server/database/auth.db +0 -0
  105. package/server/database/db.js +0 -1547
  106. package/server/database/init.sql +0 -70
  107. package/server/index.js +0 -3813
  108. package/server/load-env.js +0 -26
  109. package/server/mcp-server.js +0 -621
  110. package/server/middleware/auth.js +0 -184
  111. package/server/middleware/relayHelpers.js +0 -44
  112. package/server/middleware/sandboxRouter.js +0 -174
  113. package/server/openai-codex.js +0 -403
  114. package/server/openrouter.js +0 -137
  115. package/server/projects.js +0 -1807
  116. package/server/provider-factory.js +0 -174
  117. package/server/relay-client.js +0 -390
  118. package/server/routes/agent.js +0 -1234
  119. package/server/routes/auth.js +0 -559
  120. package/server/routes/browser.js +0 -419
  121. package/server/routes/canvas.js +0 -53
  122. package/server/routes/cli-auth.js +0 -263
  123. package/server/routes/codex.js +0 -396
  124. package/server/routes/commands.js +0 -707
  125. package/server/routes/composio.js +0 -176
  126. package/server/routes/cursor.js +0 -770
  127. package/server/routes/dashboard.js +0 -295
  128. package/server/routes/git.js +0 -1208
  129. package/server/routes/keys.js +0 -34
  130. package/server/routes/mcp-utils.js +0 -48
  131. package/server/routes/mcp.js +0 -661
  132. package/server/routes/payments.js +0 -227
  133. package/server/routes/projects.js +0 -754
  134. package/server/routes/sessions.js +0 -146
  135. package/server/routes/settings.js +0 -261
  136. package/server/routes/taskmaster.js +0 -1928
  137. package/server/routes/user.js +0 -106
  138. package/server/routes/vapi-chat.js +0 -624
  139. package/server/routes/voice.js +0 -235
  140. package/server/routes/webhooks.js +0 -166
  141. package/server/routes/workflows.js +0 -312
  142. package/server/sandbox.js +0 -120
  143. package/server/services/browser-ai.js +0 -154
  144. package/server/services/composio.js +0 -204
  145. package/server/services/sessionRegistry.js +0 -139
  146. package/server/services/whisperService.js +0 -84
  147. package/server/services/workflowScheduler.js +0 -211
  148. package/server/tests/relay-flow.test.js +0 -570
  149. package/server/tests/sessions.test.js +0 -259
  150. package/server/utils/commandParser.js +0 -303
  151. package/server/utils/email.js +0 -66
  152. package/server/utils/gitConfig.js +0 -24
  153. package/server/utils/mcp-detector.js +0 -198
  154. package/server/utils/taskmaster-websocket.js +0 -129
  155. package/shared/integrationCatalog.d.ts +0 -12
  156. package/shared/integrationCatalog.js +0 -172
  157. package/shared/modelConstants.js +0 -96
  158. /package/{shared → dist}/agents/claude.js +0 -0
  159. /package/{shared → dist}/agents/codex.js +0 -0
  160. /package/{shared → dist}/agents/cursor.js +0 -0
  161. /package/{shared → dist}/agents/detect.js +0 -0
  162. /package/{shared → dist}/agents/exec.js +0 -0
  163. /package/{shared → dist}/agents/files.js +0 -0
  164. /package/{shared → dist}/agents/git.js +0 -0
  165. /package/{shared → dist}/agents/gitagent.js +0 -0
  166. /package/{shared → dist}/agents/index.js +0 -0
  167. /package/{shared → dist}/agents/shell.js +0 -0
  168. /package/{shared → dist}/agents/utils.js +0 -0
  169. /package/{client/dist → dist/client}/api-docs.html +0 -0
  170. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  171. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  172. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  173. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  174. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  175. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  176. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  177. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  178. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  179. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  180. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  181. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  182. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  183. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  184. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  185. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  186. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  187. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  188. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  189. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  190. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  191. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  192. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  193. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  194. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  195. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  196. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  197. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  198. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  199. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  200. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  201. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  202. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  203. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  204. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  205. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  206. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  207. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  208. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  209. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  210. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  211. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  212. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  213. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  214. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  215. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  216. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  217. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  218. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  219. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  220. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  221. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  222. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  223. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  224. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  225. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  226. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  227. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  228. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  229. /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
  230. /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
  231. /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +0 -0
  232. /package/{client/dist → dist/client}/clear-cache.html +0 -0
  233. /package/{client/dist → dist/client}/convert-icons.md +0 -0
  234. /package/{client/dist → dist/client}/favicon.svg +0 -0
  235. /package/{client/dist → dist/client}/generate-icons.js +0 -0
  236. /package/{client/dist → dist/client}/icons/claude-ai-icon.svg +0 -0
  237. /package/{client/dist → dist/client}/icons/codex-white.svg +0 -0
  238. /package/{client/dist → dist/client}/icons/codex.svg +0 -0
  239. /package/{client/dist → dist/client}/icons/cursor-white.svg +0 -0
  240. /package/{client/dist → dist/client}/icons/cursor.svg +0 -0
  241. /package/{client/dist → dist/client}/icons/icon-128x128.svg +0 -0
  242. /package/{client/dist → dist/client}/icons/icon-144x144.svg +0 -0
  243. /package/{client/dist → dist/client}/icons/icon-152x152.svg +0 -0
  244. /package/{client/dist → dist/client}/icons/icon-192x192.svg +0 -0
  245. /package/{client/dist → dist/client}/icons/icon-384x384.svg +0 -0
  246. /package/{client/dist → dist/client}/icons/icon-512x512.svg +0 -0
  247. /package/{client/dist → dist/client}/icons/icon-72x72.svg +0 -0
  248. /package/{client/dist → dist/client}/icons/icon-96x96.svg +0 -0
  249. /package/{client/dist → dist/client}/icons/icon-template.svg +0 -0
  250. /package/{client/dist → dist/client}/logo.svg +0 -0
  251. /package/{client/dist → dist/client}/offline.html +0 -0
  252. /package/{client/dist → dist/client}/screenshots/cli-selection.png +0 -0
  253. /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
  254. /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
  255. /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
  256. /package/{shared → dist}/gitagent/index.js +0 -0
  257. /package/{shared → dist}/gitagent/parser.js +0 -0
  258. /package/{shared → dist}/gitagent/prompt-builder.js +0 -0
@@ -1,661 +0,0 @@
1
- import express from 'express';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import os from 'os';
5
- import { fileURLToPath } from 'url';
6
- import { dirname } from 'path';
7
- import { spawn } from 'child_process';
8
-
9
- const router = express.Router();
10
- const __filename = fileURLToPath(import.meta.url);
11
- const __dirname = dirname(__filename);
12
-
13
- // Claude CLI command routes
14
-
15
- // GET /api/mcp/cli/list - List MCP servers using Claude CLI
16
- router.get('/cli/list', async (req, res) => {
17
- try {
18
- // Cloud mode: run claude CLI on user's machine via relay
19
- if (req.isCloud) {
20
- if (!req.requireRelay()) return;
21
- try {
22
- const result = await req.sendRelay('shell-command', { command: 'claude mcp list' }, 30000);
23
- const output = result.stdout || result.output || '';
24
- return res.json({ success: true, output, servers: parseClaudeListOutput(output) });
25
- } catch (err) {
26
- return res.status(500).json({ error: 'Claude CLI command failed via relay', details: err.message });
27
- }
28
- }
29
-
30
- // Local mode
31
- const { spawn } = await import('child_process');
32
-
33
- const process = spawn('claude', ['mcp', 'list'], {
34
- stdio: ['pipe', 'pipe', 'pipe']
35
- });
36
-
37
- let stdout = '';
38
- let stderr = '';
39
-
40
- process.stdout.on('data', (data) => {
41
- stdout += data.toString();
42
- });
43
-
44
- process.stderr.on('data', (data) => {
45
- stderr += data.toString();
46
- });
47
-
48
- process.on('close', (code) => {
49
- if (code === 0) {
50
- res.json({ success: true, output: stdout, servers: parseClaudeListOutput(stdout) });
51
- } else {
52
- // CLI error
53
- res.status(500).json({ error: 'Claude CLI command failed', details: stderr });
54
- }
55
- });
56
-
57
- process.on('error', (error) => {
58
- // CLI run error
59
- res.status(500).json({ error: 'Failed to run Claude CLI', details: 'An error occurred' });
60
- });
61
- } catch (error) {
62
- // MCP list error
63
- res.status(500).json({ error: 'Failed to list MCP servers', details: 'An error occurred' });
64
- }
65
- });
66
-
67
- // POST /api/mcp/cli/add - Add MCP server using Claude CLI
68
- router.post('/cli/add', async (req, res) => {
69
- try {
70
- const { name, type = 'stdio', command, args = [], url, headers = {}, env = {}, scope = 'user', projectPath } = req.body;
71
-
72
- // Cloud mode: run claude CLI on user's machine via relay
73
- if (req.isCloud) {
74
- if (!req.requireRelay()) return;
75
- try {
76
- let cliParts = ['claude', 'mcp', 'add', '--scope', scope];
77
- if (type === 'http') {
78
- cliParts.push('--transport', 'http', name, url);
79
- Object.entries(headers).forEach(([key, value]) => { cliParts.push('--header', `${key}: ${value}`); });
80
- } else if (type === 'sse') {
81
- cliParts.push('--transport', 'sse', name, url);
82
- Object.entries(headers).forEach(([key, value]) => { cliParts.push('--header', `${key}: ${value}`); });
83
- } else {
84
- cliParts.push(name);
85
- Object.entries(env).forEach(([key, value]) => { cliParts.push('-e', `${key}=${value}`); });
86
- cliParts.push(command);
87
- if (args && args.length > 0) cliParts.push(...args);
88
- }
89
- const fullCmd = cliParts.map(a => (typeof a === 'string' && a.includes(' ')) ? `"${a}"` : a).join(' ');
90
- const payload = { command: fullCmd };
91
- if (scope === 'local' && projectPath) payload.cwd = projectPath;
92
- const result = await req.sendRelay('shell-command', payload, 30000);
93
- return res.json({ success: true, output: result.stdout || result.output || '', message: `MCP server "${name}" added successfully` });
94
- } catch (err) {
95
- return res.status(500).json({ error: 'Claude CLI command failed via relay', details: err.message });
96
- }
97
- }
98
-
99
- const { spawn } = await import('child_process');
100
-
101
- let cliArgs = ['mcp', 'add'];
102
-
103
- // Add scope flag
104
- cliArgs.push('--scope', scope);
105
-
106
- if (type === 'http') {
107
- cliArgs.push('--transport', 'http', name, url);
108
- // Add headers if provided
109
- Object.entries(headers).forEach(([key, value]) => {
110
- cliArgs.push('--header', `${key}: ${value}`);
111
- });
112
- } else if (type === 'sse') {
113
- cliArgs.push('--transport', 'sse', name, url);
114
- // Add headers if provided
115
- Object.entries(headers).forEach(([key, value]) => {
116
- cliArgs.push('--header', `${key}: ${value}`);
117
- });
118
- } else {
119
- // stdio (default): claude mcp add --scope user <name> <command> [args...]
120
- cliArgs.push(name);
121
- // Add environment variables
122
- Object.entries(env).forEach(([key, value]) => {
123
- cliArgs.push('-e', `${key}=${value}`);
124
- });
125
- cliArgs.push(command);
126
- if (args && args.length > 0) {
127
- cliArgs.push(...args);
128
- }
129
- }
130
-
131
-
132
- // For local scope, we need to run the command in the project directory
133
- const spawnOptions = {
134
- stdio: ['pipe', 'pipe', 'pipe']
135
- };
136
-
137
- if (scope === 'local' && projectPath) {
138
- spawnOptions.cwd = projectPath;
139
- }
140
-
141
- const process = spawn('claude', cliArgs, spawnOptions);
142
-
143
- let stdout = '';
144
- let stderr = '';
145
-
146
- process.stdout.on('data', (data) => {
147
- stdout += data.toString();
148
- });
149
-
150
- process.stderr.on('data', (data) => {
151
- stderr += data.toString();
152
- });
153
-
154
- process.on('close', (code) => {
155
- if (code === 0) {
156
- res.json({ success: true, output: stdout, message: `MCP server "${name}" added successfully` });
157
- } else {
158
- // CLI error
159
- res.status(400).json({ error: 'Claude CLI command failed', details: stderr });
160
- }
161
- });
162
-
163
- process.on('error', (error) => {
164
- // CLI run error
165
- res.status(500).json({ error: 'Failed to run Claude CLI', details: 'An error occurred' });
166
- });
167
- } catch (error) {
168
- // MCP add error
169
- res.status(500).json({ error: 'Failed to add MCP server', details: 'An error occurred' });
170
- }
171
- });
172
-
173
- // POST /api/mcp/cli/add-json - Add MCP server using JSON format
174
- router.post('/cli/add-json', async (req, res) => {
175
- try {
176
- const { name, jsonConfig, scope = 'user', projectPath } = req.body;
177
-
178
- // Validate and parse JSON config
179
- let parsedConfig;
180
- try {
181
- parsedConfig = typeof jsonConfig === 'string' ? JSON.parse(jsonConfig) : jsonConfig;
182
- } catch (parseError) {
183
- return res.status(400).json({
184
- error: 'Invalid JSON configuration',
185
- details: parseError.message
186
- });
187
- }
188
-
189
- // Validate required fields
190
- if (!parsedConfig.type) {
191
- return res.status(400).json({
192
- error: 'Invalid configuration',
193
- details: 'Missing required field: type'
194
- });
195
- }
196
-
197
- if (parsedConfig.type === 'stdio' && !parsedConfig.command) {
198
- return res.status(400).json({
199
- error: 'Invalid configuration',
200
- details: 'stdio type requires a command field'
201
- });
202
- }
203
-
204
- if ((parsedConfig.type === 'http' || parsedConfig.type === 'sse') && !parsedConfig.url) {
205
- return res.status(400).json({
206
- error: 'Invalid configuration',
207
- details: `${parsedConfig.type} type requires a url field`
208
- });
209
- }
210
-
211
- // Cloud mode: run claude CLI on user's machine via relay
212
- if (req.isCloud) {
213
- if (!req.requireRelay()) return;
214
- try {
215
- const jsonString = JSON.stringify(parsedConfig);
216
- const fullCmd = `claude mcp add-json --scope ${scope} ${name} '${jsonString}'`;
217
- const payload = { command: fullCmd };
218
- if (scope === 'local' && projectPath) payload.cwd = projectPath;
219
- const result = await req.sendRelay('shell-command', payload, 30000);
220
- return res.json({ success: true, output: result.stdout || result.output || '', message: `MCP server "${name}" added successfully via JSON` });
221
- } catch (err) {
222
- return res.status(500).json({ error: 'Claude CLI command failed via relay', details: err.message });
223
- }
224
- }
225
-
226
- const { spawn } = await import('child_process');
227
-
228
- // Build the command: claude mcp add-json --scope <scope> <name> '<json>'
229
- const cliArgs = ['mcp', 'add-json', '--scope', scope, name];
230
-
231
- // Add the JSON config as a properly formatted string
232
- const jsonString = JSON.stringify(parsedConfig);
233
- cliArgs.push(jsonString);
234
-
235
-
236
- // For local scope, we need to run the command in the project directory
237
- const spawnOptions = {
238
- stdio: ['pipe', 'pipe', 'pipe']
239
- };
240
-
241
- if (scope === 'local' && projectPath) {
242
- spawnOptions.cwd = projectPath;
243
- }
244
-
245
- const process = spawn('claude', cliArgs, spawnOptions);
246
-
247
- let stdout = '';
248
- let stderr = '';
249
-
250
- process.stdout.on('data', (data) => {
251
- stdout += data.toString();
252
- });
253
-
254
- process.stderr.on('data', (data) => {
255
- stderr += data.toString();
256
- });
257
-
258
- process.on('close', (code) => {
259
- if (code === 0) {
260
- res.json({ success: true, output: stdout, message: `MCP server "${name}" added successfully via JSON` });
261
- } else {
262
- // CLI error
263
- res.status(400).json({ error: 'Claude CLI command failed', details: stderr });
264
- }
265
- });
266
-
267
- process.on('error', (error) => {
268
- // CLI run error
269
- res.status(500).json({ error: 'Failed to run Claude CLI', details: 'An error occurred' });
270
- });
271
- } catch (error) {
272
- // MCP JSON add error
273
- res.status(500).json({ error: 'Failed to add MCP server', details: 'An error occurred' });
274
- }
275
- });
276
-
277
- // DELETE /api/mcp/cli/remove/:name - Remove MCP server using Claude CLI
278
- router.delete('/cli/remove/:name', async (req, res) => {
279
- try {
280
- const { name } = req.params;
281
- const { scope } = req.query;
282
-
283
- // Handle the ID format (remove scope prefix if present)
284
- let actualName = name;
285
- let actualScope = scope;
286
-
287
- if (name.includes(':')) {
288
- const [prefix, serverName] = name.split(':');
289
- actualName = serverName;
290
- actualScope = actualScope || prefix;
291
- }
292
-
293
- // Cloud mode: run claude CLI on user's machine via relay
294
- if (req.isCloud) {
295
- if (!req.requireRelay()) return;
296
- try {
297
- const scopeFlag = actualScope === 'local' ? '--scope local' : '--scope user';
298
- const result = await req.sendRelay('shell-command', { command: `claude mcp remove ${scopeFlag} ${actualName}` }, 30000);
299
- return res.json({ success: true, output: result.stdout || result.output || '', message: `MCP server "${name}" removed successfully` });
300
- } catch (err) {
301
- return res.status(500).json({ error: 'Claude CLI command failed via relay', details: err.message });
302
- }
303
- }
304
-
305
- const { spawn } = await import('child_process');
306
-
307
- // Build command args based on scope
308
- let cliArgs = ['mcp', 'remove'];
309
-
310
- // Add scope flag if it's local scope
311
- if (actualScope === 'local') {
312
- cliArgs.push('--scope', 'local');
313
- } else if (actualScope === 'user' || !actualScope) {
314
- // User scope is default, but we can be explicit
315
- cliArgs.push('--scope', 'user');
316
- }
317
-
318
- cliArgs.push(actualName);
319
-
320
-
321
- const process = spawn('claude', cliArgs, {
322
- stdio: ['pipe', 'pipe', 'pipe']
323
- });
324
-
325
- let stdout = '';
326
- let stderr = '';
327
-
328
- process.stdout.on('data', (data) => {
329
- stdout += data.toString();
330
- });
331
-
332
- process.stderr.on('data', (data) => {
333
- stderr += data.toString();
334
- });
335
-
336
- process.on('close', (code) => {
337
- if (code === 0) {
338
- res.json({ success: true, output: stdout, message: `MCP server "${name}" removed successfully` });
339
- } else {
340
- // CLI error
341
- res.status(400).json({ error: 'Claude CLI command failed', details: stderr });
342
- }
343
- });
344
-
345
- process.on('error', (error) => {
346
- // CLI run error
347
- res.status(500).json({ error: 'Failed to run Claude CLI', details: 'An error occurred' });
348
- });
349
- } catch (error) {
350
- // MCP remove error
351
- res.status(500).json({ error: 'Failed to remove MCP server', details: 'An error occurred' });
352
- }
353
- });
354
-
355
- // GET /api/mcp/cli/get/:name - Get MCP server details using Claude CLI
356
- router.get('/cli/get/:name', async (req, res) => {
357
- try {
358
- const { name } = req.params;
359
-
360
- // Cloud mode: run claude CLI on user's machine via relay
361
- if (req.isCloud) {
362
- if (!req.requireRelay()) return;
363
- try {
364
- const result = await req.sendRelay('shell-command', { command: `claude mcp get ${name}` }, 30000);
365
- const output = result.stdout || result.output || '';
366
- return res.json({ success: true, output, server: parseClaudeGetOutput(output) });
367
- } catch (err) {
368
- return res.status(404).json({ error: 'Claude CLI command failed via relay', details: err.message });
369
- }
370
- }
371
-
372
- const { spawn } = await import('child_process');
373
-
374
- const process = spawn('claude', ['mcp', 'get', name], {
375
- stdio: ['pipe', 'pipe', 'pipe']
376
- });
377
-
378
- let stdout = '';
379
- let stderr = '';
380
-
381
- process.stdout.on('data', (data) => {
382
- stdout += data.toString();
383
- });
384
-
385
- process.stderr.on('data', (data) => {
386
- stderr += data.toString();
387
- });
388
-
389
- process.on('close', (code) => {
390
- if (code === 0) {
391
- res.json({ success: true, output: stdout, server: parseClaudeGetOutput(stdout) });
392
- } else {
393
- // CLI error
394
- res.status(404).json({ error: 'Claude CLI command failed', details: stderr });
395
- }
396
- });
397
-
398
- process.on('error', (error) => {
399
- // CLI run error
400
- res.status(500).json({ error: 'Failed to run Claude CLI', details: 'An error occurred' });
401
- });
402
- } catch (error) {
403
- // MCP CLI details error
404
- res.status(500).json({ error: 'Failed to get MCP server details', details: 'An error occurred' });
405
- }
406
- });
407
-
408
- // GET /api/mcp/config/read - Read MCP servers directly from Claude config files
409
- router.get('/config/read', async (req, res) => {
410
- try {
411
- // Cloud mode: read config from user's machine via relay
412
- if (req.isCloud) {
413
- if (!req.requireRelay()) return;
414
- const configFiles = ['~/.claude.json', '~/.claude/settings.json'];
415
- let configData = null;
416
- let configPath = null;
417
-
418
- for (const filePath of configFiles) {
419
- try {
420
- const result = await req.sendRelay('file-read', { filePath }, 15000);
421
- configData = JSON.parse(result.content);
422
- configPath = filePath;
423
- break;
424
- } catch { /* try next */ }
425
- }
426
-
427
- if (!configData) {
428
- return res.json({ success: true, configPath: null, servers: [] });
429
- }
430
-
431
- const servers = [];
432
- if (configData.mcpServers && typeof configData.mcpServers === 'object') {
433
- for (const [name, config] of Object.entries(configData.mcpServers)) {
434
- const server = { id: name, name, type: 'stdio', scope: 'user', config: {}, raw: config };
435
- if (config.command) {
436
- server.type = 'stdio';
437
- server.config = { command: config.command, args: config.args || [], env: config.env || {} };
438
- } else if (config.url) {
439
- server.type = config.transport || 'http';
440
- server.config = { url: config.url, headers: config.headers || {} };
441
- }
442
- servers.push(server);
443
- }
444
- }
445
- // Check for project-scoped MCP servers if projectPath is provided
446
- const cloudProjectPath = req.query.projectPath;
447
- if (cloudProjectPath && configData.projects && typeof configData.projects === 'object') {
448
- const projectConfig = configData.projects[cloudProjectPath];
449
- if (projectConfig?.mcpServers && typeof projectConfig.mcpServers === 'object') {
450
- for (const [name, config] of Object.entries(projectConfig.mcpServers)) {
451
- const server = { id: `project:${name}`, name, type: 'stdio', scope: 'project', projectPath: cloudProjectPath, config: {}, raw: config };
452
- if (config.command) {
453
- server.type = 'stdio';
454
- server.config = { command: config.command, args: config.args || [], env: config.env || {} };
455
- } else if (config.url) {
456
- server.type = config.transport || 'http';
457
- server.config = { url: config.url, headers: config.headers || {} };
458
- }
459
- servers.push(server);
460
- }
461
- }
462
- }
463
-
464
- return res.json({ success: true, configPath, servers });
465
- }
466
-
467
- const homeDir = os.homedir();
468
- const configPaths = [
469
- path.join(homeDir, '.claude.json'),
470
- path.join(homeDir, '.claude', 'settings.json')
471
- ];
472
-
473
- let configData = null;
474
- let configPath = null;
475
-
476
- // Try to read from either config file
477
- for (const filepath of configPaths) {
478
- try {
479
- const fileContent = await fs.readFile(filepath, 'utf8');
480
- configData = JSON.parse(fileContent);
481
- configPath = filepath;
482
- break;
483
- } catch (error) {
484
- // File doesn't exist or is not valid JSON, try next
485
- }
486
- }
487
-
488
- if (!configData) {
489
- return res.json({
490
- success: false,
491
- message: 'No Claude configuration file found',
492
- servers: []
493
- });
494
- }
495
-
496
- // Extract MCP servers from the config
497
- const servers = [];
498
-
499
- // Check for user-scoped MCP servers (at root level)
500
- if (configData.mcpServers && typeof configData.mcpServers === 'object' && Object.keys(configData.mcpServers).length > 0) {
501
- for (const [name, config] of Object.entries(configData.mcpServers)) {
502
- const server = {
503
- id: name,
504
- name: name,
505
- type: 'stdio', // Default type
506
- scope: 'user', // User scope - available across all projects
507
- config: {},
508
- raw: config // Include raw config for full details
509
- };
510
-
511
- // Determine transport type and extract config
512
- if (config.command) {
513
- server.type = 'stdio';
514
- server.config.command = config.command;
515
- server.config.args = config.args || [];
516
- server.config.env = config.env || {};
517
- } else if (config.url) {
518
- server.type = config.transport || 'http';
519
- server.config.url = config.url;
520
- server.config.headers = config.headers || {};
521
- }
522
-
523
- servers.push(server);
524
- }
525
- }
526
-
527
- // Check for local-scoped MCP servers (project-specific)
528
- // Use provided projectPath query param, or fall back to cwd
529
- const currentProjectPath = req.query.projectPath || process.cwd();
530
-
531
- // Check under 'projects' key
532
- if (configData.projects && configData.projects[currentProjectPath]) {
533
- const projectConfig = configData.projects[currentProjectPath];
534
- if (projectConfig.mcpServers && typeof projectConfig.mcpServers === 'object' && Object.keys(projectConfig.mcpServers).length > 0) {
535
- for (const [name, config] of Object.entries(projectConfig.mcpServers)) {
536
- const server = {
537
- id: `local:${name}`, // Prefix with scope for uniqueness
538
- name: name, // Keep original name
539
- type: 'stdio', // Default type
540
- scope: 'local', // Local scope - only for this project
541
- projectPath: currentProjectPath,
542
- config: {},
543
- raw: config // Include raw config for full details
544
- };
545
-
546
- // Determine transport type and extract config
547
- if (config.command) {
548
- server.type = 'stdio';
549
- server.config.command = config.command;
550
- server.config.args = config.args || [];
551
- server.config.env = config.env || {};
552
- } else if (config.url) {
553
- server.type = config.transport || 'http';
554
- server.config.url = config.url;
555
- server.config.headers = config.headers || {};
556
- }
557
-
558
- servers.push(server);
559
- }
560
- }
561
- }
562
-
563
-
564
- res.json({
565
- success: true,
566
- configPath: configPath,
567
- servers: servers
568
- });
569
- } catch (error) {
570
- res.status(500).json({
571
- error: 'Failed to read Claude configuration',
572
- details: 'An error occurred'
573
- });
574
- }
575
- });
576
-
577
- // Helper functions to parse Claude CLI output
578
- function parseClaudeListOutput(output) {
579
- const servers = [];
580
- const lines = output.split('\n').filter(line => line.trim());
581
-
582
- for (const line of lines) {
583
- // Skip the header line
584
- if (line.includes('Checking MCP server health')) continue;
585
-
586
- // Parse lines like "test: test test - ✗ Failed to connect"
587
- // or "server-name: command or description - ✓ Connected"
588
- if (line.includes(':')) {
589
- const colonIndex = line.indexOf(':');
590
- const name = line.substring(0, colonIndex).trim();
591
-
592
- // Skip empty names
593
- if (!name) continue;
594
-
595
- // Extract the rest after the name
596
- const rest = line.substring(colonIndex + 1).trim();
597
-
598
- // Try to extract description and status
599
- let description = rest;
600
- let status = 'unknown';
601
- let type = 'stdio'; // default type
602
-
603
- // Check for status indicators
604
- if (rest.includes('✓') || rest.includes('✗')) {
605
- const statusMatch = rest.match(/(.*?)\s*-\s*([✓✗].*)$/);
606
- if (statusMatch) {
607
- description = statusMatch[1].trim();
608
- status = statusMatch[2].includes('✓') ? 'connected' : 'failed';
609
- }
610
- }
611
-
612
- // Try to determine type from description
613
- if (description.startsWith('http://') || description.startsWith('https://')) {
614
- type = 'http';
615
- }
616
-
617
- servers.push({
618
- name,
619
- type,
620
- status: status || 'active',
621
- description
622
- });
623
- }
624
- }
625
-
626
- return servers;
627
- }
628
-
629
- function parseClaudeGetOutput(output) {
630
- // Parse the output from 'claude mcp get <name>' command
631
- // This is a simple parser - might need adjustment based on actual output format
632
- try {
633
- // Try to extract JSON if present
634
- const jsonMatch = output.match(/\{[\s\S]*\}/);
635
- if (jsonMatch) {
636
- return JSON.parse(jsonMatch[0]);
637
- }
638
-
639
- // Otherwise, parse as text
640
- const server = { raw_output: output };
641
- const lines = output.split('\n');
642
-
643
- for (const line of lines) {
644
- if (line.includes('Name:')) {
645
- server.name = line.split(':')[1]?.trim();
646
- } else if (line.includes('Type:')) {
647
- server.type = line.split(':')[1]?.trim();
648
- } else if (line.includes('Command:')) {
649
- server.command = line.split(':')[1]?.trim();
650
- } else if (line.includes('URL:')) {
651
- server.url = line.split(':')[1]?.trim();
652
- }
653
- }
654
-
655
- return server;
656
- } catch (error) {
657
- return { raw_output: output, parse_error: error.message };
658
- }
659
- }
660
-
661
- export default router;