upfynai-code 2.6.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -88
- package/bin/cli.js +63 -0
- package/package.json +48 -106
- package/src/auth.js +115 -0
- package/src/config.js +33 -0
- package/src/connect.js +314 -0
- package/src/launch.js +54 -0
- package/src/mcp.js +57 -0
- package/src/server.js +54 -0
- package/client/dist/api-docs.html +0 -879
- package/client/dist/assets/AppContent-C0CyP3g5.js +0 -513
- package/client/dist/assets/CanvasPanel-0u9QR7U-.js +0 -34
- package/client/dist/assets/CanvasPanel-WhZulBJw.css +0 -1
- package/client/dist/assets/DashboardPanel-Dgqw1yZk.js +0 -1
- 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-CZDEzqjK.js +0 -19
- package/client/dist/assets/MarkdownPreview-CYdvwJaV.js +0 -1
- package/client/dist/assets/Onboarding-DR6NZ4Vz.js +0 -1
- package/client/dist/assets/SetupForm-D49gtWY4.js +0 -1
- package/client/dist/assets/Tableau10-B-NsZVaP.js +0 -1
- package/client/dist/assets/WorkflowsPanel-CqlbEJA_.js +0 -1
- package/client/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +0 -1
- package/client/dist/assets/ar-SA-G6X2FPQ2-BWqa1yBH.js +0 -10
- package/client/dist/assets/arc-BegSKqEW.js +0 -1
- package/client/dist/assets/array-BKyUJesY.js +0 -1
- package/client/dist/assets/az-AZ-76LH7QW2-DrVlbZDP.js +0 -1
- package/client/dist/assets/bg-BG-XCXSNQG7-DdunjBgT.js +0 -5
- package/client/dist/assets/blockDiagram-38ab4fdb-BKMbwGHu.js +0 -118
- package/client/dist/assets/bn-BD-2XOGV67Q-_7DtmvwO.js +0 -5
- package/client/dist/assets/c4Diagram-3d4e48cf-hJuiHhSn.js +0 -10
- package/client/dist/assets/ca-ES-6MX7JW3Y-BFIrmojG.js +0 -8
- package/client/dist/assets/channel-Bur-rRTp.js +0 -1
- package/client/dist/assets/classDiagram-70f12bd4-BjiAf9cM.js +0 -2
- package/client/dist/assets/classDiagram-v2-f2320105-pwBewejc.js +0 -2
- package/client/dist/assets/clone-BtqXeoBJ.js +0 -1
- package/client/dist/assets/createText-2e5e7dd3-Dq_acOWe.js +0 -5
- package/client/dist/assets/cs-CZ-2BRQDIVT-B-x4F6TJ.js +0 -11
- package/client/dist/assets/da-DK-5WZEPLOC-Btlc8Dgn.js +0 -5
- package/client/dist/assets/de-DE-XR44H4JA-BVu3ZIoD.js +0 -8
- package/client/dist/assets/directory-open-01563666-DWU9wJ6I.js +0 -1
- package/client/dist/assets/directory-open-4ed118d0-CunoC1EB.js +0 -1
- package/client/dist/assets/edges-e0da2a9e-DH0wVTXR.js +0 -4
- package/client/dist/assets/el-GR-BZB4AONW-h2ll8_ZC.js +0 -10
- package/client/dist/assets/erDiagram-9861fffd-BYezLIR7.js +0 -51
- package/client/dist/assets/es-ES-U4NZUMDT-Cveiulwt.js +0 -9
- package/client/dist/assets/eu-ES-A7QVB2H4-DQluL2PY.js +0 -11
- package/client/dist/assets/fa-IR-HGAKTJCU-BJtcMBSv.js +0 -8
- package/client/dist/assets/fi-FI-Z5N7JZ37-D8NfbVXV.js +0 -6
- package/client/dist/assets/file-open-002ab408-DIuFHtCF.js +0 -1
- package/client/dist/assets/file-open-7c801643-684qeFg4.js +0 -1
- package/client/dist/assets/file-save-3189631c-C1wFhQhH.js +0 -1
- package/client/dist/assets/file-save-745eba88-Bb9F9Kg7.js +0 -1
- package/client/dist/assets/flowDb-956e92f1-scnUykhM.js +0 -10
- package/client/dist/assets/flowDiagram-66a62f08-jVyWsfyU.js +0 -4
- package/client/dist/assets/flowDiagram-v2-96b9c2cf-N6xgi25h.js +0 -1
- package/client/dist/assets/flowchart-elk-definition-4a651766-gKGX3HqR.js +0 -139
- package/client/dist/assets/fr-FR-RHASNOE6-vdj42kC6.js +0 -9
- package/client/dist/assets/ganttDiagram-c361ad54-C2CiWFUP.js +0 -257
- package/client/dist/assets/gitGraphDiagram-72cf32ee-C59Yz2LK.js +0 -70
- package/client/dist/assets/gl-ES-HMX3MZ6V-DQo0TzoP.js +0 -10
- package/client/dist/assets/graph-Dx_H43Kv.js +0 -1
- package/client/dist/assets/he-IL-6SHJWFNN-DKXK5e33.js +0 -10
- package/client/dist/assets/hi-IN-IWLTKZ5I-C2Qgqc0R.js +0 -4
- package/client/dist/assets/hu-HU-A5ZG7DT2-Ss-6vX0m.js +0 -7
- package/client/dist/assets/id-ID-SAP4L64H-D7Wsg1S2.js +0 -10
- package/client/dist/assets/image-blob-reduce.esm-D6s-rqMO.js +0 -7
- package/client/dist/assets/index-3862675e-u8Nv7hHC.js +0 -1
- package/client/dist/assets/index-BVowJdZF.js +0 -97
- package/client/dist/assets/index-ce18TYkg.js +0 -27
- package/client/dist/assets/index-kQoJx-bc.css +0 -1
- package/client/dist/assets/infoDiagram-f8f76790-LmoJYsxo.js +0 -7
- package/client/dist/assets/init-Gi6I4Gst.js +0 -1
- package/client/dist/assets/it-IT-JPQ66NNP-CAPTVl7M.js +0 -11
- package/client/dist/assets/ja-JP-DBVTYXUO-eNVPawR2.js +0 -8
- package/client/dist/assets/journeyDiagram-49397b02-BaJqehpR.js +0 -139
- package/client/dist/assets/kaa-6HZHGXH3-tpuNkKhS.js +0 -1
- package/client/dist/assets/kab-KAB-ZGHBKWFO-Dp83kx4x.js +0 -8
- package/client/dist/assets/kk-KZ-P5N5QNE5-B9IlC6YN.js +0 -1
- package/client/dist/assets/km-KH-HSX4SM5Z-B_KMYaMj.js +0 -11
- package/client/dist/assets/ko-KR-MTYHY66A-yebnUNdb.js +0 -9
- package/client/dist/assets/ku-TR-6OUDTVRD-BR6fh6-5.js +0 -9
- package/client/dist/assets/layout-DLl5Jwcl.js +0 -1
- package/client/dist/assets/line-FpB7omSK.js +0 -1
- package/client/dist/assets/linear-CkXqUFJ8.js +0 -1
- package/client/dist/assets/lt-LT-XHIRWOB4-SutZSWtR.js +0 -3
- package/client/dist/assets/lv-LV-5QDEKY6T-DuAxdcZL.js +0 -7
- package/client/dist/assets/mindmap-definition-fc14e90a-DyxXOExh.js +0 -425
- package/client/dist/assets/mr-IN-CRQNXWMA-DqDUWM_8.js +0 -13
- package/client/dist/assets/my-MM-5M5IBNSE-C40kMFMR.js +0 -1
- package/client/dist/assets/nb-NO-T6EIAALU-DVij32Ju.js +0 -10
- package/client/dist/assets/nl-NL-IS3SIHDZ-rT84mDYq.js +0 -8
- package/client/dist/assets/nn-NO-6E72VCQL-BBZXBW8V.js +0 -8
- package/client/dist/assets/oc-FR-POXYY2M6-DzjOugOf.js +0 -8
- package/client/dist/assets/ordinal-Cboi1Yqb.js +0 -1
- package/client/dist/assets/pa-IN-N4M65BXN-DD1iU8_F.js +0 -4
- package/client/dist/assets/path-CbwjOpE9.js +0 -1
- package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
- package/client/dist/assets/pdf.worker-BA9kU3Pw.mjs +0 -61080
- package/client/dist/assets/percentages-BXMCSKIN-WVlHS4wx.js +0 -207
- package/client/dist/assets/pica-CQIY57Tf.js +0 -7
- package/client/dist/assets/pieDiagram-8a3498a8-Dd_85qBH.js +0 -35
- package/client/dist/assets/pl-PL-T2D74RX3-ukVXa48G.js +0 -9
- package/client/dist/assets/pt-BR-5N22H2LF-BibawarT.js +0 -9
- package/client/dist/assets/pt-PT-UZXXM6DQ-So3i9l9w.js +0 -9
- package/client/dist/assets/quadrantDiagram-120e2f19-C4dFVDEx.js +0 -7
- package/client/dist/assets/requirementDiagram-deff3bca-DrTO7yFl.js +0 -52
- package/client/dist/assets/ro-RO-JPDTUUEW-DY0Xq_Hd.js +0 -11
- package/client/dist/assets/roundRect-0PYZxl1G.js +0 -1
- package/client/dist/assets/ru-RU-B4JR7IUQ-B7u_Zvkd.js +0 -9
- package/client/dist/assets/sankeyDiagram-04a897e0-D24gfzuS.js +0 -8
- package/client/dist/assets/sequenceDiagram-704730f1-Dgji2XLQ.js +0 -122
- package/client/dist/assets/si-LK-N5RQ5JYF-OejsLzQ_.js +0 -1
- package/client/dist/assets/sk-SK-C5VTKIMK-_vy2Bt-M.js +0 -6
- package/client/dist/assets/sl-SI-NN7IZMDC-DKOl_u2M.js +0 -6
- package/client/dist/assets/stateDiagram-587899a1-CJ8eBaiU.js +0 -1
- package/client/dist/assets/stateDiagram-v2-d93cdb3a-C5K3l-Nt.js +0 -1
- package/client/dist/assets/styles-6aaf32cf-DAKE0jbx.js +0 -207
- package/client/dist/assets/styles-9a916d00-LFAJCgEy.js +0 -160
- package/client/dist/assets/styles-c10674c1-CllKO8NG.js +0 -116
- package/client/dist/assets/subset-shared.chunk-Uy-J87FQ.js +0 -84
- package/client/dist/assets/subset-worker.chunk-dvgDvqt9.js +0 -1
- package/client/dist/assets/sv-SE-XGPEYMSR-CDCB2ZV5.js +0 -10
- package/client/dist/assets/svgDrawCommon-08f97a94-CObOzbFQ.js +0 -1
- package/client/dist/assets/ta-IN-2NMHFXQM-DHUNdO69.js +0 -9
- package/client/dist/assets/th-TH-HPSO5L25-zI2hnBq3.js +0 -2
- package/client/dist/assets/timeline-definition-85554ec2-C2XHRmxK.js +0 -61
- package/client/dist/assets/tr-TR-DEFEU3FU-l-6Hu4-D.js +0 -7
- package/client/dist/assets/uk-UA-QMV73CPH-CqSOwrl7.js +0 -6
- package/client/dist/assets/vendor-codemirror-D_s0aGBu.js +0 -35
- package/client/dist/assets/vendor-i18n-DCFGyhQR.js +0 -1
- package/client/dist/assets/vendor-icons-Lb69KSFJ.js +0 -646
- package/client/dist/assets/vendor-markdown-BXEi_H3G.js +0 -298
- package/client/dist/assets/vendor-react-9mUTKBHH.js +0 -67
- package/client/dist/assets/vendor-syntax-DnmwQQJF.js +0 -16
- package/client/dist/assets/vendor-xterm-CZq1hqo1.js +0 -66
- package/client/dist/assets/vendor-xterm-qxJ8_QYu.css +0 -32
- package/client/dist/assets/vi-VN-M7AON7JQ-CUL8-mBZ.js +0 -5
- package/client/dist/assets/xychartDiagram-e933f94c-1fmf6slj.js +0 -7
- package/client/dist/assets/zh-CN-LNUGB5OW-CB5y5VVU.js +0 -10
- package/client/dist/assets/zh-HK-E62DVLB3-BHcrrEeJ.js +0 -1
- package/client/dist/assets/zh-TW-RAJ6MFWO-DoDUdkaJ.js +0 -9
- package/client/dist/clear-cache.html +0 -85
- package/client/dist/convert-icons.md +0 -53
- package/client/dist/favicon.png +0 -0
- package/client/dist/favicon.svg +0 -9
- package/client/dist/generate-icons.js +0 -49
- package/client/dist/icons/claude-ai-icon.svg +0 -1
- package/client/dist/icons/codex-white.svg +0 -3
- package/client/dist/icons/codex.svg +0 -3
- package/client/dist/icons/cursor-white.svg +0 -12
- package/client/dist/icons/cursor.svg +0 -1
- package/client/dist/icons/icon-128x128.png +0 -0
- package/client/dist/icons/icon-128x128.svg +0 -12
- package/client/dist/icons/icon-144x144.png +0 -0
- package/client/dist/icons/icon-144x144.svg +0 -12
- package/client/dist/icons/icon-152x152.png +0 -0
- package/client/dist/icons/icon-152x152.svg +0 -12
- package/client/dist/icons/icon-192x192.png +0 -0
- package/client/dist/icons/icon-192x192.svg +0 -12
- package/client/dist/icons/icon-384x384.png +0 -0
- package/client/dist/icons/icon-384x384.svg +0 -12
- package/client/dist/icons/icon-512x512.png +0 -0
- package/client/dist/icons/icon-512x512.svg +0 -12
- package/client/dist/icons/icon-72x72.png +0 -0
- package/client/dist/icons/icon-72x72.svg +0 -12
- package/client/dist/icons/icon-96x96.png +0 -0
- package/client/dist/icons/icon-96x96.svg +0 -12
- package/client/dist/icons/icon-template.svg +0 -12
- package/client/dist/index.html +0 -128
- 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 +0 -17
- package/client/dist/manifest.json +0 -61
- package/client/dist/mcp-docs.html +0 -119
- 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 +0 -19
- package/commands/upfynai-connect.md +0 -59
- package/commands/upfynai-disconnect.md +0 -31
- package/commands/upfynai-doctor.md +0 -99
- package/commands/upfynai-export.md +0 -49
- package/commands/upfynai-local.md +0 -82
- package/commands/upfynai-status.md +0 -75
- package/commands/upfynai-stop.md +0 -49
- package/commands/upfynai-uninstall.md +0 -58
- package/commands/upfynai.md +0 -69
- package/scripts/build-client.js +0 -17
- package/scripts/fix-node-pty.js +0 -67
- package/scripts/install-commands.js +0 -78
- package/server/claude-sdk.js +0 -714
- package/server/cli-ui.js +0 -785
- package/server/cli.js +0 -596
- package/server/constants/config.js +0 -31
- package/server/cursor-cli.js +0 -270
- package/server/database/auth.db +0 -0
- package/server/database/db.js +0 -822
- package/server/database/init.sql +0 -70
- package/server/index.js +0 -2738
- package/server/load-env.js +0 -26
- package/server/mcp-server.js +0 -621
- package/server/middleware/auth.js +0 -181
- package/server/openai-codex.js +0 -403
- package/server/openrouter.js +0 -137
- package/server/projects.js +0 -1742
- package/server/relay-client.js +0 -672
- package/server/routes/agent.js +0 -1226
- package/server/routes/auth.js +0 -266
- package/server/routes/cli-auth.js +0 -263
- package/server/routes/codex.js +0 -344
- package/server/routes/commands.js +0 -598
- package/server/routes/cursor.js +0 -807
- package/server/routes/dashboard.js +0 -205
- package/server/routes/git.js +0 -1151
- package/server/routes/mcp-utils.js +0 -48
- package/server/routes/mcp.js +0 -535
- package/server/routes/payments.js +0 -172
- package/server/routes/projects.js +0 -552
- package/server/routes/settings.js +0 -261
- package/server/routes/taskmaster.js +0 -1928
- package/server/routes/user.js +0 -106
- package/server/routes/vapi-chat.js +0 -94
- package/server/routes/voice.js +0 -194
- package/server/routes/webhooks.js +0 -166
- package/server/routes/workflows.js +0 -118
- package/server/sandbox.js +0 -120
- package/server/services/whisperService.js +0 -84
- package/server/services/workflowScheduler.js +0 -186
- package/server/utils/commandParser.js +0 -303
- package/server/utils/gitConfig.js +0 -24
- package/server/utils/mcp-detector.js +0 -198
- package/server/utils/taskmaster-websocket.js +0 -129
- package/shared/modelConstants.js +0 -96
package/server/relay-client.js
DELETED
|
@@ -1,672 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Upfyn-Code Relay Client
|
|
4
|
-
*
|
|
5
|
-
* Connects your local machine to the hosted Upfyn-Code server.
|
|
6
|
-
* Bridges Claude CLI, terminal, filesystem, and git to the web UI.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* upfynai-code connect --server https://upfynai.thinqmesh.com --key upfyn_xxx
|
|
10
|
-
* upfynai-code connect (uses saved config from ~/.upfynai/config.json)
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import WebSocket from 'ws';
|
|
14
|
-
import os from 'os';
|
|
15
|
-
import fs from 'fs';
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import { spawn, execSync } from 'child_process';
|
|
18
|
-
import { promises as fsPromises } from 'fs';
|
|
19
|
-
import crypto from 'crypto';
|
|
20
|
-
import {
|
|
21
|
-
c,
|
|
22
|
-
showConnectStartup,
|
|
23
|
-
showConnectionBanner,
|
|
24
|
-
logRelayEvent,
|
|
25
|
-
createSpinner,
|
|
26
|
-
} from './cli-ui.js';
|
|
27
|
-
|
|
28
|
-
// Load package.json for version
|
|
29
|
-
import { fileURLToPath } from 'url';
|
|
30
|
-
const __filename_rc = fileURLToPath(import.meta.url);
|
|
31
|
-
const __dirname_rc = path.dirname(__filename_rc);
|
|
32
|
-
let VERSION = '0.0.0';
|
|
33
|
-
try {
|
|
34
|
-
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname_rc, '../package.json'), 'utf8'));
|
|
35
|
-
VERSION = pkg.version;
|
|
36
|
-
} catch { /* ignore */ }
|
|
37
|
-
|
|
38
|
-
const CONFIG_DIR = path.join(os.homedir(), '.upfynai');
|
|
39
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
40
|
-
|
|
41
|
-
function loadConfig() {
|
|
42
|
-
try {
|
|
43
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
44
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
45
|
-
}
|
|
46
|
-
} catch (e) { /* ignore */ }
|
|
47
|
-
return {};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function saveConfig(config) {
|
|
51
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
52
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Execute a shell command and return stdout
|
|
59
|
-
*/
|
|
60
|
-
function execCommand(cmd, args, options = {}) {
|
|
61
|
-
return new Promise((resolve, reject) => {
|
|
62
|
-
const proc = spawn(cmd, args, {
|
|
63
|
-
shell: true,
|
|
64
|
-
cwd: options.cwd || os.homedir(),
|
|
65
|
-
env: { ...process.env, ...options.env },
|
|
66
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
let stdout = '';
|
|
70
|
-
let stderr = '';
|
|
71
|
-
proc.stdout.on('data', (d) => { stdout += d; });
|
|
72
|
-
proc.stderr.on('data', (d) => { stderr += d; });
|
|
73
|
-
|
|
74
|
-
const timeout = setTimeout(() => {
|
|
75
|
-
proc.kill();
|
|
76
|
-
reject(new Error('Command timed out'));
|
|
77
|
-
}, options.timeout || 30000);
|
|
78
|
-
|
|
79
|
-
proc.on('close', (code) => {
|
|
80
|
-
clearTimeout(timeout);
|
|
81
|
-
if (code === 0) resolve(stdout);
|
|
82
|
-
else reject(new Error(stderr || `Exit code ${code}`));
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
proc.on('error', (err) => {
|
|
86
|
-
clearTimeout(timeout);
|
|
87
|
-
reject(err);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Resolve a binary name to its full path, checking PATH first then local node_modules/.bin
|
|
94
|
-
*/
|
|
95
|
-
function resolveBinary(name) {
|
|
96
|
-
const isWindows = process.platform === 'win32';
|
|
97
|
-
try {
|
|
98
|
-
const result = execSync(`${isWindows ? 'where' : 'which'} ${name}`, { stdio: 'pipe', timeout: 5000 }).toString().trim();
|
|
99
|
-
return result.split('\n')[0].trim();
|
|
100
|
-
} catch {
|
|
101
|
-
const localPath = path.join(__dirname_rc, '../node_modules/.bin', name + (isWindows ? '.cmd' : ''));
|
|
102
|
-
if (fs.existsSync(localPath)) return localPath;
|
|
103
|
-
return name; // fallback to bare name
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Handle incoming relay commands from the server
|
|
109
|
-
*/
|
|
110
|
-
async function handleRelayCommand(data, ws) {
|
|
111
|
-
const { requestId, action } = data;
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
switch (action) {
|
|
115
|
-
case 'claude-query': {
|
|
116
|
-
const { command, options } = data;
|
|
117
|
-
logRelayEvent('>', `Claude query: ${command?.slice(0, 60)}...`, 'cyan');
|
|
118
|
-
|
|
119
|
-
const args = ['--print'];
|
|
120
|
-
if (options?.projectPath) args.push('--cwd', options.projectPath);
|
|
121
|
-
if (options?.sessionId) args.push('--continue', options.sessionId);
|
|
122
|
-
|
|
123
|
-
const proc = spawn(resolveBinary('claude'), [...args, command || ''], {
|
|
124
|
-
shell: true,
|
|
125
|
-
cwd: options?.projectPath || os.homedir(),
|
|
126
|
-
env: process.env,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
proc.stdout.on('data', (chunk) => {
|
|
130
|
-
ws.send(JSON.stringify({
|
|
131
|
-
type: 'relay-stream',
|
|
132
|
-
requestId,
|
|
133
|
-
data: { type: 'claude-response', content: chunk.toString() }
|
|
134
|
-
}));
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
proc.stderr.on('data', (chunk) => {
|
|
138
|
-
ws.send(JSON.stringify({
|
|
139
|
-
type: 'relay-stream',
|
|
140
|
-
requestId,
|
|
141
|
-
data: { type: 'claude-error', content: chunk.toString() }
|
|
142
|
-
}));
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
proc.on('close', (code) => {
|
|
146
|
-
ws.send(JSON.stringify({
|
|
147
|
-
type: 'relay-complete',
|
|
148
|
-
requestId,
|
|
149
|
-
exitCode: code
|
|
150
|
-
}));
|
|
151
|
-
});
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
case 'codex-query': {
|
|
156
|
-
const { command, options } = data;
|
|
157
|
-
logRelayEvent('>', `Codex query: ${command?.slice(0, 60)}...`, 'cyan');
|
|
158
|
-
|
|
159
|
-
const codexArgs = ['--quiet'];
|
|
160
|
-
if (options?.projectPath || options?.cwd) {
|
|
161
|
-
codexArgs.push('--cwd', options.projectPath || options.cwd);
|
|
162
|
-
}
|
|
163
|
-
if (options?.model) codexArgs.push('--model', options.model);
|
|
164
|
-
|
|
165
|
-
const codexProc = spawn(resolveBinary('codex'), [...codexArgs, command || ''], {
|
|
166
|
-
shell: true,
|
|
167
|
-
cwd: options?.projectPath || options?.cwd || os.homedir(),
|
|
168
|
-
env: process.env,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
codexProc.stdout.on('data', (chunk) => {
|
|
172
|
-
ws.send(JSON.stringify({
|
|
173
|
-
type: 'relay-stream',
|
|
174
|
-
requestId,
|
|
175
|
-
data: { type: 'codex-response', content: chunk.toString() }
|
|
176
|
-
}));
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
codexProc.stderr.on('data', (chunk) => {
|
|
180
|
-
ws.send(JSON.stringify({
|
|
181
|
-
type: 'relay-stream',
|
|
182
|
-
requestId,
|
|
183
|
-
data: { type: 'codex-error', content: chunk.toString() }
|
|
184
|
-
}));
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
codexProc.on('close', (code) => {
|
|
188
|
-
ws.send(JSON.stringify({
|
|
189
|
-
type: 'relay-complete',
|
|
190
|
-
requestId,
|
|
191
|
-
exitCode: code
|
|
192
|
-
}));
|
|
193
|
-
});
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
case 'cursor-query': {
|
|
198
|
-
const { command, options } = data;
|
|
199
|
-
logRelayEvent('>', `Cursor query: ${command?.slice(0, 60)}...`, 'cyan');
|
|
200
|
-
|
|
201
|
-
const cursorArgs = [];
|
|
202
|
-
if (options?.projectPath || options?.cwd) {
|
|
203
|
-
cursorArgs.push('--cwd', options.projectPath || options.cwd);
|
|
204
|
-
}
|
|
205
|
-
if (options?.model) cursorArgs.push('--model', options.model);
|
|
206
|
-
|
|
207
|
-
const cursorProc = spawn(resolveBinary('cursor-agent'), [...cursorArgs, command || ''], {
|
|
208
|
-
shell: true,
|
|
209
|
-
cwd: options?.projectPath || options?.cwd || os.homedir(),
|
|
210
|
-
env: process.env,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
cursorProc.stdout.on('data', (chunk) => {
|
|
214
|
-
ws.send(JSON.stringify({
|
|
215
|
-
type: 'relay-stream',
|
|
216
|
-
requestId,
|
|
217
|
-
data: { type: 'cursor-response', content: chunk.toString() }
|
|
218
|
-
}));
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
cursorProc.stderr.on('data', (chunk) => {
|
|
222
|
-
ws.send(JSON.stringify({
|
|
223
|
-
type: 'relay-stream',
|
|
224
|
-
requestId,
|
|
225
|
-
data: { type: 'cursor-error', content: chunk.toString() }
|
|
226
|
-
}));
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
cursorProc.on('close', (code) => {
|
|
230
|
-
ws.send(JSON.stringify({
|
|
231
|
-
type: 'relay-complete',
|
|
232
|
-
requestId,
|
|
233
|
-
exitCode: code
|
|
234
|
-
}));
|
|
235
|
-
});
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
case 'detect-agents': {
|
|
240
|
-
const agents = detectInstalledAgents();
|
|
241
|
-
ws.send(JSON.stringify({
|
|
242
|
-
type: 'relay-response',
|
|
243
|
-
requestId,
|
|
244
|
-
data: { agents }
|
|
245
|
-
}));
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
case 'shell-command': {
|
|
250
|
-
const { command: cmd, cwd } = data;
|
|
251
|
-
if (!cmd || typeof cmd !== 'string') throw new Error('Invalid command');
|
|
252
|
-
// Block dangerous shell patterns (cross-platform)
|
|
253
|
-
const cmdLower = cmd.toLowerCase();
|
|
254
|
-
const dangerous = [
|
|
255
|
-
'rm -rf /', 'mkfs', 'dd if=', ':(){', 'fork bomb', '> /dev/sd', // Unix
|
|
256
|
-
'format c:', 'format d:', 'format e:', 'del /s /q c:\\', // Windows
|
|
257
|
-
'rd /s /q c:\\', 'reg delete', 'bcdedit', // Windows
|
|
258
|
-
];
|
|
259
|
-
if (dangerous.some(d => cmdLower.includes(d.toLowerCase()))) throw new Error('Command blocked for safety');
|
|
260
|
-
logRelayEvent('$', `Shell: ${cmd?.slice(0, 50)}`, 'dim');
|
|
261
|
-
const result = await execCommand(cmd, [], { cwd: cwd || process.cwd(), timeout: 60000 });
|
|
262
|
-
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { stdout: result } }));
|
|
263
|
-
break;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
case 'file-read': {
|
|
267
|
-
const { filePath } = data;
|
|
268
|
-
if (!filePath || typeof filePath !== 'string') throw new Error('Invalid file path');
|
|
269
|
-
const normalizedPath = path.resolve(filePath);
|
|
270
|
-
// Block reading sensitive files (cross-platform, case-insensitive)
|
|
271
|
-
const normLower = normalizedPath.toLowerCase().replace(/\\/g, '/');
|
|
272
|
-
const blockedRead = ['/etc/shadow', '/etc/passwd', '.ssh/id_rsa', '.ssh/id_ed25519', '/.env'];
|
|
273
|
-
if (blockedRead.some(b => normLower.includes(b))) throw new Error('Access denied');
|
|
274
|
-
logRelayEvent('R', `Read: ${filePath}`, 'dim');
|
|
275
|
-
const content = await fsPromises.readFile(normalizedPath, 'utf8');
|
|
276
|
-
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { content } }));
|
|
277
|
-
break;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
case 'file-write': {
|
|
281
|
-
const { filePath: fp, content: fileContent } = data;
|
|
282
|
-
if (!fp || typeof fp !== 'string') throw new Error('Invalid file path');
|
|
283
|
-
const normalizedFp = path.resolve(fp);
|
|
284
|
-
// Block writing to sensitive locations (cross-platform, case-insensitive)
|
|
285
|
-
const fpLower = normalizedFp.toLowerCase().replace(/\\/g, '/');
|
|
286
|
-
const blockedWrite = [
|
|
287
|
-
'/etc/', '/usr/bin/', '/usr/sbin/', // Unix
|
|
288
|
-
'/windows/system32', '/windows/syswow64', '/program files', // Windows
|
|
289
|
-
'.ssh/', '/.env',
|
|
290
|
-
];
|
|
291
|
-
if (blockedWrite.some(d => fpLower.includes(d))) throw new Error('Access denied');
|
|
292
|
-
// Ensure parent directory exists (works across drives)
|
|
293
|
-
const parentDir = path.dirname(normalizedFp);
|
|
294
|
-
await fsPromises.mkdir(parentDir, { recursive: true }).catch(() => {});
|
|
295
|
-
logRelayEvent('W', `Write: ${fp}`, 'dim');
|
|
296
|
-
await fsPromises.writeFile(normalizedFp, fileContent, 'utf8');
|
|
297
|
-
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { success: true } }));
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
case 'file-tree': {
|
|
302
|
-
const { dirPath, depth = 3 } = data;
|
|
303
|
-
// Use provided path (any drive), fall back to cwd, then home
|
|
304
|
-
const resolvedDir = dirPath ? path.resolve(dirPath) : process.cwd();
|
|
305
|
-
const tree = await buildFileTree(resolvedDir, depth);
|
|
306
|
-
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { tree } }));
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
case 'git-operation': {
|
|
311
|
-
const { gitCommand, cwd: gitCwd } = data;
|
|
312
|
-
logRelayEvent('G', `Git: ${gitCommand}`, 'dim');
|
|
313
|
-
const resolvedGitCwd = gitCwd ? path.resolve(gitCwd) : process.cwd();
|
|
314
|
-
const result = await execCommand('git', [gitCommand], { cwd: resolvedGitCwd });
|
|
315
|
-
ws.send(JSON.stringify({ type: 'relay-response', requestId, data: { stdout: result } }));
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
default:
|
|
320
|
-
ws.send(JSON.stringify({
|
|
321
|
-
type: 'relay-response',
|
|
322
|
-
requestId,
|
|
323
|
-
error: `Unknown action: ${action}`
|
|
324
|
-
}));
|
|
325
|
-
}
|
|
326
|
-
} catch (err) {
|
|
327
|
-
ws.send(JSON.stringify({
|
|
328
|
-
type: 'relay-response',
|
|
329
|
-
requestId,
|
|
330
|
-
error: err.message
|
|
331
|
-
}));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Build a file tree for a directory
|
|
337
|
-
*/
|
|
338
|
-
async function buildFileTree(dirPath, maxDepth, currentDepth = 0) {
|
|
339
|
-
if (currentDepth >= maxDepth) return [];
|
|
340
|
-
try {
|
|
341
|
-
const entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
|
|
342
|
-
const items = [];
|
|
343
|
-
for (const entry of entries.slice(0, 100)) {
|
|
344
|
-
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
345
|
-
const item = { name: entry.name, type: entry.isDirectory() ? 'directory' : 'file' };
|
|
346
|
-
if (entry.isDirectory() && currentDepth < maxDepth - 1) {
|
|
347
|
-
item.children = await buildFileTree(path.join(dirPath, entry.name), maxDepth, currentDepth + 1);
|
|
348
|
-
}
|
|
349
|
-
items.push(item);
|
|
350
|
-
}
|
|
351
|
-
return items;
|
|
352
|
-
} catch (e) {
|
|
353
|
-
return [];
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* Detect which AI CLI agents are installed on this machine
|
|
359
|
-
* Returns an object with agent names and their availability
|
|
360
|
-
*/
|
|
361
|
-
function detectInstalledAgents() {
|
|
362
|
-
const isWindows = process.platform === 'win32';
|
|
363
|
-
const whichCmd = isWindows ? 'where' : 'which';
|
|
364
|
-
|
|
365
|
-
// Also check our own node_modules/.bin for bundled agents
|
|
366
|
-
const localBinDir = path.join(__dirname_rc, '../node_modules/.bin');
|
|
367
|
-
|
|
368
|
-
const agents = [
|
|
369
|
-
{ name: 'claude', binary: 'claude', label: 'Claude Code' },
|
|
370
|
-
{ name: 'codex', binary: 'codex', label: 'OpenAI Codex' },
|
|
371
|
-
{ name: 'cursor', binary: 'cursor-agent', label: 'Cursor Agent' },
|
|
372
|
-
];
|
|
373
|
-
|
|
374
|
-
const detected = {};
|
|
375
|
-
for (const agent of agents) {
|
|
376
|
-
try {
|
|
377
|
-
const result = execSync(`${whichCmd} ${agent.binary}`, { stdio: 'pipe', timeout: 5000 }).toString().trim();
|
|
378
|
-
detected[agent.name] = {
|
|
379
|
-
installed: true,
|
|
380
|
-
path: result.split('\n')[0].trim(),
|
|
381
|
-
label: agent.label,
|
|
382
|
-
};
|
|
383
|
-
} catch {
|
|
384
|
-
// Fallback: check bundled node_modules/.bin
|
|
385
|
-
const localPath = path.join(localBinDir, agent.binary + (isWindows ? '.cmd' : ''));
|
|
386
|
-
if (fs.existsSync(localPath)) {
|
|
387
|
-
detected[agent.name] = {
|
|
388
|
-
installed: true,
|
|
389
|
-
path: localPath,
|
|
390
|
-
label: agent.label,
|
|
391
|
-
};
|
|
392
|
-
} else {
|
|
393
|
-
detected[agent.name] = {
|
|
394
|
-
installed: false,
|
|
395
|
-
label: agent.label,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
return detected;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Create WebSocket connection with optional API key in handshake
|
|
405
|
-
*/
|
|
406
|
-
function createRelayConnection(wsUrl, config = {}) {
|
|
407
|
-
const headers = {};
|
|
408
|
-
// Send API key in WebSocket headers if available
|
|
409
|
-
if (config.anthropicApiKey) {
|
|
410
|
-
headers['x-anthropic-api-key'] = config.anthropicApiKey;
|
|
411
|
-
}
|
|
412
|
-
headers['x-upfyn-version'] = VERSION;
|
|
413
|
-
headers['x-upfyn-machine'] = os.hostname();
|
|
414
|
-
headers['x-upfyn-platform'] = process.platform;
|
|
415
|
-
headers['x-upfyn-cwd'] = process.cwd();
|
|
416
|
-
|
|
417
|
-
return new WebSocket(wsUrl, { headers });
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Main connect function (interactive — with animation and logging)
|
|
422
|
-
*/
|
|
423
|
-
export async function connectToServer(options = {}) {
|
|
424
|
-
const config = loadConfig();
|
|
425
|
-
const serverUrl = options.server || config.server || 'https://upfynai-code-production.up.railway.app';
|
|
426
|
-
const relayKey = options.key || config.relayKey;
|
|
427
|
-
|
|
428
|
-
if (!relayKey) {
|
|
429
|
-
console.log('');
|
|
430
|
-
console.log(` ${c.red('FAIL')} No relay key provided.`);
|
|
431
|
-
console.log('');
|
|
432
|
-
console.log(` ${c.gray('Get your relay token from the web UI:')}`);
|
|
433
|
-
console.log(` ${c.dim('1.')} Sign in at ${c.cyan('https://cli.upfyn.com')}`);
|
|
434
|
-
console.log(` ${c.dim('2.')} Click ${c.bright('Connect')} button`);
|
|
435
|
-
console.log(` ${c.dim('3.')} Copy the command and run it here`);
|
|
436
|
-
console.log('');
|
|
437
|
-
console.log(` ${c.gray('Or run:')} ${c.bright('uc connect --server <url> --key upfyn_your_token')}`);
|
|
438
|
-
console.log('');
|
|
439
|
-
process.exit(1);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// Save config for future use
|
|
443
|
-
saveConfig({ ...config, server: serverUrl, relayKey });
|
|
444
|
-
|
|
445
|
-
// Show beautiful startup with rocket animation
|
|
446
|
-
await showConnectStartup(
|
|
447
|
-
serverUrl,
|
|
448
|
-
os.hostname(),
|
|
449
|
-
os.userInfo().username,
|
|
450
|
-
VERSION
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
const wsUrl = serverUrl.replace(/^http/, 'ws') + '/relay?token=' + encodeURIComponent(relayKey);
|
|
454
|
-
|
|
455
|
-
const spinner = createSpinner('Connecting to server...');
|
|
456
|
-
spinner.start();
|
|
457
|
-
|
|
458
|
-
let reconnectAttempts = 0;
|
|
459
|
-
const MAX_RECONNECT = 10;
|
|
460
|
-
|
|
461
|
-
let lastPongTime = Date.now();
|
|
462
|
-
|
|
463
|
-
function connect() {
|
|
464
|
-
const ws = createRelayConnection(wsUrl, config);
|
|
465
|
-
lastPongTime = Date.now();
|
|
466
|
-
|
|
467
|
-
ws.on('open', () => {
|
|
468
|
-
reconnectAttempts = 0;
|
|
469
|
-
lastPongTime = Date.now();
|
|
470
|
-
// Don't stop spinner yet — wait for relay-connected message
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
ws.on('message', (rawMessage) => {
|
|
474
|
-
try {
|
|
475
|
-
const data = JSON.parse(rawMessage);
|
|
476
|
-
|
|
477
|
-
if (data.type === 'relay-connected') {
|
|
478
|
-
spinner.stop('Connected!');
|
|
479
|
-
// Extract username from message if possible
|
|
480
|
-
const nameMatch = data.message?.match(/Connected as (.+?)\./);
|
|
481
|
-
const username = nameMatch ? nameMatch[1] : 'Unknown';
|
|
482
|
-
showConnectionBanner(username, serverUrl);
|
|
483
|
-
|
|
484
|
-
// Detect and report installed agents
|
|
485
|
-
const agents = detectInstalledAgents();
|
|
486
|
-
const installed = Object.entries(agents)
|
|
487
|
-
.filter(([, info]) => info.installed)
|
|
488
|
-
.map(([name, info]) => info.label);
|
|
489
|
-
const missing = Object.entries(agents)
|
|
490
|
-
.filter(([, info]) => !info.installed)
|
|
491
|
-
.map(([name, info]) => info.label);
|
|
492
|
-
|
|
493
|
-
if (installed.length > 0) {
|
|
494
|
-
logRelayEvent('+', `Agents found: ${installed.join(', ')}`, 'green');
|
|
495
|
-
}
|
|
496
|
-
if (missing.length > 0) {
|
|
497
|
-
logRelayEvent('~', `Not found: ${missing.join(', ')} (install to enable)`, 'yellow');
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// Send agent capabilities to server
|
|
501
|
-
ws.send(JSON.stringify({
|
|
502
|
-
type: 'agent-capabilities',
|
|
503
|
-
agents,
|
|
504
|
-
machine: {
|
|
505
|
-
hostname: os.hostname(),
|
|
506
|
-
platform: process.platform,
|
|
507
|
-
cwd: process.cwd(),
|
|
508
|
-
}
|
|
509
|
-
}));
|
|
510
|
-
|
|
511
|
-
logRelayEvent('*', 'Relay active -- waiting for commands...', 'green');
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (data.type === 'relay-command') {
|
|
516
|
-
handleRelayCommand(data, ws);
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (data.type === 'pong' || data.type === 'server-ping') {
|
|
521
|
-
lastPongTime = Date.now();
|
|
522
|
-
// Reply to server-ping so server knows we're alive
|
|
523
|
-
if (data.type === 'server-ping') {
|
|
524
|
-
ws.send(JSON.stringify({ type: 'ping' }));
|
|
525
|
-
}
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
if (data.type === 'error') {
|
|
530
|
-
spinner.fail(`Server error: ${data.error}`);
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
} catch (e) {
|
|
534
|
-
logRelayEvent('!', `Parse error: ${e.message}`, 'red');
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
ws.on('close', (code) => {
|
|
539
|
-
if (code === 1000) {
|
|
540
|
-
logRelayEvent('-', 'Disconnected gracefully.', 'dim');
|
|
541
|
-
process.exit(0);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
reconnectAttempts++;
|
|
545
|
-
if (reconnectAttempts <= MAX_RECONNECT) {
|
|
546
|
-
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
|
547
|
-
logRelayEvent('~', `Connection lost. Reconnecting in ${delay / 1000}s... (${reconnectAttempts}/${MAX_RECONNECT})`, 'yellow');
|
|
548
|
-
setTimeout(connect, delay);
|
|
549
|
-
} else {
|
|
550
|
-
logRelayEvent('X', 'Max reconnection attempts reached. Exiting.', 'red');
|
|
551
|
-
process.exit(1);
|
|
552
|
-
}
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
ws.on('error', (err) => {
|
|
556
|
-
if (err.code === 'ECONNREFUSED') {
|
|
557
|
-
spinner.fail(`Cannot reach ${serverUrl}. Is the server running?`);
|
|
558
|
-
} else if (err.message?.includes('401')) {
|
|
559
|
-
spinner.fail('Authentication failed (401). Your relay token may be invalid or expired.');
|
|
560
|
-
logRelayEvent('!', 'Get a new token from the dashboard: https://cli.upfyn.com/dashboard', 'yellow');
|
|
561
|
-
logRelayEvent('!', 'Then run: uc connect --server <url> --key <new_token>', 'yellow');
|
|
562
|
-
} else if (err.message?.includes('403') || err.message?.includes('Forbidden')) {
|
|
563
|
-
spinner.fail('Access forbidden (403). Your account may be inactive.');
|
|
564
|
-
} else {
|
|
565
|
-
logRelayEvent('!', `WebSocket error: ${err.message || err.code || 'unknown'}`, 'red');
|
|
566
|
-
}
|
|
567
|
-
// close handler will trigger reconnect
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// Heartbeat every 30 seconds with pong timeout detection
|
|
571
|
-
const heartbeat = setInterval(() => {
|
|
572
|
-
if (ws.readyState !== 1) {
|
|
573
|
-
clearInterval(heartbeat);
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
// If no pong received in 75s, consider connection dead
|
|
577
|
-
if (Date.now() - lastPongTime > 75000) {
|
|
578
|
-
clearInterval(heartbeat);
|
|
579
|
-
logRelayEvent('!', 'No heartbeat response — connection stale, reconnecting...', 'yellow');
|
|
580
|
-
ws.terminate();
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
ws.send(JSON.stringify({ type: 'ping' }));
|
|
584
|
-
}, 30000);
|
|
585
|
-
|
|
586
|
-
ws.on('close', () => clearInterval(heartbeat));
|
|
587
|
-
ws.on('error', () => clearInterval(heartbeat));
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
connect();
|
|
591
|
-
|
|
592
|
-
// Graceful shutdown
|
|
593
|
-
process.on('SIGINT', () => {
|
|
594
|
-
console.log('');
|
|
595
|
-
logRelayEvent('-', 'Disconnecting...', 'dim');
|
|
596
|
-
process.exit(0);
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
/**
|
|
601
|
-
* Background connect function (silent — used when uc launches Claude Code)
|
|
602
|
-
* Runs relay in the background without animation or user-facing output.
|
|
603
|
-
*/
|
|
604
|
-
export function connectToServerBackground(options = {}) {
|
|
605
|
-
const config = loadConfig();
|
|
606
|
-
const serverUrl = options.server || config.server;
|
|
607
|
-
const relayKey = options.key || config.relayKey;
|
|
608
|
-
|
|
609
|
-
if (!serverUrl || !relayKey) return;
|
|
610
|
-
|
|
611
|
-
const wsUrl = serverUrl.replace(/^http/, 'ws') + '/relay?token=' + encodeURIComponent(relayKey);
|
|
612
|
-
|
|
613
|
-
let reconnectAttempts = 0;
|
|
614
|
-
const MAX_RECONNECT = 5;
|
|
615
|
-
let lastPongTime = Date.now();
|
|
616
|
-
|
|
617
|
-
function connect() {
|
|
618
|
-
const ws = createRelayConnection(wsUrl, config);
|
|
619
|
-
lastPongTime = Date.now();
|
|
620
|
-
|
|
621
|
-
ws.on('message', (rawMessage) => {
|
|
622
|
-
try {
|
|
623
|
-
const data = JSON.parse(rawMessage);
|
|
624
|
-
if (data.type === 'pong' || data.type === 'server-ping') {
|
|
625
|
-
lastPongTime = Date.now();
|
|
626
|
-
if (data.type === 'server-ping') {
|
|
627
|
-
ws.send(JSON.stringify({ type: 'ping' }));
|
|
628
|
-
}
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
if (data.type === 'relay-command') {
|
|
632
|
-
handleRelayCommand(data, ws);
|
|
633
|
-
}
|
|
634
|
-
} catch { /* ignore */ }
|
|
635
|
-
});
|
|
636
|
-
|
|
637
|
-
ws.on('open', () => {
|
|
638
|
-
reconnectAttempts = 0;
|
|
639
|
-
lastPongTime = Date.now();
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
ws.on('close', (code) => {
|
|
643
|
-
clearInterval(heartbeat);
|
|
644
|
-
if (code === 1000) return;
|
|
645
|
-
reconnectAttempts++;
|
|
646
|
-
if (reconnectAttempts <= MAX_RECONNECT) {
|
|
647
|
-
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
|
|
648
|
-
setTimeout(connect, delay);
|
|
649
|
-
}
|
|
650
|
-
});
|
|
651
|
-
|
|
652
|
-
ws.on('error', () => {
|
|
653
|
-
clearInterval(heartbeat);
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
// Heartbeat with pong timeout
|
|
657
|
-
const heartbeat = setInterval(() => {
|
|
658
|
-
if (ws.readyState !== 1) {
|
|
659
|
-
clearInterval(heartbeat);
|
|
660
|
-
return;
|
|
661
|
-
}
|
|
662
|
-
if (Date.now() - lastPongTime > 75000) {
|
|
663
|
-
clearInterval(heartbeat);
|
|
664
|
-
ws.terminate();
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
ws.send(JSON.stringify({ type: 'ping' }));
|
|
668
|
-
}, 30000);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
connect();
|
|
672
|
-
}
|