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,106 +0,0 @@
1
- import express from 'express';
2
- import { userDb } from '../database/db.js';
3
- import { authenticateToken } from '../middleware/auth.js';
4
- import { getSystemGitConfig } from '../utils/gitConfig.js';
5
- import { exec } from 'child_process';
6
- import { promisify } from 'util';
7
-
8
- const execAsync = promisify(exec);
9
- const router = express.Router();
10
-
11
- router.get('/git-config', authenticateToken, async (req, res) => {
12
- try {
13
- const userId = req.user.id;
14
- let gitConfig = await userDb.getGitConfig(userId);
15
-
16
- // If database is empty, try to get from system git config
17
- if (!gitConfig || (!gitConfig.git_name && !gitConfig.git_email)) {
18
- const systemConfig = await getSystemGitConfig();
19
-
20
- // If system has values, save them to database for this user
21
- if (systemConfig.git_name || systemConfig.git_email) {
22
- await userDb.updateGitConfig(userId, systemConfig.git_name, systemConfig.git_email);
23
- gitConfig = systemConfig;
24
- // auto-populated git config from system
25
- }
26
- }
27
-
28
- res.json({
29
- success: true,
30
- gitName: gitConfig?.git_name || null,
31
- gitEmail: gitConfig?.git_email || null
32
- });
33
- } catch (error) {
34
- // git config error
35
- res.status(500).json({ error: 'Failed to get git configuration' });
36
- }
37
- });
38
-
39
- // Apply git config globally via git config --global
40
- router.post('/git-config', authenticateToken, async (req, res) => {
41
- try {
42
- const userId = req.user.id;
43
- const { gitName, gitEmail } = req.body;
44
-
45
- if (!gitName || !gitEmail) {
46
- return res.status(400).json({ error: 'Git name and email are required' });
47
- }
48
-
49
- // Validate email format
50
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
51
- if (!emailRegex.test(gitEmail)) {
52
- return res.status(400).json({ error: 'Invalid email format' });
53
- }
54
-
55
- await userDb.updateGitConfig(userId, gitName, gitEmail);
56
-
57
- try {
58
- await execAsync(`git config --global user.name "${gitName.replace(/"/g, '\\"')}"`);
59
- await execAsync(`git config --global user.email "${gitEmail.replace(/"/g, '\\"')}"`);
60
- // git config applied
61
- } catch (gitError) {
62
- // git config apply error
63
- }
64
-
65
- res.json({
66
- success: true,
67
- gitName,
68
- gitEmail
69
- });
70
- } catch (error) {
71
- // git config update error
72
- res.status(500).json({ error: 'Failed to update git configuration' });
73
- }
74
- });
75
-
76
- router.post('/complete-onboarding', authenticateToken, async (req, res) => {
77
- try {
78
- const userId = req.user.id;
79
- await userDb.completeOnboarding(userId);
80
-
81
- res.json({
82
- success: true,
83
- message: 'Onboarding completed successfully'
84
- });
85
- } catch (error) {
86
- // onboarding error
87
- res.status(500).json({ error: 'Failed to complete onboarding' });
88
- }
89
- });
90
-
91
- router.get('/onboarding-status', authenticateToken, async (req, res) => {
92
- try {
93
- const userId = req.user.id;
94
- const hasCompleted = await userDb.hasCompletedOnboarding(userId);
95
-
96
- res.json({
97
- success: true,
98
- hasCompletedOnboarding: hasCompleted
99
- });
100
- } catch (error) {
101
- // onboarding status error
102
- res.status(500).json({ error: 'Failed to check onboarding status' });
103
- }
104
- });
105
-
106
- export default router;
@@ -1,94 +0,0 @@
1
- import express from 'express';
2
-
3
- const router = express.Router();
4
-
5
- const VAPI_PRIVATE_KEY = process.env.VAPI_PRIVATE_KEY;
6
- const VAPI_PUBLIC_KEY = process.env.VAPI_PUBLIC_KEY;
7
- const VAPI_ASSISTANT_ID = process.env.VAPI_ASSISTANT_ID;
8
-
9
- /**
10
- * POST /api/vapi/chat — Proxy to VAPI Chat API
11
- * Body: { message, chatId? }
12
- * Returns: { reply, chatId }
13
- */
14
- router.post('/chat', async (req, res) => {
15
- if (!VAPI_PRIVATE_KEY || !VAPI_ASSISTANT_ID) {
16
- return res.status(503).json({ error: 'VAPI chat not configured' });
17
- }
18
-
19
- const { message, chatId } = req.body;
20
- if (!message || typeof message !== 'string') {
21
- return res.status(400).json({ error: 'Message is required' });
22
- }
23
-
24
- try {
25
- const body = {
26
- assistantId: VAPI_ASSISTANT_ID,
27
- input: message.slice(0, 2000),
28
- };
29
- if (chatId) body.previousChatId = chatId;
30
-
31
- const response = await fetch('https://api.vapi.ai/chat', {
32
- method: 'POST',
33
- headers: {
34
- 'Authorization': `Bearer ${VAPI_PRIVATE_KEY}`,
35
- 'Content-Type': 'application/json',
36
- },
37
- body: JSON.stringify(body),
38
- });
39
-
40
- if (!response.ok) {
41
- const err = await response.text().catch(() => '');
42
- return res.status(response.status).json({ error: 'VAPI chat request failed' });
43
- }
44
-
45
- const data = await response.json();
46
-
47
- // Extract the assistant's reply from the output array
48
- const assistantMsg = data.output?.find(o => o.role === 'assistant');
49
- const reply = assistantMsg?.content || data.output?.[0]?.content || 'No response';
50
-
51
- res.json({
52
- reply,
53
- chatId: data.chat?.id || chatId || null,
54
- });
55
- } catch (error) {
56
- res.status(500).json({ error: 'Chat request failed' });
57
- }
58
- });
59
-
60
- /**
61
- * POST /api/vapi/call — Create a VAPI web call server-side
62
- * Returns: { webCallUrl, callId }
63
- */
64
- router.post('/call', async (req, res) => {
65
- if (!VAPI_PUBLIC_KEY || !VAPI_ASSISTANT_ID) {
66
- return res.status(503).json({ error: 'VAPI voice not configured' });
67
- }
68
-
69
- try {
70
- const response = await fetch('https://api.vapi.ai/call/web', {
71
- method: 'POST',
72
- headers: {
73
- 'Authorization': `Bearer ${VAPI_PUBLIC_KEY}`,
74
- 'Content-Type': 'application/json',
75
- },
76
- body: JSON.stringify({ assistantId: VAPI_ASSISTANT_ID }),
77
- });
78
-
79
- if (!response.ok) {
80
- const err = await response.text().catch(() => '');
81
- return res.status(response.status).json({ error: 'Failed to create call', details: err });
82
- }
83
-
84
- const data = await response.json();
85
- res.json({
86
- webCallUrl: data.webCallUrl || data.transport?.callUrl,
87
- callId: data.id,
88
- });
89
- } catch (error) {
90
- res.status(500).json({ error: 'Call creation failed' });
91
- }
92
- });
93
-
94
- export default router;
@@ -1,194 +0,0 @@
1
- import express from 'express';
2
- import multer from 'multer';
3
- import path from 'path';
4
- import os from 'os';
5
- import { promises as fs } from 'fs';
6
- import { isWhisperAvailable, transcribeLocal } from '../services/whisperService.js';
7
-
8
- const router = express.Router();
9
- const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: 25 * 1024 * 1024 } }); // 25MB max
10
-
11
- /**
12
- * Helper: Get user's OpenAI key (BYOK) or server env key.
13
- * Imported lazily to avoid circular deps.
14
- */
15
- async function getOpenAIKey(userId) {
16
- try {
17
- const { credentialsDb } = await import('../database/db.js');
18
- const creds = await credentialsDb.getCredentials(userId, 'openai_key');
19
- const active = creds.find(c => c.is_active);
20
- return active?.credential_value || process.env.OPENAI_API_KEY || null;
21
- } catch {
22
- return process.env.OPENAI_API_KEY || null;
23
- }
24
- }
25
-
26
- /**
27
- * POST /api/voice/stt — Speech-to-text
28
- * Primary: OpenAI Whisper API (cloud)
29
- * Fallback: nodejs-whisper (local, when no API key)
30
- */
31
- router.post('/stt', upload.single('audio'), async (req, res) => {
32
- try {
33
- if (!req.file) {
34
- return res.status(400).json({ error: 'No audio file provided' });
35
- }
36
-
37
- const mode = req.body?.mode || 'default';
38
- const apiKey = await getOpenAIKey(req.user?.id);
39
-
40
- // Try OpenAI Whisper API first
41
- if (apiKey) {
42
- try {
43
- const FormData = (await import('form-data')).default;
44
- const formData = new FormData();
45
- formData.append('file', req.file.buffer, {
46
- filename: req.file.originalname || 'audio.webm',
47
- contentType: req.file.mimetype || 'audio/webm'
48
- });
49
- formData.append('model', 'whisper-1');
50
- formData.append('response_format', 'json');
51
- formData.append('language', 'en');
52
-
53
- const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
54
- method: 'POST',
55
- headers: {
56
- 'Authorization': `Bearer ${apiKey}`,
57
- ...formData.getHeaders()
58
- },
59
- body: formData
60
- });
61
-
62
- if (!response.ok) {
63
- const errorData = await response.json().catch(() => ({}));
64
- throw new Error(errorData.error?.message || `Whisper API error: ${response.status}`);
65
- }
66
-
67
- const data = await response.json();
68
- let text = data.text || '';
69
-
70
- // Enhancement modes (prompt, vibe, architect)
71
- if (text && mode !== 'default') {
72
- text = await enhanceTranscription(text, mode, apiKey);
73
- }
74
-
75
- return res.json({ text, source: 'openai' });
76
- } catch (err) {
77
- // If cloud fails, try local fallback
78
- }
79
- }
80
-
81
- // Fallback: local nodejs-whisper
82
- const localAvailable = await isWhisperAvailable();
83
- if (localAvailable) {
84
- // Write buffer to temp file for nodejs-whisper
85
- const tmpFile = path.join(os.tmpdir(), `stt-${Date.now()}.webm`);
86
- await fs.writeFile(tmpFile, req.file.buffer);
87
- try {
88
- const text = await transcribeLocal(tmpFile);
89
- if (text) return res.json({ text, source: 'local' });
90
- } finally {
91
- await fs.unlink(tmpFile).catch(() => {});
92
- }
93
- }
94
-
95
- return res.status(500).json({ error: 'No STT engine available. Add your OpenAI key in Settings > AI Providers, or install nodejs-whisper + ffmpeg for local transcription.' });
96
- } catch (error) {
97
- res.status(500).json({ error: 'Transcription failed' });
98
- }
99
- });
100
-
101
- /**
102
- * POST /api/voice/tts — Text-to-speech using Edge-TTS
103
- * Returns audio/mp3 blob
104
- */
105
- router.post('/tts', async (req, res) => {
106
- try {
107
- const { text, voice = 'en-US-AriaNeural' } = req.body;
108
- if (!text || typeof text !== 'string') {
109
- return res.status(400).json({ error: 'Text is required' });
110
- }
111
-
112
- // Truncate very long text to avoid excessive audio generation
113
- const truncated = text.length > 5000 ? text.slice(0, 5000) : text;
114
-
115
- const { EdgeTTS } = await import('edge-tts-universal');
116
- const tts = new EdgeTTS(truncated, voice);
117
- const result = await tts.synthesize();
118
- const audioBuffer = Buffer.from(await result.audio.arrayBuffer());
119
-
120
- res.setHeader('Content-Type', 'audio/mp3');
121
- res.setHeader('Content-Length', audioBuffer.length);
122
- res.send(audioBuffer);
123
- } catch (error) {
124
- res.status(500).json({ error: 'TTS synthesis failed' });
125
- }
126
- });
127
-
128
- /**
129
- * GET /api/voice/voices — List available TTS voices
130
- */
131
- router.get('/voices', async (req, res) => {
132
- try {
133
- const { listVoices } = await import('edge-tts-universal');
134
- const voices = await listVoices();
135
-
136
- // Filter to English voices and simplify the response
137
- const englishVoices = voices
138
- .filter(v => v.Locale?.startsWith('en-'))
139
- .map(v => ({
140
- id: v.ShortName,
141
- name: v.FriendlyName || v.ShortName,
142
- locale: v.Locale,
143
- gender: v.Gender
144
- }));
145
-
146
- res.json({ voices: englishVoices });
147
- } catch (error) {
148
- res.status(500).json({ error: 'Failed to fetch voices' });
149
- }
150
- });
151
-
152
- /**
153
- * Enhance transcribed text using GPT (prompt engineering, vibe coding, etc.)
154
- */
155
- async function enhanceTranscription(text, mode, apiKey) {
156
- try {
157
- const OpenAI = (await import('openai')).default;
158
- const openai = new OpenAI({ apiKey });
159
-
160
- let systemMessage, prompt, temperature = 0.7, maxTokens = 800;
161
-
162
- switch (mode) {
163
- case 'prompt':
164
- systemMessage = 'You are an expert prompt engineer who creates clear, detailed, and effective prompts.';
165
- prompt = `Transform the following rough instruction into a clear, detailed, and context-aware AI prompt.\n\nYour enhanced prompt should:\n1. Be specific and unambiguous\n2. Include relevant context and constraints\n3. Specify the desired output format\n4. Use clear, actionable language\n\nRough instruction: "${text}"\n\nEnhanced prompt:`;
166
- break;
167
- case 'vibe':
168
- case 'instructions':
169
- case 'architect':
170
- systemMessage = 'You are a helpful assistant that formats ideas into clear, actionable instructions for AI agents.';
171
- temperature = 0.5;
172
- prompt = `Transform the following idea into clear, well-structured instructions that an AI agent can easily understand and execute.\n\nIMPORTANT RULES:\n- Format as clear, step-by-step instructions\n- Add reasonable implementation details based on common patterns\n- Only include details directly related to what was asked\n- Do NOT add features or functionality not mentioned\n- Keep the original intent and scope intact\n\nIdea: "${text}"\n\nAgent instructions:`;
173
- break;
174
- default:
175
- return text;
176
- }
177
-
178
- const completion = await openai.chat.completions.create({
179
- model: 'gpt-4o-mini',
180
- messages: [
181
- { role: 'system', content: systemMessage },
182
- { role: 'user', content: prompt }
183
- ],
184
- temperature,
185
- max_tokens: maxTokens
186
- });
187
-
188
- return completion.choices[0]?.message?.content || text;
189
- } catch {
190
- return text; // Fallback to original on error
191
- }
192
- }
193
-
194
- export default router;
@@ -1,166 +0,0 @@
1
- import express from 'express';
2
- import { webhookDb } from '../database/db.js';
3
-
4
- const router = express.Router();
5
-
6
- // GET /api/webhooks — list all webhooks for the user
7
- router.get('/', async (req, res) => {
8
- try {
9
- const webhooks = await webhookDb.getAll(req.user.id);
10
- res.json({ webhooks });
11
- } catch (error) {
12
- res.status(500).json({ error: 'Failed to fetch webhooks' });
13
- }
14
- });
15
-
16
- // POST /api/webhooks — create a webhook
17
- router.post('/', async (req, res) => {
18
- try {
19
- const { name, url, method, headers, description } = req.body;
20
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
21
- if (!url || !url.trim()) return res.status(400).json({ error: 'URL is required' });
22
-
23
- try { new URL(url); } catch { return res.status(400).json({ error: 'Invalid URL' }); }
24
-
25
- const allowedMethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
26
- const m = (method || 'POST').toUpperCase();
27
- if (!allowedMethods.includes(m)) return res.status(400).json({ error: 'Invalid HTTP method' });
28
-
29
- // Validate headers is valid JSON if provided
30
- let headersStr = '{}';
31
- if (headers) {
32
- if (typeof headers === 'string') {
33
- try { JSON.parse(headers); headersStr = headers; } catch { return res.status(400).json({ error: 'Headers must be valid JSON' }); }
34
- } else {
35
- headersStr = JSON.stringify(headers);
36
- }
37
- }
38
-
39
- const webhook = await webhookDb.create(req.user.id, {
40
- name: name.trim(),
41
- url: url.trim(),
42
- method: m,
43
- headers: headersStr,
44
- description: description?.trim() || null
45
- });
46
- res.json({ success: true, webhook });
47
- } catch (error) {
48
- res.status(500).json({ error: 'Failed to create webhook' });
49
- }
50
- });
51
-
52
- // PUT /api/webhooks/:id — update a webhook
53
- router.put('/:id', async (req, res) => {
54
- try {
55
- const { name, url, method, headers, description } = req.body;
56
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
57
- if (!url || !url.trim()) return res.status(400).json({ error: 'URL is required' });
58
-
59
- try { new URL(url); } catch { return res.status(400).json({ error: 'Invalid URL' }); }
60
-
61
- let headersStr = '{}';
62
- if (headers) {
63
- if (typeof headers === 'string') {
64
- try { JSON.parse(headers); headersStr = headers; } catch { return res.status(400).json({ error: 'Headers must be valid JSON' }); }
65
- } else {
66
- headersStr = JSON.stringify(headers);
67
- }
68
- }
69
-
70
- const updated = await webhookDb.update(Number(req.params.id), req.user.id, {
71
- name: name.trim(),
72
- url: url.trim(),
73
- method: (method || 'POST').toUpperCase(),
74
- headers: headersStr,
75
- description: description?.trim() || null
76
- });
77
-
78
- if (!updated) return res.status(404).json({ error: 'Webhook not found' });
79
- res.json({ success: true });
80
- } catch (error) {
81
- res.status(500).json({ error: 'Failed to update webhook' });
82
- }
83
- });
84
-
85
- // DELETE /api/webhooks/:id — delete a webhook
86
- router.delete('/:id', async (req, res) => {
87
- try {
88
- const deleted = await webhookDb.delete(Number(req.params.id), req.user.id);
89
- if (!deleted) return res.status(404).json({ error: 'Webhook not found' });
90
- res.json({ success: true });
91
- } catch (error) {
92
- res.status(500).json({ error: 'Failed to delete webhook' });
93
- }
94
- });
95
-
96
- // POST /api/webhooks/:id/test — fire a test request to the webhook
97
- router.post('/:id/test', async (req, res) => {
98
- try {
99
- const webhook = await webhookDb.getById(Number(req.params.id), req.user.id);
100
- if (!webhook) return res.status(404).json({ error: 'Webhook not found' });
101
-
102
- let parsedHeaders = {};
103
- try { parsedHeaders = JSON.parse(webhook.headers || '{}'); } catch { /* ignore */ }
104
-
105
- const controller = new AbortController();
106
- const timeout = setTimeout(() => controller.abort(), 10000);
107
-
108
- try {
109
- const fetchOptions = {
110
- method: webhook.method,
111
- headers: {
112
- 'Content-Type': 'application/json',
113
- 'User-Agent': 'UpfynAI-Webhook/1.0',
114
- ...parsedHeaders
115
- },
116
- signal: controller.signal
117
- };
118
-
119
- // Add test body for methods that support it
120
- if (['POST', 'PUT', 'PATCH'].includes(webhook.method)) {
121
- fetchOptions.body = JSON.stringify({
122
- event: 'test',
123
- webhook_id: webhook.id,
124
- webhook_name: webhook.name,
125
- timestamp: new Date().toISOString()
126
- });
127
- }
128
-
129
- const response = await fetch(webhook.url, fetchOptions);
130
- clearTimeout(timeout);
131
-
132
- let body;
133
- const contentType = response.headers.get('content-type') || '';
134
- if (contentType.includes('application/json')) {
135
- body = await response.json();
136
- } else {
137
- body = await response.text();
138
- if (body.length > 2000) body = body.slice(0, 2000) + '...';
139
- }
140
-
141
- await webhookDb.updateLastTriggered(webhook.id);
142
-
143
- res.json({
144
- success: true,
145
- result: {
146
- status: response.status,
147
- statusText: response.statusText,
148
- body,
149
- headers: Object.fromEntries(response.headers.entries())
150
- }
151
- });
152
- } catch (fetchError) {
153
- clearTimeout(timeout);
154
- res.json({
155
- success: false,
156
- result: {
157
- error: fetchError.name === 'AbortError' ? 'Request timed out (10s)' : fetchError.message
158
- }
159
- });
160
- }
161
- } catch (error) {
162
- res.status(500).json({ error: 'Failed to test webhook' });
163
- }
164
- });
165
-
166
- export default router;
@@ -1,118 +0,0 @@
1
- import express from 'express';
2
- import { workflowDb, webhookDb } from '../database/db.js';
3
- import { refreshWorkflowSchedule, stopWorkflowSchedule, executeWorkflow } from '../services/workflowScheduler.js';
4
-
5
- const router = express.Router();
6
-
7
- // GET /api/workflows — list all workflows for the user
8
- router.get('/', async (req, res) => {
9
- try {
10
- const workflows = await workflowDb.getAll(req.user.id);
11
- const parsed = workflows.map(w => ({
12
- ...w,
13
- steps: typeof w.steps === 'string' ? JSON.parse(w.steps) : w.steps
14
- }));
15
- res.json({ workflows: parsed });
16
- } catch (error) {
17
- res.status(500).json({ error: 'Failed to fetch workflows' });
18
- }
19
- });
20
-
21
- // POST /api/workflows — create a workflow
22
- router.post('/', async (req, res) => {
23
- try {
24
- const { name, description, steps, schedule, schedule_enabled, schedule_timezone } = req.body;
25
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
26
- if (!Array.isArray(steps)) return res.status(400).json({ error: 'Steps must be an array' });
27
-
28
- const workflow = await workflowDb.create(req.user.id, {
29
- name: name.trim(),
30
- description: description?.trim() || null,
31
- steps,
32
- schedule: schedule || null,
33
- schedule_enabled: !!schedule_enabled,
34
- schedule_timezone: schedule_timezone || 'UTC'
35
- });
36
-
37
- // Sync cron scheduler
38
- if (workflow.id) refreshWorkflowSchedule(workflow.id, req.user.id);
39
-
40
- res.json({ success: true, workflow });
41
- } catch (error) {
42
- res.status(500).json({ error: 'Failed to create workflow' });
43
- }
44
- });
45
-
46
- // PUT /api/workflows/:id — update a workflow
47
- router.put('/:id', async (req, res) => {
48
- try {
49
- const { name, description, steps, schedule, schedule_enabled, schedule_timezone } = req.body;
50
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
51
-
52
- const wfId = Number(req.params.id);
53
- const updated = await workflowDb.update(wfId, req.user.id, {
54
- name: name.trim(),
55
- description: description?.trim() || null,
56
- steps: steps || [],
57
- schedule: schedule || null,
58
- schedule_enabled: !!schedule_enabled,
59
- schedule_timezone: schedule_timezone || 'UTC'
60
- });
61
-
62
- if (!updated) return res.status(404).json({ error: 'Workflow not found' });
63
-
64
- // Sync cron scheduler
65
- refreshWorkflowSchedule(wfId, req.user.id);
66
-
67
- res.json({ success: true });
68
- } catch (error) {
69
- res.status(500).json({ error: 'Failed to update workflow' });
70
- }
71
- });
72
-
73
- // DELETE /api/workflows/:id — delete a workflow
74
- router.delete('/:id', async (req, res) => {
75
- try {
76
- const wfId = Number(req.params.id);
77
- const deleted = await workflowDb.delete(wfId, req.user.id);
78
- if (!deleted) return res.status(404).json({ error: 'Workflow not found' });
79
-
80
- stopWorkflowSchedule(wfId);
81
-
82
- res.json({ success: true });
83
- } catch (error) {
84
- res.status(500).json({ error: 'Failed to delete workflow' });
85
- }
86
- });
87
-
88
- // POST /api/workflows/:id/run — execute a workflow (manual trigger)
89
- router.post('/:id/run', async (req, res) => {
90
- try {
91
- const workflow = await workflowDb.getById(Number(req.params.id), req.user.id);
92
- if (!workflow) return res.status(404).json({ error: 'Workflow not found' });
93
-
94
- const steps = typeof workflow.steps === 'string' ? JSON.parse(workflow.steps) : workflow.steps;
95
- if (!steps.length) return res.status(400).json({ error: 'Workflow has no steps' });
96
-
97
- const result = await executeWorkflow({ ...workflow, steps });
98
- res.json(result);
99
- } catch (error) {
100
- res.status(500).json({ error: 'Failed to run workflow' });
101
- }
102
- });
103
-
104
- // GET /api/workflows/:id/runs — list execution history for a workflow
105
- router.get('/:id/runs', async (req, res) => {
106
- try {
107
- const runs = await workflowDb.getRuns(Number(req.params.id), req.user.id);
108
- const parsed = runs.map(r => ({
109
- ...r,
110
- result: typeof r.result === 'string' ? (() => { try { return JSON.parse(r.result); } catch { return r.result; } })() : r.result
111
- }));
112
- res.json({ runs: parsed });
113
- } catch (error) {
114
- res.status(500).json({ error: 'Failed to fetch workflow runs' });
115
- }
116
- });
117
-
118
- export default router;