upfynai-code 0.1.0 → 2.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.
- package/client/dist/api-docs.html +879 -0
- package/client/dist/assets/AppContent-CTSHQdyq.js +513 -0
- package/client/dist/assets/CanvasPanel-Cig0Mo9s.js +6 -0
- package/client/dist/assets/CanvasPanel-q4HEqNtV.css +1 -0
- package/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/client/dist/assets/LoginModal-silya-zP.js +11 -0
- package/client/dist/assets/MarkdownPreview-B3c7OEj6.js +1 -0
- package/client/dist/assets/Onboarding-Coxo6mFA.js +1 -0
- package/client/dist/assets/SetupForm-BzYOsbji.js +1 -0
- package/client/dist/assets/Tableau10-B-NsZVaP.js +1 -0
- package/client/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +1 -0
- package/client/dist/assets/ar-SA-G6X2FPQ2-Bmw2-hDt.js +10 -0
- package/client/dist/assets/arc-BMqY7_Ci.js +1 -0
- package/client/dist/assets/array-BKyUJesY.js +1 -0
- package/client/dist/assets/az-AZ-76LH7QW2-Dh1le_qs.js +1 -0
- package/client/dist/assets/bg-BG-XCXSNQG7-Cbav8Z9z.js +5 -0
- package/client/dist/assets/blockDiagram-38ab4fdb-ChHJxsXw.js +118 -0
- package/client/dist/assets/bn-BD-2XOGV67Q-DCNjOaWz.js +5 -0
- package/client/dist/assets/c4Diagram-3d4e48cf-b8Xue4Z6.js +10 -0
- package/client/dist/assets/ca-ES-6MX7JW3Y-Dl_vM7NS.js +8 -0
- package/client/dist/assets/channel-CSnvHe_M.js +1 -0
- package/client/dist/assets/classDiagram-70f12bd4-BheP7Ggo.js +2 -0
- package/client/dist/assets/classDiagram-v2-f2320105-xtym7GEZ.js +2 -0
- package/client/dist/assets/clone-B75abXxS.js +1 -0
- package/client/dist/assets/createText-2e5e7dd3-_n4jI_fO.js +5 -0
- package/client/dist/assets/cs-CZ-2BRQDIVT-ftsKDdz4.js +11 -0
- package/client/dist/assets/da-DK-5WZEPLOC-DAjdwGRO.js +5 -0
- package/client/dist/assets/de-DE-XR44H4JA-BJXczHGT.js +8 -0
- package/client/dist/assets/directory-open-01563666-DWU9wJ6I.js +1 -0
- package/client/dist/assets/directory-open-4ed118d0-CunoC1EB.js +1 -0
- package/client/dist/assets/edges-e0da2a9e-CfPZr4YM.js +4 -0
- package/client/dist/assets/el-GR-BZB4AONW-DW2p_uy7.js +10 -0
- package/client/dist/assets/erDiagram-9861fffd-CF33V-Of.js +51 -0
- package/client/dist/assets/es-ES-U4NZUMDT-DLOIGnrl.js +9 -0
- package/client/dist/assets/eu-ES-A7QVB2H4-LJXbf89m.js +11 -0
- package/client/dist/assets/fa-IR-HGAKTJCU-Dvx65fgW.js +8 -0
- package/client/dist/assets/fi-FI-Z5N7JZ37-EoL65BQh.js +6 -0
- package/client/dist/assets/file-open-002ab408-DIuFHtCF.js +1 -0
- package/client/dist/assets/file-open-7c801643-684qeFg4.js +1 -0
- package/client/dist/assets/file-save-3189631c-C1wFhQhH.js +1 -0
- package/client/dist/assets/file-save-745eba88-Bb9F9Kg7.js +1 -0
- package/client/dist/assets/flowDb-956e92f1-HgoXVy2H.js +10 -0
- package/client/dist/assets/flowDiagram-66a62f08-tffoET0H.js +4 -0
- package/client/dist/assets/flowDiagram-v2-96b9c2cf-Byc3JCHh.js +1 -0
- package/client/dist/assets/flowchart-elk-definition-4a651766-DJbI2dpv.js +139 -0
- package/client/dist/assets/fr-FR-RHASNOE6-DNk_jdDs.js +9 -0
- package/client/dist/assets/ganttDiagram-c361ad54-2XX670FU.js +257 -0
- package/client/dist/assets/gitGraphDiagram-72cf32ee-CcUfruAo.js +70 -0
- package/client/dist/assets/gl-ES-HMX3MZ6V-dxzFjZlG.js +10 -0
- package/client/dist/assets/graph-BSbiMSBC.js +1 -0
- package/client/dist/assets/he-IL-6SHJWFNN-Cogsfdt1.js +10 -0
- package/client/dist/assets/hi-IN-IWLTKZ5I-L6wbgi4F.js +4 -0
- package/client/dist/assets/hu-HU-A5ZG7DT2-DSA6ZDsH.js +7 -0
- package/client/dist/assets/id-ID-SAP4L64H-BK_vGGS6.js +10 -0
- package/client/dist/assets/image-blob-reduce.esm-BLtmMM_J.js +2 -0
- package/client/dist/assets/index-3862675e-Bv32HUgT.js +1 -0
- package/client/dist/assets/index-B8wwD_Xo.css +1 -0
- package/client/dist/assets/index-BPwf8Fw3.js +27 -0
- package/client/dist/assets/index-D1urGMYu.js +95 -0
- package/client/dist/assets/infoDiagram-f8f76790-w4mR4pxn.js +7 -0
- package/client/dist/assets/init-Gi6I4Gst.js +1 -0
- package/client/dist/assets/it-IT-JPQ66NNP-BLdHYMhn.js +11 -0
- package/client/dist/assets/ja-JP-DBVTYXUO-B_vmexl_.js +8 -0
- package/client/dist/assets/journeyDiagram-49397b02-D9nmO17e.js +139 -0
- package/client/dist/assets/kaa-6HZHGXH3-5s-3jl6F.js +1 -0
- package/client/dist/assets/kab-KAB-ZGHBKWFO-2QaVDuSf.js +8 -0
- package/client/dist/assets/kk-KZ-P5N5QNE5-CTC52Vbi.js +1 -0
- package/client/dist/assets/km-KH-HSX4SM5Z-DxawH8UZ.js +11 -0
- package/client/dist/assets/ko-KR-MTYHY66A-CmosEM8_.js +9 -0
- package/client/dist/assets/ku-TR-6OUDTVRD-DbiLen4y.js +9 -0
- package/client/dist/assets/layout-jmt3H9tA.js +1 -0
- package/client/dist/assets/line-JTlRayUJ.js +1 -0
- package/client/dist/assets/linear-DJeB5p7x.js +1 -0
- package/client/dist/assets/lt-LT-XHIRWOB4-CH15wrjA.js +3 -0
- package/client/dist/assets/lv-LV-5QDEKY6T-dhgfPuCQ.js +7 -0
- package/client/dist/assets/mindmap-definition-fc14e90a-BOOrexmz.js +415 -0
- package/client/dist/assets/mr-IN-CRQNXWMA-3Gi6iq7A.js +13 -0
- package/client/dist/assets/my-MM-5M5IBNSE-CpH4rdJj.js +1 -0
- package/client/dist/assets/nb-NO-T6EIAALU-Du6iiGql.js +10 -0
- package/client/dist/assets/nl-NL-IS3SIHDZ-BGvsd1MT.js +8 -0
- package/client/dist/assets/nn-NO-6E72VCQL-B-odvJZW.js +8 -0
- package/client/dist/assets/oc-FR-POXYY2M6-COC8xNjo.js +8 -0
- package/client/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/client/dist/assets/pa-IN-N4M65BXN-CE21PUQH.js +4 -0
- package/client/dist/assets/path-CbwjOpE9.js +1 -0
- package/client/dist/assets/pdf-TYrZqVzP.js +12 -0
- package/client/dist/assets/pdf.worker-BA9kU3Pw.mjs +61080 -0
- package/client/dist/assets/percentages-BXMCSKIN-C9GT0OD3.js +199 -0
- package/client/dist/assets/pica-VkdyTzi8.js +2 -0
- package/client/dist/assets/pieDiagram-8a3498a8-Cvfh7Qr5.js +35 -0
- package/client/dist/assets/pl-PL-T2D74RX3-D4xFVSoT.js +9 -0
- package/client/dist/assets/pt-BR-5N22H2LF-CCq257gA.js +9 -0
- package/client/dist/assets/pt-PT-UZXXM6DQ-1l8gt5vA.js +9 -0
- package/client/dist/assets/quadrantDiagram-120e2f19-BA0js1aD.js +7 -0
- package/client/dist/assets/requirementDiagram-deff3bca-B0QNFfIn.js +52 -0
- package/client/dist/assets/ro-RO-JPDTUUEW-yosBW01E.js +11 -0
- package/client/dist/assets/roundRect-mAH3dD0p.js +1 -0
- package/client/dist/assets/ru-RU-B4JR7IUQ-8LkEJUix.js +9 -0
- package/client/dist/assets/sankeyDiagram-04a897e0-D4T9eCXn.js +8 -0
- package/client/dist/assets/sequenceDiagram-704730f1-CfBUTCrO.js +122 -0
- package/client/dist/assets/si-LK-N5RQ5JYF-D8rjbqtd.js +1 -0
- package/client/dist/assets/sk-SK-C5VTKIMK-Bg14sAzN.js +6 -0
- package/client/dist/assets/sl-SI-NN7IZMDC-CMTib6Zs.js +6 -0
- package/client/dist/assets/stateDiagram-587899a1-BGgvmVSZ.js +1 -0
- package/client/dist/assets/stateDiagram-v2-d93cdb3a-Qn3DpYuO.js +1 -0
- package/client/dist/assets/styles-6aaf32cf-IdVZLPrD.js +207 -0
- package/client/dist/assets/styles-9a916d00-BAC3L45X.js +160 -0
- package/client/dist/assets/styles-c10674c1-COhXxX8c.js +116 -0
- package/client/dist/assets/subset-shared.chunk-BWHnFai4.js +22 -0
- package/client/dist/assets/subset-worker.chunk-C8QUSruZ.js +1 -0
- package/client/dist/assets/sv-SE-XGPEYMSR-C1425rOF.js +10 -0
- package/client/dist/assets/svgDrawCommon-08f97a94-Cfk-fgnN.js +1 -0
- package/client/dist/assets/ta-IN-2NMHFXQM-BHHo1zpF.js +9 -0
- package/client/dist/assets/th-TH-HPSO5L25-CZVzm_WT.js +2 -0
- package/client/dist/assets/timeline-definition-85554ec2-VAvuJith.js +61 -0
- package/client/dist/assets/tr-TR-DEFEU3FU-DE1lclCq.js +7 -0
- package/client/dist/assets/uk-UA-QMV73CPH-D4lJZ85O.js +6 -0
- package/client/dist/assets/vendor-codemirror-BARtJV1V.js +16 -0
- package/client/dist/assets/vendor-codemirror-langs-52_y1wip.js +20 -0
- package/client/dist/assets/vendor-i18n-ByAl-gdx.js +1 -0
- package/client/dist/assets/vendor-icons-D33IkSIf.js +1 -0
- package/client/dist/assets/vendor-markdown-CIVH08vJ.js +298 -0
- package/client/dist/assets/vendor-react-CHoMc7ka.js +8 -0
- package/client/dist/assets/vendor-syntax-Djb62v3a.js +9 -0
- package/client/dist/assets/vendor-xterm-DBb3RXlu.js +66 -0
- package/client/dist/assets/vendor-xterm-DrlLKa8f.css +1 -0
- package/client/dist/assets/vi-VN-M7AON7JQ-Dgc_SShk.js +5 -0
- package/client/dist/assets/xychartDiagram-e933f94c-BeyVBJhb.js +7 -0
- package/client/dist/assets/zh-CN-LNUGB5OW-MH4Yh8in.js +10 -0
- package/client/dist/assets/zh-HK-E62DVLB3-D4XHehjx.js +1 -0
- package/client/dist/assets/zh-TW-RAJ6MFWO--efj3evj.js +9 -0
- package/client/dist/clear-cache.html +85 -0
- package/client/dist/convert-icons.md +53 -0
- package/client/dist/favicon.png +0 -0
- package/client/dist/favicon.svg +9 -0
- package/client/dist/generate-icons.js +49 -0
- package/client/dist/icons/claude-ai-icon.svg +1 -0
- package/client/dist/icons/codex-white.svg +3 -0
- package/client/dist/icons/codex.svg +3 -0
- package/client/dist/icons/cursor-white.svg +12 -0
- package/client/dist/icons/cursor.svg +1 -0
- package/client/dist/icons/icon-128x128.png +0 -0
- package/client/dist/icons/icon-128x128.svg +12 -0
- package/client/dist/icons/icon-144x144.png +0 -0
- package/client/dist/icons/icon-144x144.svg +12 -0
- package/client/dist/icons/icon-152x152.png +0 -0
- package/client/dist/icons/icon-152x152.svg +12 -0
- package/client/dist/icons/icon-192x192.png +0 -0
- package/client/dist/icons/icon-192x192.svg +12 -0
- package/client/dist/icons/icon-384x384.png +0 -0
- package/client/dist/icons/icon-384x384.svg +12 -0
- package/client/dist/icons/icon-512x512.png +0 -0
- package/client/dist/icons/icon-512x512.svg +12 -0
- package/client/dist/icons/icon-72x72.png +0 -0
- package/client/dist/icons/icon-72x72.svg +12 -0
- package/client/dist/icons/icon-96x96.png +0 -0
- package/client/dist/icons/icon-96x96.svg +12 -0
- package/client/dist/icons/icon-template.svg +12 -0
- package/client/dist/index.html +128 -0
- package/client/dist/logo-128.png +0 -0
- package/client/dist/logo-256.png +0 -0
- package/client/dist/logo-32.png +0 -0
- package/client/dist/logo-512.png +0 -0
- package/client/dist/logo-64.png +0 -0
- package/client/dist/logo.svg +17 -0
- package/client/dist/manifest.json +61 -0
- package/client/dist/mcp-docs.html +119 -0
- package/client/dist/screenshots/cli-selection.png +0 -0
- package/client/dist/screenshots/desktop-main.png +0 -0
- package/client/dist/screenshots/mobile-chat.png +0 -0
- package/client/dist/screenshots/tools-modal.png +0 -0
- package/client/dist/sw.js +19 -0
- package/commands/upfynai-connect.md +46 -0
- package/commands/upfynai-disconnect.md +31 -0
- package/commands/upfynai-doctor.md +99 -0
- package/commands/upfynai-export.md +49 -0
- package/commands/upfynai-local.md +82 -0
- package/commands/upfynai-status.md +75 -0
- package/commands/upfynai-stop.md +49 -0
- package/commands/upfynai-uninstall.md +58 -0
- package/commands/upfynai.md +50 -0
- package/package.json +106 -47
- package/scripts/build-client.js +17 -0
- package/scripts/fix-node-pty.js +67 -0
- package/scripts/install-commands.js +78 -0
- package/server/claude-sdk.js +714 -0
- package/server/cli.js +419 -0
- package/server/constants/config.js +5 -0
- package/server/cursor-cli.js +270 -0
- package/server/database/auth.db +0 -0
- package/server/database/db.js +606 -0
- package/server/database/init.sql +70 -0
- package/server/index.js +2269 -0
- package/server/load-env.js +26 -0
- package/server/mcp-server.js +620 -0
- package/server/middleware/auth.js +158 -0
- package/server/openai-codex.js +403 -0
- package/server/projects.js +1849 -0
- package/server/relay-client.js +314 -0
- package/server/routes/agent.js +1231 -0
- package/server/routes/auth.js +220 -0
- package/server/routes/cli-auth.js +263 -0
- package/server/routes/codex.js +344 -0
- package/server/routes/commands.js +601 -0
- package/server/routes/cursor.js +808 -0
- package/server/routes/git.js +1165 -0
- package/server/routes/mcp-utils.js +48 -0
- package/server/routes/mcp.js +552 -0
- package/server/routes/payments.js +172 -0
- package/server/routes/projects.js +549 -0
- package/server/routes/settings.js +178 -0
- package/server/routes/taskmaster.js +1964 -0
- package/server/routes/user.js +106 -0
- package/server/utils/commandParser.js +303 -0
- package/server/utils/gitConfig.js +24 -0
- package/server/utils/mcp-detector.js +198 -0
- package/server/utils/taskmaster-websocket.js +129 -0
- package/shared/modelConstants.js +67 -0
- package/LICENSE +0 -22
- package/bin/cli.js +0 -86
- package/dist/assets/CanvasPanel-B48gAKVY.js +0 -538
- package/dist/assets/CanvasPanel-B48gAKVY.js.map +0 -1
- package/dist/assets/CanvasPanel-BsOG3EVs.css +0 -1
- package/dist/assets/index-CEhTwG68.css +0 -1
- package/dist/assets/index-GqAGWpJI.js +0 -70
- package/dist/assets/index-GqAGWpJI.js.map +0 -1
- package/dist/index.html +0 -18
- package/index.html +0 -17
- package/src/App.tsx +0 -226
- package/src/components/canvas/CanvasPanel.tsx +0 -62
- package/src/components/canvas/layout/graph-builder.ts +0 -136
- package/src/components/canvas/shapes/CompactionNodeShape.tsx +0 -76
- package/src/components/canvas/shapes/SessionNodeShape.tsx +0 -93
- package/src/components/canvas/shapes/StatuslineWidgetShape.tsx +0 -125
- package/src/components/canvas/shapes/TextResponseNodeShape.tsx +0 -86
- package/src/components/canvas/shapes/ToolCallNodeShape.tsx +0 -107
- package/src/components/canvas/shapes/ToolResultNodeShape.tsx +0 -87
- package/src/components/canvas/shapes/shared-styles.ts +0 -35
- package/src/components/chat/ChatPanel.tsx +0 -96
- package/src/components/chat/InputBar.tsx +0 -81
- package/src/components/chat/MessageList.tsx +0 -130
- package/src/components/chat/PermissionDialog.tsx +0 -70
- package/src/components/layout/FolderSelector.tsx +0 -152
- package/src/components/layout/ModelSelector.tsx +0 -65
- package/src/components/layout/SessionManager.tsx +0 -115
- package/src/components/statusline/StatuslineBar.tsx +0 -114
- package/src/main.tsx +0 -10
- package/src/server/claude-session.ts +0 -156
- package/src/server/index.ts +0 -149
- package/src/services/stream-consumer.ts +0 -330
- package/src/statusline-core/bin/statusline.sh +0 -121
- package/src/statusline-core/commands/sls-config.md +0 -42
- package/src/statusline-core/commands/sls-doctor.md +0 -35
- package/src/statusline-core/commands/sls-help.md +0 -48
- package/src/statusline-core/commands/sls-layout.md +0 -38
- package/src/statusline-core/commands/sls-preview.md +0 -34
- package/src/statusline-core/commands/sls-theme.md +0 -40
- package/src/statusline-core/installer.js +0 -228
- package/src/statusline-core/layouts/compact.sh +0 -21
- package/src/statusline-core/layouts/full.sh +0 -62
- package/src/statusline-core/layouts/standard.sh +0 -39
- package/src/statusline-core/lib/core.sh +0 -389
- package/src/statusline-core/lib/helpers.sh +0 -81
- package/src/statusline-core/lib/json-parser.sh +0 -71
- package/src/statusline-core/themes/catppuccin.sh +0 -32
- package/src/statusline-core/themes/default.sh +0 -37
- package/src/statusline-core/themes/gruvbox.sh +0 -32
- package/src/statusline-core/themes/nord.sh +0 -32
- package/src/statusline-core/themes/tokyo-night.sh +0 -32
- package/src/store/canvas-store.ts +0 -50
- package/src/store/chat-store.ts +0 -60
- package/src/store/permission-store.ts +0 -29
- package/src/store/session-store.ts +0 -52
- package/src/store/statusline-store.ts +0 -160
- package/src/styles/global.css +0 -117
- package/src/themes/index.ts +0 -149
- package/src/types/canvas-graph.ts +0 -24
- package/src/types/sdk-messages.ts +0 -156
- package/src/types/statusline-fields.ts +0 -67
- package/src/vite-env.d.ts +0 -1
- package/tsconfig.json +0 -26
- package/vite.config.ts +0 -24
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Load environment variables from .env before other imports execute.
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const envPath = path.join(__dirname, '../.env');
|
|
12
|
+
const envFile = fs.readFileSync(envPath, 'utf8');
|
|
13
|
+
envFile.split('\n').forEach(line => {
|
|
14
|
+
const trimmedLine = line.trim();
|
|
15
|
+
if (trimmedLine && !trimmedLine.startsWith('#')) {
|
|
16
|
+
const [key, ...valueParts] = trimmedLine.split('=');
|
|
17
|
+
if (key && valueParts.length > 0 && !process.env[key]) {
|
|
18
|
+
process.env[key] = valueParts.join('=').trim();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
} catch (e) {
|
|
23
|
+
if (!process.env.VERCEL) {
|
|
24
|
+
console.log('No .env file found or error reading it:', e.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upfyn-Code MCP Server
|
|
3
|
+
*
|
|
4
|
+
* Exposes the app's capabilities as MCP tools and resources so any MCP-compatible
|
|
5
|
+
* client (ChatGPT, Claude Desktop, Cursor, etc.) can control the app.
|
|
6
|
+
*
|
|
7
|
+
* Transport: Streamable HTTP mounted at /mcp on the Express server
|
|
8
|
+
*
|
|
9
|
+
* Tools:
|
|
10
|
+
* - send-prompt: Send a message to Claude and get a response
|
|
11
|
+
* - list-projects: List all available projects
|
|
12
|
+
* - list-sessions: List sessions for a project
|
|
13
|
+
* - get-session-messages: Get messages from a session
|
|
14
|
+
* - get-canvas-state: Get current canvas nodes
|
|
15
|
+
* - add-canvas-node: Add a node to the canvas
|
|
16
|
+
* - clear-canvas: Clear all canvas nodes
|
|
17
|
+
* - create-session: Start a new Claude session
|
|
18
|
+
* - abort-session: Stop an active session
|
|
19
|
+
* - read-file: Read a file from a project
|
|
20
|
+
* - list-files: List files in a project directory
|
|
21
|
+
*
|
|
22
|
+
* Resources:
|
|
23
|
+
* - upfynai://canvas/state: Current canvas state
|
|
24
|
+
* - upfynai://sessions/active: Active sessions list
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
28
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
29
|
+
import { z } from 'zod';
|
|
30
|
+
import crypto from 'crypto';
|
|
31
|
+
import jwt from 'jsonwebtoken';
|
|
32
|
+
import { userDb, apiKeysDb, relayTokensDb } from './database/db.js';
|
|
33
|
+
|
|
34
|
+
const JWT_SECRET = process.env.JWT_SECRET?.trim() || (() => { throw new Error('JWT_SECRET required'); })();
|
|
35
|
+
|
|
36
|
+
// In-memory canvas state (Excalidraw elements, synced via WebSocket with browser clients)
|
|
37
|
+
let canvasElements = [];
|
|
38
|
+
const canvasListeners = new Set();
|
|
39
|
+
|
|
40
|
+
export function getCanvasElements() {
|
|
41
|
+
return canvasElements;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function setCanvasElements(elements) {
|
|
45
|
+
canvasElements = elements;
|
|
46
|
+
notifyCanvasListeners();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function addCanvasElement(element) {
|
|
50
|
+
canvasElements.push(element);
|
|
51
|
+
notifyCanvasListeners();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function clearCanvas() {
|
|
55
|
+
canvasElements = [];
|
|
56
|
+
notifyCanvasListeners();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function onCanvasChange(listener) {
|
|
60
|
+
canvasListeners.add(listener);
|
|
61
|
+
return () => canvasListeners.delete(listener);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function notifyCanvasListeners() {
|
|
65
|
+
for (const listener of canvasListeners) {
|
|
66
|
+
try { listener(canvasElements); } catch (e) { /* ignore */ }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Helper: create an Excalidraw rectangle + text element pair
|
|
71
|
+
function createExcalidrawNote(text, { x = 100, y = 100, width = 300, height = 100, label = '' } = {}) {
|
|
72
|
+
const id = `el-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
73
|
+
const textId = `${id}-text`;
|
|
74
|
+
const fullText = label ? `${label}\n\n${text}` : text;
|
|
75
|
+
|
|
76
|
+
const rect = {
|
|
77
|
+
id,
|
|
78
|
+
type: 'rectangle',
|
|
79
|
+
x,
|
|
80
|
+
y,
|
|
81
|
+
width,
|
|
82
|
+
height,
|
|
83
|
+
strokeColor: '#a855f7',
|
|
84
|
+
backgroundColor: '#1a1a2e',
|
|
85
|
+
fillStyle: 'solid',
|
|
86
|
+
strokeWidth: 2,
|
|
87
|
+
roughness: 0,
|
|
88
|
+
opacity: 100,
|
|
89
|
+
angle: 0,
|
|
90
|
+
groupIds: [],
|
|
91
|
+
roundness: { type: 3 },
|
|
92
|
+
boundElements: [{ id: textId, type: 'text' }],
|
|
93
|
+
isDeleted: false,
|
|
94
|
+
version: 1,
|
|
95
|
+
versionNonce: Math.floor(Math.random() * 1e9),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const textEl = {
|
|
99
|
+
id: textId,
|
|
100
|
+
type: 'text',
|
|
101
|
+
x: x + 10,
|
|
102
|
+
y: y + 10,
|
|
103
|
+
width: width - 20,
|
|
104
|
+
height: height - 20,
|
|
105
|
+
text: fullText,
|
|
106
|
+
fontSize: 16,
|
|
107
|
+
fontFamily: 1,
|
|
108
|
+
textAlign: 'left',
|
|
109
|
+
verticalAlign: 'top',
|
|
110
|
+
containerId: id,
|
|
111
|
+
originalText: fullText,
|
|
112
|
+
isDeleted: false,
|
|
113
|
+
version: 1,
|
|
114
|
+
versionNonce: Math.floor(Math.random() * 1e9),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
return [rect, textEl];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create and configure the MCP server with all tools and resources
|
|
122
|
+
*/
|
|
123
|
+
export function createMcpServer({ getProjects, getSessions, getSessionMessages, queryClaudeSDK, abortClaudeSDKSession, getActiveClaudeSDKSessions, connectedClients }) {
|
|
124
|
+
const server = new McpServer({
|
|
125
|
+
name: 'upfynai-code',
|
|
126
|
+
version: '2.0.0',
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ═══════════════════════════════════════════
|
|
130
|
+
// TOOLS
|
|
131
|
+
// ═══════════════════════════════════════════
|
|
132
|
+
|
|
133
|
+
// Send a prompt to Claude
|
|
134
|
+
server.tool(
|
|
135
|
+
'send-prompt',
|
|
136
|
+
'Send a message to Claude and get a streaming response. The response will appear on the canvas and in chat.',
|
|
137
|
+
{
|
|
138
|
+
prompt: z.string().describe('The message to send to Claude'),
|
|
139
|
+
projectPath: z.string().optional().describe('Project directory path for context'),
|
|
140
|
+
sessionId: z.string().optional().describe('Session ID to resume, or omit for new session'),
|
|
141
|
+
model: z.string().optional().describe('Model to use (e.g. claude-sonnet-4-5-20250929)'),
|
|
142
|
+
},
|
|
143
|
+
async ({ prompt, projectPath, sessionId, model }) => {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
const responseChunks = [];
|
|
146
|
+
let resolved = false;
|
|
147
|
+
|
|
148
|
+
// Create a mock WebSocket writer that collects the response
|
|
149
|
+
const mockWs = {
|
|
150
|
+
readyState: 1, // WebSocket.OPEN
|
|
151
|
+
send: (data) => {
|
|
152
|
+
try {
|
|
153
|
+
const msg = typeof data === 'string' ? JSON.parse(data) : data;
|
|
154
|
+
if (msg.type === 'claude-response' && msg.data?.text) {
|
|
155
|
+
responseChunks.push(msg.data.text);
|
|
156
|
+
}
|
|
157
|
+
if (msg.type === 'claude-complete' && !resolved) {
|
|
158
|
+
resolved = true;
|
|
159
|
+
const fullText = responseChunks.join('');
|
|
160
|
+
// Add to canvas as Excalidraw elements
|
|
161
|
+
const yOffset = canvasElements.length * 60;
|
|
162
|
+
const els = createExcalidrawNote(fullText, { y: 100 + yOffset, label: 'Claude' });
|
|
163
|
+
els.forEach(el => addCanvasElement(el));
|
|
164
|
+
// Broadcast canvas update to browser clients
|
|
165
|
+
broadcastToClients(connectedClients, {
|
|
166
|
+
type: 'canvas-update',
|
|
167
|
+
elements: canvasElements,
|
|
168
|
+
});
|
|
169
|
+
resolve({
|
|
170
|
+
content: [{ type: 'text', text: fullText || 'No response received.' }],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (msg.type === 'error' && !resolved) {
|
|
174
|
+
resolved = true;
|
|
175
|
+
resolve({
|
|
176
|
+
content: [{ type: 'text', text: `Error: ${msg.error || 'Unknown error'}` }],
|
|
177
|
+
isError: true,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
} catch (e) { /* ignore parse errors */ }
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Add user prompt to canvas as Excalidraw elements
|
|
185
|
+
const userYOffset = canvasElements.length * 60;
|
|
186
|
+
const userEls = createExcalidrawNote(prompt, { y: 100 + userYOffset, label: 'You (MCP)' });
|
|
187
|
+
userEls.forEach(el => addCanvasElement(el));
|
|
188
|
+
|
|
189
|
+
// Broadcast the user elements
|
|
190
|
+
broadcastToClients(connectedClients, {
|
|
191
|
+
type: 'canvas-update',
|
|
192
|
+
elements: canvasElements,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const options = {
|
|
196
|
+
projectPath: projectPath || process.cwd(),
|
|
197
|
+
cwd: projectPath || process.cwd(),
|
|
198
|
+
sessionId: sessionId || undefined,
|
|
199
|
+
resume: Boolean(sessionId),
|
|
200
|
+
model: model || undefined,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
queryClaudeSDK(prompt, options, mockWs).catch((err) => {
|
|
204
|
+
if (!resolved) {
|
|
205
|
+
resolved = true;
|
|
206
|
+
resolve({
|
|
207
|
+
content: [{ type: 'text', text: `SDK Error: ${err.message}` }],
|
|
208
|
+
isError: true,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Safety timeout
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
if (!resolved) {
|
|
216
|
+
resolved = true;
|
|
217
|
+
const partial = responseChunks.join('');
|
|
218
|
+
resolve({
|
|
219
|
+
content: [{ type: 'text', text: partial || 'Response timed out after 5 minutes.' }],
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}, 300000);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// List projects
|
|
228
|
+
server.tool(
|
|
229
|
+
'list-projects',
|
|
230
|
+
'List all available projects that Claude can work on',
|
|
231
|
+
{},
|
|
232
|
+
async () => {
|
|
233
|
+
try {
|
|
234
|
+
const projects = await getProjects();
|
|
235
|
+
const summary = projects.map((p) => ({
|
|
236
|
+
name: p.name,
|
|
237
|
+
displayName: p.displayName || p.name,
|
|
238
|
+
path: p.fullPath || p.path,
|
|
239
|
+
sessions: (p.sessions || []).length,
|
|
240
|
+
}));
|
|
241
|
+
return {
|
|
242
|
+
content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }],
|
|
243
|
+
};
|
|
244
|
+
} catch (err) {
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: 'text', text: `Error listing projects: ${err.message}` }],
|
|
247
|
+
isError: true,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// List sessions
|
|
254
|
+
server.tool(
|
|
255
|
+
'list-sessions',
|
|
256
|
+
'List sessions for a project',
|
|
257
|
+
{
|
|
258
|
+
projectName: z.string().describe('Project name to list sessions for'),
|
|
259
|
+
limit: z.number().optional().describe('Max sessions to return (default 10)'),
|
|
260
|
+
},
|
|
261
|
+
async ({ projectName, limit }) => {
|
|
262
|
+
try {
|
|
263
|
+
const sessions = await getSessions(projectName, limit || 10, 0);
|
|
264
|
+
return {
|
|
265
|
+
content: [{ type: 'text', text: JSON.stringify(sessions, null, 2) }],
|
|
266
|
+
};
|
|
267
|
+
} catch (err) {
|
|
268
|
+
return {
|
|
269
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
270
|
+
isError: true,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// Get session messages
|
|
277
|
+
server.tool(
|
|
278
|
+
'get-session-messages',
|
|
279
|
+
'Get message history from a specific session',
|
|
280
|
+
{
|
|
281
|
+
projectName: z.string().describe('Project name'),
|
|
282
|
+
sessionId: z.string().describe('Session ID'),
|
|
283
|
+
limit: z.number().optional().describe('Max messages (default 50)'),
|
|
284
|
+
},
|
|
285
|
+
async ({ projectName, sessionId, limit }) => {
|
|
286
|
+
try {
|
|
287
|
+
const messages = await getSessionMessages(projectName, sessionId, limit || 50, 0);
|
|
288
|
+
return {
|
|
289
|
+
content: [{ type: 'text', text: JSON.stringify(messages, null, 2) }],
|
|
290
|
+
};
|
|
291
|
+
} catch (err) {
|
|
292
|
+
return {
|
|
293
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
294
|
+
isError: true,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
// Get canvas state
|
|
301
|
+
server.tool(
|
|
302
|
+
'get-canvas-state',
|
|
303
|
+
'Get the current Upfyn-Canvas state including all elements',
|
|
304
|
+
{},
|
|
305
|
+
async () => {
|
|
306
|
+
return {
|
|
307
|
+
content: [{
|
|
308
|
+
type: 'text',
|
|
309
|
+
text: JSON.stringify({
|
|
310
|
+
elementCount: canvasElements.length,
|
|
311
|
+
elements: canvasElements,
|
|
312
|
+
}, null, 2),
|
|
313
|
+
}],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// Add canvas node (creates Upfyn-Canvas rectangle + text)
|
|
319
|
+
server.tool(
|
|
320
|
+
'add-canvas-node',
|
|
321
|
+
'Add a visual note to the Upfyn-Canvas whiteboard (rectangle with text)',
|
|
322
|
+
{
|
|
323
|
+
text: z.string().describe('Text content for the note'),
|
|
324
|
+
label: z.string().optional().describe('Label for the note (default: "MCP Note")'),
|
|
325
|
+
x: z.number().optional().describe('X position (default: 100)'),
|
|
326
|
+
y: z.number().optional().describe('Y position (default: auto)'),
|
|
327
|
+
},
|
|
328
|
+
async ({ text, label, x, y }) => {
|
|
329
|
+
const yPos = y ?? (100 + canvasElements.length * 60);
|
|
330
|
+
const els = createExcalidrawNote(text, { x: x ?? 100, y: yPos, label: label || 'MCP Note' });
|
|
331
|
+
els.forEach(el => addCanvasElement(el));
|
|
332
|
+
broadcastToClients(connectedClients, {
|
|
333
|
+
type: 'canvas-update',
|
|
334
|
+
elements: canvasElements,
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
content: [{ type: 'text', text: `Note added to canvas: ${els[0].id}` }],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// Clear canvas
|
|
343
|
+
server.tool(
|
|
344
|
+
'clear-canvas',
|
|
345
|
+
'Clear all elements from the Upfyn-Canvas whiteboard',
|
|
346
|
+
{},
|
|
347
|
+
async () => {
|
|
348
|
+
clearCanvas();
|
|
349
|
+
broadcastToClients(connectedClients, {
|
|
350
|
+
type: 'canvas-update',
|
|
351
|
+
elements: [],
|
|
352
|
+
});
|
|
353
|
+
return {
|
|
354
|
+
content: [{ type: 'text', text: 'Canvas cleared.' }],
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
// Update full canvas scene
|
|
360
|
+
server.tool(
|
|
361
|
+
'update-canvas-scene',
|
|
362
|
+
'Replace the entire Upfyn-Canvas with new elements',
|
|
363
|
+
{
|
|
364
|
+
elements: z.array(z.object({}).passthrough()).describe('Array of canvas elements'),
|
|
365
|
+
},
|
|
366
|
+
async ({ elements }) => {
|
|
367
|
+
setCanvasElements(elements);
|
|
368
|
+
broadcastToClients(connectedClients, {
|
|
369
|
+
type: 'canvas-update',
|
|
370
|
+
elements: canvasElements,
|
|
371
|
+
});
|
|
372
|
+
return {
|
|
373
|
+
content: [{ type: 'text', text: `Canvas updated with ${elements.length} elements.` }],
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
// Get active sessions
|
|
379
|
+
server.tool(
|
|
380
|
+
'get-active-sessions',
|
|
381
|
+
'Get list of currently active Claude sessions',
|
|
382
|
+
{},
|
|
383
|
+
async () => {
|
|
384
|
+
try {
|
|
385
|
+
const sessions = getActiveClaudeSDKSessions();
|
|
386
|
+
return {
|
|
387
|
+
content: [{ type: 'text', text: JSON.stringify(sessions, null, 2) }],
|
|
388
|
+
};
|
|
389
|
+
} catch (err) {
|
|
390
|
+
return {
|
|
391
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
392
|
+
isError: true,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// Abort session
|
|
399
|
+
server.tool(
|
|
400
|
+
'abort-session',
|
|
401
|
+
'Stop an active Claude session',
|
|
402
|
+
{
|
|
403
|
+
sessionId: z.string().describe('Session ID to abort'),
|
|
404
|
+
},
|
|
405
|
+
async ({ sessionId }) => {
|
|
406
|
+
try {
|
|
407
|
+
const result = await abortClaudeSDKSession(sessionId);
|
|
408
|
+
return {
|
|
409
|
+
content: [{ type: 'text', text: result ? `Session ${sessionId} aborted.` : `Session ${sessionId} not found or already stopped.` }],
|
|
410
|
+
};
|
|
411
|
+
} catch (err) {
|
|
412
|
+
return {
|
|
413
|
+
content: [{ type: 'text', text: `Error: ${err.message}` }],
|
|
414
|
+
isError: true,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// ═══════════════════════════════════════════
|
|
421
|
+
// RESOURCES
|
|
422
|
+
// ═══════════════════════════════════════════
|
|
423
|
+
|
|
424
|
+
server.resource(
|
|
425
|
+
'canvas-state',
|
|
426
|
+
'upfynai://canvas/state',
|
|
427
|
+
{
|
|
428
|
+
description: 'Current Upfyn-Canvas state with all elements',
|
|
429
|
+
mimeType: 'application/json',
|
|
430
|
+
},
|
|
431
|
+
async (uri) => ({
|
|
432
|
+
contents: [{
|
|
433
|
+
uri: uri.href,
|
|
434
|
+
text: JSON.stringify({ elementCount: canvasElements.length, elements: canvasElements }, null, 2),
|
|
435
|
+
}],
|
|
436
|
+
})
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
server.resource(
|
|
440
|
+
'active-sessions',
|
|
441
|
+
'upfynai://sessions/active',
|
|
442
|
+
{
|
|
443
|
+
description: 'Currently active Claude sessions',
|
|
444
|
+
mimeType: 'application/json',
|
|
445
|
+
},
|
|
446
|
+
async (uri) => ({
|
|
447
|
+
contents: [{
|
|
448
|
+
uri: uri.href,
|
|
449
|
+
text: JSON.stringify(getActiveClaudeSDKSessions(), null, 2),
|
|
450
|
+
}],
|
|
451
|
+
})
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
return server;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Mount the MCP server on an Express app at /mcp
|
|
459
|
+
*/
|
|
460
|
+
export async function mountMcpServer(app, mcpServer, mcpServerFactory) {
|
|
461
|
+
// On Vercel serverless, in-memory session state is lost between invocations.
|
|
462
|
+
// Use per-request stateless MCP servers on Vercel; session-based on local.
|
|
463
|
+
const isServerless = !!process.env.VERCEL;
|
|
464
|
+
const transports = new Map();
|
|
465
|
+
|
|
466
|
+
// MCP authentication middleware — cookie → Bearer → API key → query param
|
|
467
|
+
const authenticateMcp = async (req, res, next) => {
|
|
468
|
+
// 1. Try httpOnly cookie (browser sessions)
|
|
469
|
+
if (req.cookies?.session) {
|
|
470
|
+
try {
|
|
471
|
+
const decoded = jwt.verify(req.cookies.session, JWT_SECRET);
|
|
472
|
+
const user = await userDb.getUserById(decoded.userId);
|
|
473
|
+
if (user) { req.user = user; return next(); }
|
|
474
|
+
} catch (e) { /* fall through */ }
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 2. Try Bearer token — supports JWT, relay token (upfyn_/rt_), or API key (up-cli-)
|
|
478
|
+
const authHeader = req.headers['authorization'];
|
|
479
|
+
const token = authHeader && authHeader.split(' ')[1];
|
|
480
|
+
if (token) {
|
|
481
|
+
// 2a. Relay token (upfyn_xxx or legacy rt_xxx) — same token used for CLI connect
|
|
482
|
+
if (token.startsWith('upfyn_') || token.startsWith('rt_')) {
|
|
483
|
+
try {
|
|
484
|
+
const tokenData = await relayTokensDb.validateToken(token);
|
|
485
|
+
if (tokenData) {
|
|
486
|
+
const user = await userDb.getUserById(tokenData.user_id);
|
|
487
|
+
if (user) { req.user = user; return next(); }
|
|
488
|
+
}
|
|
489
|
+
} catch (e) { /* fall through */ }
|
|
490
|
+
}
|
|
491
|
+
// 2b. API key (up-cli-xxx)
|
|
492
|
+
if (token.startsWith('up-cli-')) {
|
|
493
|
+
try {
|
|
494
|
+
const user = await apiKeysDb.validateApiKey(token);
|
|
495
|
+
if (user) { req.user = user; return next(); }
|
|
496
|
+
} catch (e) { /* fall through */ }
|
|
497
|
+
}
|
|
498
|
+
// 2c. JWT token
|
|
499
|
+
try {
|
|
500
|
+
const decoded = jwt.verify(token, JWT_SECRET);
|
|
501
|
+
const user = await userDb.getUserById(decoded.userId);
|
|
502
|
+
if (user) { req.user = user; return next(); }
|
|
503
|
+
} catch (e) { /* fall through */ }
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// 3. Try API key header (MCP clients: ChatGPT, Claude Desktop, Cursor, etc.)
|
|
507
|
+
const apiKey = req.headers['x-api-key'];
|
|
508
|
+
if (apiKey) {
|
|
509
|
+
try {
|
|
510
|
+
const user = await apiKeysDb.validateApiKey(apiKey);
|
|
511
|
+
if (user) { req.user = user; return next(); }
|
|
512
|
+
} catch (e) { /* ignore */ }
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 4. Try query param token (SSE EventSource fallback)
|
|
516
|
+
if (req.query.token) {
|
|
517
|
+
try {
|
|
518
|
+
const decoded = jwt.verify(req.query.token, JWT_SECRET);
|
|
519
|
+
const user = await userDb.getUserById(decoded.userId);
|
|
520
|
+
if (user) { req.user = user; return next(); }
|
|
521
|
+
} catch (e) { /* ignore */ }
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
res.status(401).json({ error: 'Authentication required. Use Authorization: Bearer <jwt>, x-api-key header, or up-cli- API key.' });
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// Handle MCP requests at /mcp endpoint
|
|
528
|
+
app.post('/mcp', authenticateMcp, async (req, res) => {
|
|
529
|
+
try {
|
|
530
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
531
|
+
let transport;
|
|
532
|
+
|
|
533
|
+
if (!isServerless && sessionId && transports.has(sessionId)) {
|
|
534
|
+
// Local: reuse existing session transport
|
|
535
|
+
transport = transports.get(sessionId);
|
|
536
|
+
} else {
|
|
537
|
+
// Create a fresh MCP server + transport per request on serverless,
|
|
538
|
+
// or a new session on local
|
|
539
|
+
const server = isServerless && mcpServerFactory ? mcpServerFactory() : mcpServer;
|
|
540
|
+
|
|
541
|
+
// On serverless: no sessionIdGenerator → disables session validation
|
|
542
|
+
// entirely, so requests don't need the initialize handshake.
|
|
543
|
+
// On local: sessionIdGenerator enables session tracking.
|
|
544
|
+
transport = new StreamableHTTPServerTransport({
|
|
545
|
+
sessionIdGenerator: isServerless ? undefined : (() => crypto.randomUUID()),
|
|
546
|
+
stateless: isServerless,
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
transport.onclose = () => {
|
|
550
|
+
const sid = transport.sessionId;
|
|
551
|
+
if (sid) transports.delete(sid);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
await server.connect(transport);
|
|
555
|
+
|
|
556
|
+
if (!isServerless && transport.sessionId) {
|
|
557
|
+
transports.set(transport.sessionId, transport);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Express already consumed the raw body stream, so we must pass the
|
|
562
|
+
// parsed body as the 3rd arg to avoid "Parse error: Invalid JSON"
|
|
563
|
+
await transport.handleRequest(req, res, req.body);
|
|
564
|
+
} catch (err) {
|
|
565
|
+
console.error('[MCP] Error handling POST:', err.message);
|
|
566
|
+
if (!res.headersSent) {
|
|
567
|
+
res.status(500).json({ error: 'MCP server error', details: err.message });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// SSE stream for server-initiated messages (local only — serverless is stateless)
|
|
573
|
+
app.get('/mcp', authenticateMcp, async (req, res) => {
|
|
574
|
+
if (isServerless) {
|
|
575
|
+
return res.status(405).json({ error: 'SSE not supported on serverless. Use stateless POST requests.' });
|
|
576
|
+
}
|
|
577
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
578
|
+
if (!sessionId || !transports.has(sessionId)) {
|
|
579
|
+
res.status(400).json({ error: 'Invalid or missing session ID' });
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const transport = transports.get(sessionId);
|
|
583
|
+
await transport.handleRequest(req, res);
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
// Session termination
|
|
587
|
+
app.delete('/mcp', authenticateMcp, async (req, res) => {
|
|
588
|
+
if (isServerless) {
|
|
589
|
+
return res.json({ ok: true });
|
|
590
|
+
}
|
|
591
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
592
|
+
if (sessionId && transports.has(sessionId)) {
|
|
593
|
+
const transport = transports.get(sessionId);
|
|
594
|
+
await transport.handleRequest(req, res);
|
|
595
|
+
transports.delete(sessionId);
|
|
596
|
+
} else {
|
|
597
|
+
res.status(404).json({ error: 'Session not found' });
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
console.log(`${c_info('[MCP]')} MCP server mounted at /mcp (${isServerless ? 'stateless/serverless' : 'session-based'})`);
|
|
602
|
+
console.log(`${c_info('[MCP]')} Tools: send-prompt, list-projects, list-sessions, get-session-messages, get-canvas-state, add-canvas-node, clear-canvas, update-canvas-scene, get-active-sessions, abort-session`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Simple color helper (avoids importing from index.js)
|
|
606
|
+
function c_info(text) {
|
|
607
|
+
return `\x1b[36m${text}\x1b[0m`;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function broadcastToClients(clients, message) {
|
|
611
|
+
if (!clients || clients.size === 0) return;
|
|
612
|
+
const data = JSON.stringify(message);
|
|
613
|
+
for (const client of clients) {
|
|
614
|
+
try {
|
|
615
|
+
if (client.readyState === 1) {
|
|
616
|
+
client.send(data);
|
|
617
|
+
}
|
|
618
|
+
} catch (e) { /* ignore */ }
|
|
619
|
+
}
|
|
620
|
+
}
|