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.
Files changed (295) hide show
  1. package/README.md +123 -88
  2. package/bin/cli.js +63 -0
  3. package/package.json +48 -106
  4. package/src/auth.js +115 -0
  5. package/src/config.js +33 -0
  6. package/src/connect.js +314 -0
  7. package/src/launch.js +54 -0
  8. package/src/mcp.js +57 -0
  9. package/src/server.js +54 -0
  10. package/client/dist/api-docs.html +0 -879
  11. package/client/dist/assets/AppContent-C0CyP3g5.js +0 -513
  12. package/client/dist/assets/CanvasPanel-0u9QR7U-.js +0 -34
  13. package/client/dist/assets/CanvasPanel-WhZulBJw.css +0 -1
  14. package/client/dist/assets/DashboardPanel-Dgqw1yZk.js +0 -1
  15. package/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  16. package/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  17. package/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  18. package/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  19. package/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  20. package/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  21. package/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  22. package/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  23. package/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  24. package/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  25. package/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  26. package/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  27. package/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  28. package/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  29. package/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  30. package/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  31. package/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  32. package/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  33. package/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  34. package/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  35. package/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  36. package/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  37. package/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  38. package/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  39. package/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  40. package/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  41. package/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  42. package/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  43. package/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  44. package/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  45. package/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  46. package/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  47. package/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  48. package/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  49. package/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  50. package/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  51. package/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  52. package/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  53. package/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  54. package/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  55. package/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  56. package/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  57. package/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  58. package/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  59. package/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  60. package/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  61. package/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  62. package/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  63. package/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  64. package/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  65. package/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  66. package/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  67. package/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  68. package/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  69. package/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  70. package/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  71. package/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  72. package/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  73. package/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  74. package/client/dist/assets/LoginModal-CZDEzqjK.js +0 -19
  75. package/client/dist/assets/MarkdownPreview-CYdvwJaV.js +0 -1
  76. package/client/dist/assets/Onboarding-DR6NZ4Vz.js +0 -1
  77. package/client/dist/assets/SetupForm-D49gtWY4.js +0 -1
  78. package/client/dist/assets/Tableau10-B-NsZVaP.js +0 -1
  79. package/client/dist/assets/WorkflowsPanel-CqlbEJA_.js +0 -1
  80. package/client/dist/assets/_commonjs-dynamic-modules-TDtrdbi3.js +0 -1
  81. package/client/dist/assets/ar-SA-G6X2FPQ2-BWqa1yBH.js +0 -10
  82. package/client/dist/assets/arc-BegSKqEW.js +0 -1
  83. package/client/dist/assets/array-BKyUJesY.js +0 -1
  84. package/client/dist/assets/az-AZ-76LH7QW2-DrVlbZDP.js +0 -1
  85. package/client/dist/assets/bg-BG-XCXSNQG7-DdunjBgT.js +0 -5
  86. package/client/dist/assets/blockDiagram-38ab4fdb-BKMbwGHu.js +0 -118
  87. package/client/dist/assets/bn-BD-2XOGV67Q-_7DtmvwO.js +0 -5
  88. package/client/dist/assets/c4Diagram-3d4e48cf-hJuiHhSn.js +0 -10
  89. package/client/dist/assets/ca-ES-6MX7JW3Y-BFIrmojG.js +0 -8
  90. package/client/dist/assets/channel-Bur-rRTp.js +0 -1
  91. package/client/dist/assets/classDiagram-70f12bd4-BjiAf9cM.js +0 -2
  92. package/client/dist/assets/classDiagram-v2-f2320105-pwBewejc.js +0 -2
  93. package/client/dist/assets/clone-BtqXeoBJ.js +0 -1
  94. package/client/dist/assets/createText-2e5e7dd3-Dq_acOWe.js +0 -5
  95. package/client/dist/assets/cs-CZ-2BRQDIVT-B-x4F6TJ.js +0 -11
  96. package/client/dist/assets/da-DK-5WZEPLOC-Btlc8Dgn.js +0 -5
  97. package/client/dist/assets/de-DE-XR44H4JA-BVu3ZIoD.js +0 -8
  98. package/client/dist/assets/directory-open-01563666-DWU9wJ6I.js +0 -1
  99. package/client/dist/assets/directory-open-4ed118d0-CunoC1EB.js +0 -1
  100. package/client/dist/assets/edges-e0da2a9e-DH0wVTXR.js +0 -4
  101. package/client/dist/assets/el-GR-BZB4AONW-h2ll8_ZC.js +0 -10
  102. package/client/dist/assets/erDiagram-9861fffd-BYezLIR7.js +0 -51
  103. package/client/dist/assets/es-ES-U4NZUMDT-Cveiulwt.js +0 -9
  104. package/client/dist/assets/eu-ES-A7QVB2H4-DQluL2PY.js +0 -11
  105. package/client/dist/assets/fa-IR-HGAKTJCU-BJtcMBSv.js +0 -8
  106. package/client/dist/assets/fi-FI-Z5N7JZ37-D8NfbVXV.js +0 -6
  107. package/client/dist/assets/file-open-002ab408-DIuFHtCF.js +0 -1
  108. package/client/dist/assets/file-open-7c801643-684qeFg4.js +0 -1
  109. package/client/dist/assets/file-save-3189631c-C1wFhQhH.js +0 -1
  110. package/client/dist/assets/file-save-745eba88-Bb9F9Kg7.js +0 -1
  111. package/client/dist/assets/flowDb-956e92f1-scnUykhM.js +0 -10
  112. package/client/dist/assets/flowDiagram-66a62f08-jVyWsfyU.js +0 -4
  113. package/client/dist/assets/flowDiagram-v2-96b9c2cf-N6xgi25h.js +0 -1
  114. package/client/dist/assets/flowchart-elk-definition-4a651766-gKGX3HqR.js +0 -139
  115. package/client/dist/assets/fr-FR-RHASNOE6-vdj42kC6.js +0 -9
  116. package/client/dist/assets/ganttDiagram-c361ad54-C2CiWFUP.js +0 -257
  117. package/client/dist/assets/gitGraphDiagram-72cf32ee-C59Yz2LK.js +0 -70
  118. package/client/dist/assets/gl-ES-HMX3MZ6V-DQo0TzoP.js +0 -10
  119. package/client/dist/assets/graph-Dx_H43Kv.js +0 -1
  120. package/client/dist/assets/he-IL-6SHJWFNN-DKXK5e33.js +0 -10
  121. package/client/dist/assets/hi-IN-IWLTKZ5I-C2Qgqc0R.js +0 -4
  122. package/client/dist/assets/hu-HU-A5ZG7DT2-Ss-6vX0m.js +0 -7
  123. package/client/dist/assets/id-ID-SAP4L64H-D7Wsg1S2.js +0 -10
  124. package/client/dist/assets/image-blob-reduce.esm-D6s-rqMO.js +0 -7
  125. package/client/dist/assets/index-3862675e-u8Nv7hHC.js +0 -1
  126. package/client/dist/assets/index-BVowJdZF.js +0 -97
  127. package/client/dist/assets/index-ce18TYkg.js +0 -27
  128. package/client/dist/assets/index-kQoJx-bc.css +0 -1
  129. package/client/dist/assets/infoDiagram-f8f76790-LmoJYsxo.js +0 -7
  130. package/client/dist/assets/init-Gi6I4Gst.js +0 -1
  131. package/client/dist/assets/it-IT-JPQ66NNP-CAPTVl7M.js +0 -11
  132. package/client/dist/assets/ja-JP-DBVTYXUO-eNVPawR2.js +0 -8
  133. package/client/dist/assets/journeyDiagram-49397b02-BaJqehpR.js +0 -139
  134. package/client/dist/assets/kaa-6HZHGXH3-tpuNkKhS.js +0 -1
  135. package/client/dist/assets/kab-KAB-ZGHBKWFO-Dp83kx4x.js +0 -8
  136. package/client/dist/assets/kk-KZ-P5N5QNE5-B9IlC6YN.js +0 -1
  137. package/client/dist/assets/km-KH-HSX4SM5Z-B_KMYaMj.js +0 -11
  138. package/client/dist/assets/ko-KR-MTYHY66A-yebnUNdb.js +0 -9
  139. package/client/dist/assets/ku-TR-6OUDTVRD-BR6fh6-5.js +0 -9
  140. package/client/dist/assets/layout-DLl5Jwcl.js +0 -1
  141. package/client/dist/assets/line-FpB7omSK.js +0 -1
  142. package/client/dist/assets/linear-CkXqUFJ8.js +0 -1
  143. package/client/dist/assets/lt-LT-XHIRWOB4-SutZSWtR.js +0 -3
  144. package/client/dist/assets/lv-LV-5QDEKY6T-DuAxdcZL.js +0 -7
  145. package/client/dist/assets/mindmap-definition-fc14e90a-DyxXOExh.js +0 -425
  146. package/client/dist/assets/mr-IN-CRQNXWMA-DqDUWM_8.js +0 -13
  147. package/client/dist/assets/my-MM-5M5IBNSE-C40kMFMR.js +0 -1
  148. package/client/dist/assets/nb-NO-T6EIAALU-DVij32Ju.js +0 -10
  149. package/client/dist/assets/nl-NL-IS3SIHDZ-rT84mDYq.js +0 -8
  150. package/client/dist/assets/nn-NO-6E72VCQL-BBZXBW8V.js +0 -8
  151. package/client/dist/assets/oc-FR-POXYY2M6-DzjOugOf.js +0 -8
  152. package/client/dist/assets/ordinal-Cboi1Yqb.js +0 -1
  153. package/client/dist/assets/pa-IN-N4M65BXN-DD1iU8_F.js +0 -4
  154. package/client/dist/assets/path-CbwjOpE9.js +0 -1
  155. package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
  156. package/client/dist/assets/pdf.worker-BA9kU3Pw.mjs +0 -61080
  157. package/client/dist/assets/percentages-BXMCSKIN-WVlHS4wx.js +0 -207
  158. package/client/dist/assets/pica-CQIY57Tf.js +0 -7
  159. package/client/dist/assets/pieDiagram-8a3498a8-Dd_85qBH.js +0 -35
  160. package/client/dist/assets/pl-PL-T2D74RX3-ukVXa48G.js +0 -9
  161. package/client/dist/assets/pt-BR-5N22H2LF-BibawarT.js +0 -9
  162. package/client/dist/assets/pt-PT-UZXXM6DQ-So3i9l9w.js +0 -9
  163. package/client/dist/assets/quadrantDiagram-120e2f19-C4dFVDEx.js +0 -7
  164. package/client/dist/assets/requirementDiagram-deff3bca-DrTO7yFl.js +0 -52
  165. package/client/dist/assets/ro-RO-JPDTUUEW-DY0Xq_Hd.js +0 -11
  166. package/client/dist/assets/roundRect-0PYZxl1G.js +0 -1
  167. package/client/dist/assets/ru-RU-B4JR7IUQ-B7u_Zvkd.js +0 -9
  168. package/client/dist/assets/sankeyDiagram-04a897e0-D24gfzuS.js +0 -8
  169. package/client/dist/assets/sequenceDiagram-704730f1-Dgji2XLQ.js +0 -122
  170. package/client/dist/assets/si-LK-N5RQ5JYF-OejsLzQ_.js +0 -1
  171. package/client/dist/assets/sk-SK-C5VTKIMK-_vy2Bt-M.js +0 -6
  172. package/client/dist/assets/sl-SI-NN7IZMDC-DKOl_u2M.js +0 -6
  173. package/client/dist/assets/stateDiagram-587899a1-CJ8eBaiU.js +0 -1
  174. package/client/dist/assets/stateDiagram-v2-d93cdb3a-C5K3l-Nt.js +0 -1
  175. package/client/dist/assets/styles-6aaf32cf-DAKE0jbx.js +0 -207
  176. package/client/dist/assets/styles-9a916d00-LFAJCgEy.js +0 -160
  177. package/client/dist/assets/styles-c10674c1-CllKO8NG.js +0 -116
  178. package/client/dist/assets/subset-shared.chunk-Uy-J87FQ.js +0 -84
  179. package/client/dist/assets/subset-worker.chunk-dvgDvqt9.js +0 -1
  180. package/client/dist/assets/sv-SE-XGPEYMSR-CDCB2ZV5.js +0 -10
  181. package/client/dist/assets/svgDrawCommon-08f97a94-CObOzbFQ.js +0 -1
  182. package/client/dist/assets/ta-IN-2NMHFXQM-DHUNdO69.js +0 -9
  183. package/client/dist/assets/th-TH-HPSO5L25-zI2hnBq3.js +0 -2
  184. package/client/dist/assets/timeline-definition-85554ec2-C2XHRmxK.js +0 -61
  185. package/client/dist/assets/tr-TR-DEFEU3FU-l-6Hu4-D.js +0 -7
  186. package/client/dist/assets/uk-UA-QMV73CPH-CqSOwrl7.js +0 -6
  187. package/client/dist/assets/vendor-codemirror-D_s0aGBu.js +0 -35
  188. package/client/dist/assets/vendor-i18n-DCFGyhQR.js +0 -1
  189. package/client/dist/assets/vendor-icons-Lb69KSFJ.js +0 -646
  190. package/client/dist/assets/vendor-markdown-BXEi_H3G.js +0 -298
  191. package/client/dist/assets/vendor-react-9mUTKBHH.js +0 -67
  192. package/client/dist/assets/vendor-syntax-DnmwQQJF.js +0 -16
  193. package/client/dist/assets/vendor-xterm-CZq1hqo1.js +0 -66
  194. package/client/dist/assets/vendor-xterm-qxJ8_QYu.css +0 -32
  195. package/client/dist/assets/vi-VN-M7AON7JQ-CUL8-mBZ.js +0 -5
  196. package/client/dist/assets/xychartDiagram-e933f94c-1fmf6slj.js +0 -7
  197. package/client/dist/assets/zh-CN-LNUGB5OW-CB5y5VVU.js +0 -10
  198. package/client/dist/assets/zh-HK-E62DVLB3-BHcrrEeJ.js +0 -1
  199. package/client/dist/assets/zh-TW-RAJ6MFWO-DoDUdkaJ.js +0 -9
  200. package/client/dist/clear-cache.html +0 -85
  201. package/client/dist/convert-icons.md +0 -53
  202. package/client/dist/favicon.png +0 -0
  203. package/client/dist/favicon.svg +0 -9
  204. package/client/dist/generate-icons.js +0 -49
  205. package/client/dist/icons/claude-ai-icon.svg +0 -1
  206. package/client/dist/icons/codex-white.svg +0 -3
  207. package/client/dist/icons/codex.svg +0 -3
  208. package/client/dist/icons/cursor-white.svg +0 -12
  209. package/client/dist/icons/cursor.svg +0 -1
  210. package/client/dist/icons/icon-128x128.png +0 -0
  211. package/client/dist/icons/icon-128x128.svg +0 -12
  212. package/client/dist/icons/icon-144x144.png +0 -0
  213. package/client/dist/icons/icon-144x144.svg +0 -12
  214. package/client/dist/icons/icon-152x152.png +0 -0
  215. package/client/dist/icons/icon-152x152.svg +0 -12
  216. package/client/dist/icons/icon-192x192.png +0 -0
  217. package/client/dist/icons/icon-192x192.svg +0 -12
  218. package/client/dist/icons/icon-384x384.png +0 -0
  219. package/client/dist/icons/icon-384x384.svg +0 -12
  220. package/client/dist/icons/icon-512x512.png +0 -0
  221. package/client/dist/icons/icon-512x512.svg +0 -12
  222. package/client/dist/icons/icon-72x72.png +0 -0
  223. package/client/dist/icons/icon-72x72.svg +0 -12
  224. package/client/dist/icons/icon-96x96.png +0 -0
  225. package/client/dist/icons/icon-96x96.svg +0 -12
  226. package/client/dist/icons/icon-template.svg +0 -12
  227. package/client/dist/index.html +0 -128
  228. package/client/dist/logo-128.png +0 -0
  229. package/client/dist/logo-256.png +0 -0
  230. package/client/dist/logo-32.png +0 -0
  231. package/client/dist/logo-512.png +0 -0
  232. package/client/dist/logo-64.png +0 -0
  233. package/client/dist/logo.svg +0 -17
  234. package/client/dist/manifest.json +0 -61
  235. package/client/dist/mcp-docs.html +0 -119
  236. package/client/dist/screenshots/cli-selection.png +0 -0
  237. package/client/dist/screenshots/desktop-main.png +0 -0
  238. package/client/dist/screenshots/mobile-chat.png +0 -0
  239. package/client/dist/screenshots/tools-modal.png +0 -0
  240. package/client/dist/sw.js +0 -19
  241. package/commands/upfynai-connect.md +0 -59
  242. package/commands/upfynai-disconnect.md +0 -31
  243. package/commands/upfynai-doctor.md +0 -99
  244. package/commands/upfynai-export.md +0 -49
  245. package/commands/upfynai-local.md +0 -82
  246. package/commands/upfynai-status.md +0 -75
  247. package/commands/upfynai-stop.md +0 -49
  248. package/commands/upfynai-uninstall.md +0 -58
  249. package/commands/upfynai.md +0 -69
  250. package/scripts/build-client.js +0 -17
  251. package/scripts/fix-node-pty.js +0 -67
  252. package/scripts/install-commands.js +0 -78
  253. package/server/claude-sdk.js +0 -714
  254. package/server/cli-ui.js +0 -785
  255. package/server/cli.js +0 -596
  256. package/server/constants/config.js +0 -31
  257. package/server/cursor-cli.js +0 -270
  258. package/server/database/auth.db +0 -0
  259. package/server/database/db.js +0 -822
  260. package/server/database/init.sql +0 -70
  261. package/server/index.js +0 -2738
  262. package/server/load-env.js +0 -26
  263. package/server/mcp-server.js +0 -621
  264. package/server/middleware/auth.js +0 -181
  265. package/server/openai-codex.js +0 -403
  266. package/server/openrouter.js +0 -137
  267. package/server/projects.js +0 -1742
  268. package/server/relay-client.js +0 -672
  269. package/server/routes/agent.js +0 -1226
  270. package/server/routes/auth.js +0 -266
  271. package/server/routes/cli-auth.js +0 -263
  272. package/server/routes/codex.js +0 -344
  273. package/server/routes/commands.js +0 -598
  274. package/server/routes/cursor.js +0 -807
  275. package/server/routes/dashboard.js +0 -205
  276. package/server/routes/git.js +0 -1151
  277. package/server/routes/mcp-utils.js +0 -48
  278. package/server/routes/mcp.js +0 -535
  279. package/server/routes/payments.js +0 -172
  280. package/server/routes/projects.js +0 -552
  281. package/server/routes/settings.js +0 -261
  282. package/server/routes/taskmaster.js +0 -1928
  283. package/server/routes/user.js +0 -106
  284. package/server/routes/vapi-chat.js +0 -94
  285. package/server/routes/voice.js +0 -194
  286. package/server/routes/webhooks.js +0 -166
  287. package/server/routes/workflows.js +0 -118
  288. package/server/sandbox.js +0 -120
  289. package/server/services/whisperService.js +0 -84
  290. package/server/services/workflowScheduler.js +0 -186
  291. package/server/utils/commandParser.js +0 -303
  292. package/server/utils/gitConfig.js +0 -24
  293. package/server/utils/mcp-detector.js +0 -198
  294. package/server/utils/taskmaster-websocket.js +0 -129
  295. package/shared/modelConstants.js +0 -96
@@ -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
- }