upfynai-code 3.0.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +69 -92
  2. package/bin/cli.js +191 -0
  3. package/dist/client/assets/AppContent-M14Au3SB.js +542 -0
  4. package/{client/dist/assets/BrowserPanel-0TLEl-IC.js → dist/client/assets/BrowserPanel-TFKm2NDJ.js} +2 -2
  5. package/dist/client/assets/DashboardPanel-C88HjsCh.js +1 -0
  6. package/dist/client/assets/FileTree-DvO1xnDE.js +1 -0
  7. package/{client/dist/assets/GitPanel-C_xFM-N2.js → dist/client/assets/GitPanel-D-slVlyy.js} +2 -2
  8. package/dist/client/assets/LoginModal-Chi4SYcr.js +21 -0
  9. package/{client/dist/assets/MarkdownPreview-CESjI261.js → dist/client/assets/MarkdownPreview-CuIix2u9.js} +1 -1
  10. package/dist/client/assets/MermaidBlock-Dq9uFv82.js +2 -0
  11. package/dist/client/assets/Onboarding-QYXx24dX.js +1 -0
  12. package/{client/dist/assets/PreviewPanel-CqCa92Tf.js → dist/client/assets/PreviewPanel-Dd8q-jo0.js} +1 -1
  13. package/dist/client/assets/SetupForm-CrspaUva.js +1 -0
  14. package/dist/client/assets/WorkflowsPanel-DIlYAdhB.js +1 -0
  15. package/dist/client/assets/index-CnNNzw9A.css +1 -0
  16. package/{client/dist/assets/index-HaY-3pK1.js → dist/client/assets/index-rUkK9FDP.js} +26 -26
  17. package/{client/dist/assets/vendor-codemirror-D2ALgpaX.js → dist/client/assets/vendor-codemirror-jc6nyJQg.js} +1 -1
  18. package/{client/dist/assets/vendor-diff-DNQpbhrT.js → dist/client/assets/vendor-diff-THJmAcEI.js} +1 -1
  19. package/{client/dist/assets/vendor-icons-GyYE35HP.js → dist/client/assets/vendor-icons-CfjIpdrD.js} +145 -155
  20. package/{client/dist/assets/vendor-markdown-CimbIo6Y.js → dist/client/assets/vendor-markdown-Cdm6NEGf.js} +1 -1
  21. package/dist/client/assets/vendor-mermaid-DTPaBx-U.js +2559 -0
  22. package/{client/dist/assets/vendor-react-96lCPsRK.js → dist/client/assets/vendor-react-wFkb6mSf.js} +1 -1
  23. package/{client/dist/assets/vendor-syntax-LS_Nt30I.js → dist/client/assets/vendor-syntax-C_UZR7tc.js} +1 -1
  24. package/dist/client/favicon.png +0 -0
  25. package/dist/client/icons/icon-128x128.png +0 -0
  26. package/dist/client/icons/icon-144x144.png +0 -0
  27. package/dist/client/icons/icon-152x152.png +0 -0
  28. package/dist/client/icons/icon-192x192.png +0 -0
  29. package/dist/client/icons/icon-384x384.png +0 -0
  30. package/dist/client/icons/icon-512x512.png +0 -0
  31. package/dist/client/icons/icon-72x72.png +0 -0
  32. package/dist/client/icons/icon-96x96.png +0 -0
  33. package/{client/dist → dist/client}/index.html +37 -36
  34. package/dist/client/logo-128.png +0 -0
  35. package/dist/client/logo-256.png +0 -0
  36. package/dist/client/logo-32.png +0 -0
  37. package/dist/client/logo-512.png +0 -0
  38. package/dist/client/logo-64.png +0 -0
  39. package/dist/client/logo.png +0 -0
  40. package/{client/dist → dist/client}/manifest.json +12 -12
  41. package/{client/dist → dist/client}/mcp-docs.html +1 -1
  42. package/{client/dist → dist/client}/sw.js +2 -2
  43. package/package.json +56 -105
  44. package/scripts/postinstall.js +9 -0
  45. package/scripts/prepublish.js +77 -0
  46. package/src/animation.js +228 -0
  47. package/src/auth.js +142 -0
  48. package/src/config.js +40 -0
  49. package/src/connect.js +416 -0
  50. package/src/launch.js +81 -0
  51. package/src/mcp.js +57 -0
  52. package/src/permissions.js +140 -0
  53. package/src/persistent-shell.js +261 -0
  54. package/src/server.js +54 -0
  55. package/client/dist/assets/AppContent-CwrTP6TW.js +0 -545
  56. package/client/dist/assets/CanvasFullScreen-D1GWQsGL.js +0 -1
  57. package/client/dist/assets/CanvasWorkspace-D7ORj358.js +0 -163
  58. package/client/dist/assets/DashboardPanel-BV7ybUDe.js +0 -1
  59. package/client/dist/assets/FileTree-5qfhBqdE.js +0 -1
  60. package/client/dist/assets/LoginModal-CImJHRjX.js +0 -13
  61. package/client/dist/assets/MermaidBlock-BFM21cwe.js +0 -2
  62. package/client/dist/assets/Onboarding-B3cteLu2.js +0 -1
  63. package/client/dist/assets/SetupForm-P6dsYgHO.js +0 -1
  64. package/client/dist/assets/WorkflowsPanel-CBoN80kc.js +0 -1
  65. package/client/dist/assets/index-46kkVu2i.css +0 -1
  66. package/client/dist/assets/pdf-CE_K4jFx.js +0 -12
  67. package/client/dist/assets/vendor-canvas-BZV40eAE.css +0 -1
  68. package/client/dist/assets/vendor-canvas-DvHJ_Pn2.js +0 -49
  69. package/client/dist/assets/vendor-mermaid-DucWyDEe.js +0 -2556
  70. package/client/dist/favicon.png +0 -0
  71. package/client/dist/icons/icon-128x128.png +0 -0
  72. package/client/dist/icons/icon-144x144.png +0 -0
  73. package/client/dist/icons/icon-152x152.png +0 -0
  74. package/client/dist/icons/icon-192x192.png +0 -0
  75. package/client/dist/icons/icon-384x384.png +0 -0
  76. package/client/dist/icons/icon-512x512.png +0 -0
  77. package/client/dist/icons/icon-72x72.png +0 -0
  78. package/client/dist/icons/icon-96x96.png +0 -0
  79. package/client/dist/logo-128.png +0 -0
  80. package/client/dist/logo-256.png +0 -0
  81. package/client/dist/logo-32.png +0 -0
  82. package/client/dist/logo-512.png +0 -0
  83. package/client/dist/logo-64.png +0 -0
  84. package/commands/upfynai-connect.md +0 -59
  85. package/commands/upfynai-disconnect.md +0 -31
  86. package/commands/upfynai-doctor.md +0 -99
  87. package/commands/upfynai-export.md +0 -49
  88. package/commands/upfynai-local.md +0 -82
  89. package/commands/upfynai-status.md +0 -75
  90. package/commands/upfynai-stop.md +0 -49
  91. package/commands/upfynai-uninstall.md +0 -58
  92. package/commands/upfynai.md +0 -69
  93. package/scripts/build-client.js +0 -17
  94. package/scripts/fix-node-pty.js +0 -67
  95. package/scripts/install-commands.js +0 -78
  96. package/server/agent-loop.js +0 -242
  97. package/server/auto-compact.js +0 -99
  98. package/server/browser.js +0 -131
  99. package/server/claude-sdk.js +0 -797
  100. package/server/cli-ui.js +0 -798
  101. package/server/cli.js +0 -751
  102. package/server/constants/config.js +0 -31
  103. package/server/cursor-cli.js +0 -270
  104. package/server/database/auth.db +0 -0
  105. package/server/database/db.js +0 -1547
  106. package/server/database/init.sql +0 -70
  107. package/server/index.js +0 -3813
  108. package/server/load-env.js +0 -26
  109. package/server/mcp-server.js +0 -621
  110. package/server/middleware/auth.js +0 -184
  111. package/server/middleware/relayHelpers.js +0 -44
  112. package/server/middleware/sandboxRouter.js +0 -174
  113. package/server/openai-codex.js +0 -403
  114. package/server/openrouter.js +0 -137
  115. package/server/projects.js +0 -1807
  116. package/server/provider-factory.js +0 -174
  117. package/server/relay-client.js +0 -390
  118. package/server/routes/agent.js +0 -1234
  119. package/server/routes/auth.js +0 -559
  120. package/server/routes/browser.js +0 -419
  121. package/server/routes/canvas.js +0 -53
  122. package/server/routes/cli-auth.js +0 -263
  123. package/server/routes/codex.js +0 -396
  124. package/server/routes/commands.js +0 -707
  125. package/server/routes/composio.js +0 -176
  126. package/server/routes/cursor.js +0 -770
  127. package/server/routes/dashboard.js +0 -295
  128. package/server/routes/git.js +0 -1208
  129. package/server/routes/keys.js +0 -34
  130. package/server/routes/mcp-utils.js +0 -48
  131. package/server/routes/mcp.js +0 -661
  132. package/server/routes/payments.js +0 -227
  133. package/server/routes/projects.js +0 -754
  134. package/server/routes/sessions.js +0 -146
  135. package/server/routes/settings.js +0 -261
  136. package/server/routes/taskmaster.js +0 -1928
  137. package/server/routes/user.js +0 -106
  138. package/server/routes/vapi-chat.js +0 -624
  139. package/server/routes/voice.js +0 -235
  140. package/server/routes/webhooks.js +0 -166
  141. package/server/routes/workflows.js +0 -312
  142. package/server/sandbox.js +0 -120
  143. package/server/services/browser-ai.js +0 -154
  144. package/server/services/composio.js +0 -204
  145. package/server/services/sessionRegistry.js +0 -139
  146. package/server/services/whisperService.js +0 -84
  147. package/server/services/workflowScheduler.js +0 -211
  148. package/server/tests/relay-flow.test.js +0 -570
  149. package/server/tests/sessions.test.js +0 -259
  150. package/server/utils/commandParser.js +0 -303
  151. package/server/utils/email.js +0 -66
  152. package/server/utils/gitConfig.js +0 -24
  153. package/server/utils/mcp-detector.js +0 -198
  154. package/server/utils/taskmaster-websocket.js +0 -129
  155. package/shared/integrationCatalog.d.ts +0 -12
  156. package/shared/integrationCatalog.js +0 -172
  157. package/shared/modelConstants.js +0 -96
  158. /package/{shared → dist}/agents/claude.js +0 -0
  159. /package/{shared → dist}/agents/codex.js +0 -0
  160. /package/{shared → dist}/agents/cursor.js +0 -0
  161. /package/{shared → dist}/agents/detect.js +0 -0
  162. /package/{shared → dist}/agents/exec.js +0 -0
  163. /package/{shared → dist}/agents/files.js +0 -0
  164. /package/{shared → dist}/agents/git.js +0 -0
  165. /package/{shared → dist}/agents/gitagent.js +0 -0
  166. /package/{shared → dist}/agents/index.js +0 -0
  167. /package/{shared → dist}/agents/shell.js +0 -0
  168. /package/{shared → dist}/agents/utils.js +0 -0
  169. /package/{client/dist → dist/client}/api-docs.html +0 -0
  170. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  171. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  172. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  173. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  174. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  175. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  176. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  177. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  178. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  179. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  180. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  181. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  182. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  183. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  184. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  185. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  186. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  187. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  188. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  189. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  190. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  191. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  192. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  193. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  194. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  195. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  196. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  197. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  198. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  199. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  200. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  201. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  202. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  203. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  204. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  205. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  206. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  207. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  208. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  209. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  210. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  211. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  212. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  213. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  214. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  215. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  216. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  217. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  218. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  219. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  220. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  221. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  222. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  223. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  224. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  225. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  226. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  227. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  228. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  229. /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
  230. /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
  231. /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +0 -0
  232. /package/{client/dist → dist/client}/clear-cache.html +0 -0
  233. /package/{client/dist → dist/client}/convert-icons.md +0 -0
  234. /package/{client/dist → dist/client}/favicon.svg +0 -0
  235. /package/{client/dist → dist/client}/generate-icons.js +0 -0
  236. /package/{client/dist → dist/client}/icons/claude-ai-icon.svg +0 -0
  237. /package/{client/dist → dist/client}/icons/codex-white.svg +0 -0
  238. /package/{client/dist → dist/client}/icons/codex.svg +0 -0
  239. /package/{client/dist → dist/client}/icons/cursor-white.svg +0 -0
  240. /package/{client/dist → dist/client}/icons/cursor.svg +0 -0
  241. /package/{client/dist → dist/client}/icons/icon-128x128.svg +0 -0
  242. /package/{client/dist → dist/client}/icons/icon-144x144.svg +0 -0
  243. /package/{client/dist → dist/client}/icons/icon-152x152.svg +0 -0
  244. /package/{client/dist → dist/client}/icons/icon-192x192.svg +0 -0
  245. /package/{client/dist → dist/client}/icons/icon-384x384.svg +0 -0
  246. /package/{client/dist → dist/client}/icons/icon-512x512.svg +0 -0
  247. /package/{client/dist → dist/client}/icons/icon-72x72.svg +0 -0
  248. /package/{client/dist → dist/client}/icons/icon-96x96.svg +0 -0
  249. /package/{client/dist → dist/client}/icons/icon-template.svg +0 -0
  250. /package/{client/dist → dist/client}/logo.svg +0 -0
  251. /package/{client/dist → dist/client}/offline.html +0 -0
  252. /package/{client/dist → dist/client}/screenshots/cli-selection.png +0 -0
  253. /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
  254. /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
  255. /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
  256. /package/{shared → dist}/gitagent/index.js +0 -0
  257. /package/{shared → dist}/gitagent/parser.js +0 -0
  258. /package/{shared → dist}/gitagent/prompt-builder.js +0 -0
@@ -1,312 +0,0 @@
1
- import express from 'express';
2
- import { workflowDb, webhookDb, credentialsDb } from '../database/db.js';
3
- import { refreshWorkflowSchedule, stopWorkflowSchedule, executeWorkflow } from '../services/workflowScheduler.js';
4
- import { INTEGRATION_CATALOG } from '../../shared/integrationCatalog.js';
5
-
6
- const router = express.Router();
7
-
8
- // ── BYOK helpers ────────────────────────────────────────────────────
9
- async function getUserProviderKey(userId, providerType) {
10
- if (!userId) return null;
11
- try {
12
- const creds = await credentialsDb.getCredentials(userId, providerType);
13
- const active = creds.find(c => c.is_active);
14
- return active?.credential_value || null;
15
- } catch { return null; }
16
- }
17
-
18
- // ── AI workflow generation prompt ───────────────────────────────────
19
- function buildWorkflowGenerationPrompt(description, availableWebhooks, connectedIntegrations) {
20
- const integrationsList = INTEGRATION_CATALOG.map(i => {
21
- const connected = connectedIntegrations.includes(i.id);
22
- const actions = i.popularActions.map(a => ` - ${a.slug}: ${a.label} (params: ${a.params.join(', ')})`).join('\n');
23
- return `${i.name} (${i.id})${connected ? ' [CONNECTED]' : ''}:\n${actions}`;
24
- }).join('\n\n');
25
-
26
- const webhooksList = availableWebhooks.length
27
- ? availableWebhooks.map(w => `- ID ${w.id}: "${w.name}" (${w.method} ${w.url})`).join('\n')
28
- : 'No webhooks configured.';
29
-
30
- return `You are a workflow automation builder. Given a natural language description, generate a workflow JSON.
31
-
32
- AVAILABLE STEP TYPES:
33
- 1. "ai-prompt" — Run an AI prompt. Config: { prompt: string }
34
- 2. "webhook" — Call an HTTP endpoint. Config: { webhookId: string, payloadTemplate?: string }
35
- 3. "delay" — Wait N seconds (max 30). Config: { seconds: number }
36
- 4. "condition" — Branch on expression. Config: { expression: string }
37
- 5. "integration" — Use a connected app via Composio. Config: { integrationId: string, toolSlug: string, arguments: { param: value } }
38
-
39
- AVAILABLE INTEGRATIONS:
40
- ${integrationsList}
41
-
42
- AVAILABLE WEBHOOKS:
43
- ${webhooksList}
44
-
45
- RULES:
46
- - Each step needs: id (unique string), type, label (human-readable), config, order (0-indexed)
47
- - Step IDs should be like "step_1", "step_2", etc.
48
- - For integration steps, only use integrations marked [CONNECTED] or suggest connecting them
49
- - Use {{prev.field}} syntax in config values to reference previous step output
50
- - If the user mentions a schedule, include schedule (cron) and schedule_enabled: true
51
- - Keep workflows focused and practical
52
-
53
- Respond with ONLY valid JSON in this exact format (no markdown, no explanation):
54
- {
55
- "name": "Workflow Name",
56
- "description": "What this workflow does",
57
- "steps": [...],
58
- "schedule": null,
59
- "schedule_enabled": false,
60
- "schedule_timezone": "UTC"
61
- }
62
-
63
- USER REQUEST: ${description}`;
64
- }
65
-
66
- // ── Routes ──────────────────────────────────────────────────────────
67
-
68
- // GET /api/workflows — list all workflows for the user
69
- router.get('/', async (req, res) => {
70
- try {
71
- const workflows = await workflowDb.getAll(req.user.id);
72
- const parsed = workflows.map(w => ({
73
- ...w,
74
- steps: typeof w.steps === 'string' ? JSON.parse(w.steps) : w.steps
75
- }));
76
- res.json({ workflows: parsed });
77
- } catch (error) {
78
- res.status(500).json({ error: 'Failed to fetch workflows' });
79
- }
80
- });
81
-
82
- // POST /api/workflows — create a workflow
83
- router.post('/', async (req, res) => {
84
- try {
85
- const { name, description, steps, schedule, schedule_enabled, schedule_timezone } = req.body;
86
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
87
- if (!Array.isArray(steps)) return res.status(400).json({ error: 'Steps must be an array' });
88
-
89
- const workflow = await workflowDb.create(req.user.id, {
90
- name: name.trim(),
91
- description: description?.trim() || null,
92
- steps,
93
- schedule: schedule || null,
94
- schedule_enabled: !!schedule_enabled,
95
- schedule_timezone: schedule_timezone || 'UTC'
96
- });
97
-
98
- // Sync cron scheduler
99
- if (workflow.id) refreshWorkflowSchedule(workflow.id, req.user.id);
100
-
101
- res.json({ success: true, workflow });
102
- } catch (error) {
103
- res.status(500).json({ error: 'Failed to create workflow' });
104
- }
105
- });
106
-
107
- // POST /api/workflows/generate — AI-powered workflow generation from natural language
108
- router.post('/generate', async (req, res) => {
109
- try {
110
- const { description } = req.body;
111
- if (!description || !description.trim()) {
112
- return res.status(400).json({ error: 'Description is required' });
113
- }
114
-
115
- // Get user's API key (try Anthropic first, then OpenRouter)
116
- let apiKey = await getUserProviderKey(req.user.id, 'anthropic_key');
117
- let provider = 'anthropic';
118
-
119
- if (!apiKey) {
120
- apiKey = await getUserProviderKey(req.user.id, 'openrouter_key');
121
- provider = 'openrouter';
122
- }
123
-
124
- // Fall back to server key
125
- if (!apiKey) {
126
- apiKey = process.env.ANTHROPIC_API_KEY;
127
- provider = 'anthropic';
128
- }
129
-
130
- if (!apiKey) {
131
- return res.status(400).json({ error: 'No AI provider key available. Add an API key in Settings > AI Providers.' });
132
- }
133
-
134
- // Get available webhooks and connected integrations for context
135
- const webhooks = await webhookDb.getAll(req.user.id);
136
- const connectedIntegrations = []; // Will be populated if Composio is available
137
-
138
- const prompt = buildWorkflowGenerationPrompt(description.trim(), webhooks, connectedIntegrations);
139
-
140
- let generatedJson;
141
-
142
- if (provider === 'anthropic') {
143
- const response = await fetch('https://api.anthropic.com/v1/messages', {
144
- method: 'POST',
145
- headers: {
146
- 'x-api-key': apiKey,
147
- 'anthropic-version': '2023-06-01',
148
- 'content-type': 'application/json',
149
- },
150
- body: JSON.stringify({
151
- model: 'claude-sonnet-4-20250514',
152
- max_tokens: 2048,
153
- messages: [{ role: 'user', content: prompt }],
154
- }),
155
- });
156
-
157
- if (!response.ok) {
158
- return res.status(502).json({ error: 'AI provider returned an error' });
159
- }
160
-
161
- const data = await response.json();
162
- const text = data.content?.[0]?.text || '';
163
- generatedJson = text.trim();
164
- } else {
165
- // OpenRouter
166
- const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
167
- method: 'POST',
168
- headers: {
169
- 'Authorization': `Bearer ${apiKey}`,
170
- 'Content-Type': 'application/json',
171
- 'HTTP-Referer': 'https://cli.upfyn.com',
172
- 'X-Title': 'Upfyn-Code',
173
- },
174
- body: JSON.stringify({
175
- model: 'anthropic/claude-sonnet-4',
176
- messages: [{ role: 'user', content: prompt }],
177
- max_tokens: 2048,
178
- }),
179
- });
180
-
181
- if (!response.ok) {
182
- return res.status(502).json({ error: 'AI provider returned an error' });
183
- }
184
-
185
- const data = await response.json();
186
- generatedJson = data.choices?.[0]?.message?.content?.trim() || '';
187
- }
188
-
189
- // Parse the AI response — strip markdown fences if present
190
- let cleaned = generatedJson;
191
- if (cleaned.startsWith('```')) {
192
- cleaned = cleaned.replace(/^```(?:json)?\n?/, '').replace(/\n?```$/, '');
193
- }
194
-
195
- let workflow;
196
- try {
197
- workflow = JSON.parse(cleaned);
198
- } catch {
199
- return res.status(422).json({ error: 'AI generated invalid workflow format. Try rephrasing your description.' });
200
- }
201
-
202
- // Validate required fields
203
- if (!workflow.name || !Array.isArray(workflow.steps)) {
204
- return res.status(422).json({ error: 'AI generated incomplete workflow. Try being more specific.' });
205
- }
206
-
207
- // Ensure step IDs are unique
208
- workflow.steps = workflow.steps.map((step, i) => ({
209
- ...step,
210
- id: step.id || `step_${Date.now()}_${i}`,
211
- order: i,
212
- }));
213
-
214
- // Save to Turso
215
- const saved = await workflowDb.create(req.user.id, {
216
- name: workflow.name.trim(),
217
- description: workflow.description?.trim() || description.trim(),
218
- steps: workflow.steps,
219
- schedule: workflow.schedule || null,
220
- schedule_enabled: !!workflow.schedule_enabled,
221
- schedule_timezone: workflow.schedule_timezone || 'UTC',
222
- });
223
-
224
- if (saved.id && workflow.schedule_enabled) {
225
- refreshWorkflowSchedule(saved.id, req.user.id);
226
- }
227
-
228
- res.json({
229
- success: true,
230
- workflow: {
231
- ...saved,
232
- steps: workflow.steps,
233
- },
234
- });
235
- } catch (error) {
236
- res.status(500).json({ error: 'Failed to generate workflow' });
237
- }
238
- });
239
-
240
- // PUT /api/workflows/:id — update a workflow
241
- router.put('/:id', async (req, res) => {
242
- try {
243
- const { name, description, steps, schedule, schedule_enabled, schedule_timezone } = req.body;
244
- if (!name || !name.trim()) return res.status(400).json({ error: 'Name is required' });
245
-
246
- const wfId = Number(req.params.id);
247
- const updated = await workflowDb.update(wfId, req.user.id, {
248
- name: name.trim(),
249
- description: description?.trim() || null,
250
- steps: steps || [],
251
- schedule: schedule || null,
252
- schedule_enabled: !!schedule_enabled,
253
- schedule_timezone: schedule_timezone || 'UTC'
254
- });
255
-
256
- if (!updated) return res.status(404).json({ error: 'Workflow not found' });
257
-
258
- // Sync cron scheduler
259
- refreshWorkflowSchedule(wfId, req.user.id);
260
-
261
- res.json({ success: true });
262
- } catch (error) {
263
- res.status(500).json({ error: 'Failed to update workflow' });
264
- }
265
- });
266
-
267
- // DELETE /api/workflows/:id — delete a workflow
268
- router.delete('/:id', async (req, res) => {
269
- try {
270
- const wfId = Number(req.params.id);
271
- const deleted = await workflowDb.delete(wfId, req.user.id);
272
- if (!deleted) return res.status(404).json({ error: 'Workflow not found' });
273
-
274
- stopWorkflowSchedule(wfId);
275
-
276
- res.json({ success: true });
277
- } catch (error) {
278
- res.status(500).json({ error: 'Failed to delete workflow' });
279
- }
280
- });
281
-
282
- // POST /api/workflows/:id/run — execute a workflow (manual trigger)
283
- router.post('/:id/run', async (req, res) => {
284
- try {
285
- const workflow = await workflowDb.getById(Number(req.params.id), req.user.id);
286
- if (!workflow) return res.status(404).json({ error: 'Workflow not found' });
287
-
288
- const steps = typeof workflow.steps === 'string' ? JSON.parse(workflow.steps) : workflow.steps;
289
- if (!steps.length) return res.status(400).json({ error: 'Workflow has no steps' });
290
-
291
- const result = await executeWorkflow({ ...workflow, steps });
292
- res.json(result);
293
- } catch (error) {
294
- res.status(500).json({ error: 'Failed to run workflow' });
295
- }
296
- });
297
-
298
- // GET /api/workflows/:id/runs — list execution history for a workflow
299
- router.get('/:id/runs', async (req, res) => {
300
- try {
301
- const runs = await workflowDb.getRuns(Number(req.params.id), req.user.id);
302
- const parsed = runs.map(r => ({
303
- ...r,
304
- result: typeof r.result === 'string' ? (() => { try { return JSON.parse(r.result); } catch { return r.result; } })() : r.result
305
- }));
306
- res.json({ runs: parsed });
307
- } catch (error) {
308
- res.status(500).json({ error: 'Failed to fetch workflow runs' });
309
- }
310
- });
311
-
312
- export default router;
package/server/sandbox.js DELETED
@@ -1,120 +0,0 @@
1
- /**
2
- * Sandbox Client — connects the backend to the separate sandbox-service on Railway.
3
- * All sandbox operations are proxied to the sandbox service via HTTP.
4
- */
5
-
6
- const SANDBOX_SERVICE_URL = process.env.SANDBOX_SERVICE_URL || 'http://localhost:4300';
7
- const SANDBOX_SERVICE_SECRET = process.env.SANDBOX_SERVICE_SECRET || 'dev-sandbox-secret';
8
-
9
- async function sandboxFetch(path, userId, body = null) {
10
- const opts = {
11
- method: body ? 'POST' : 'GET',
12
- headers: {
13
- 'Content-Type': 'application/json',
14
- 'x-sandbox-secret': SANDBOX_SERVICE_SECRET,
15
- 'x-user-id': String(userId),
16
- },
17
- };
18
- if (body) opts.body = JSON.stringify(body);
19
-
20
- const res = await fetch(`${SANDBOX_SERVICE_URL}${path}`, opts);
21
- const data = await res.json();
22
- if (!res.ok) throw new Error(data.error || `Sandbox service error: ${res.status}`);
23
- return data;
24
- }
25
-
26
- const sandboxClient = {
27
-
28
- /**
29
- * Check if the sandbox service is reachable.
30
- */
31
- async isAvailable() {
32
- try {
33
- const res = await fetch(`${SANDBOX_SERVICE_URL}/health`, { signal: AbortSignal.timeout(3000) });
34
- return res.ok;
35
- } catch {
36
- return false;
37
- }
38
- },
39
-
40
- /**
41
- * Initialize a user's sandbox (creates if doesn't exist).
42
- */
43
- async initSandbox(userId) {
44
- return sandboxFetch('/api/sandbox/init', userId, {});
45
- },
46
-
47
- /**
48
- * Get sandbox status.
49
- */
50
- async getStatus(userId) {
51
- return sandboxFetch('/api/sandbox/status', userId);
52
- },
53
-
54
- /**
55
- * Destroy a user's sandbox.
56
- */
57
- async destroySandbox(userId) {
58
- const res = await fetch(`${SANDBOX_SERVICE_URL}/api/sandbox`, {
59
- method: 'DELETE',
60
- headers: {
61
- 'Content-Type': 'application/json',
62
- 'x-sandbox-secret': SANDBOX_SERVICE_SECRET,
63
- 'x-user-id': String(userId),
64
- },
65
- });
66
- const data = await res.json();
67
- if (!res.ok) throw new Error(data.error || 'Failed to destroy sandbox');
68
- return data;
69
- },
70
-
71
- /**
72
- * Execute a command in the user's sandbox.
73
- */
74
- async exec(userId, command, opts = {}) {
75
- return sandboxFetch('/api/exec', userId, {
76
- command,
77
- cwd: opts.cwd,
78
- timeout: opts.timeout,
79
- userKeys: opts.userKeys,
80
- });
81
- },
82
-
83
- /**
84
- * Read a file from the user's sandbox.
85
- */
86
- async readFile(userId, filePath) {
87
- return sandboxFetch('/api/file/read', userId, { filePath });
88
- },
89
-
90
- /**
91
- * Write a file to the user's sandbox.
92
- */
93
- async writeFile(userId, filePath, content) {
94
- return sandboxFetch('/api/file/write', userId, { filePath, content });
95
- },
96
-
97
- /**
98
- * Get file tree from the user's sandbox.
99
- */
100
- async getFileTree(userId, dirPath, depth = 3) {
101
- return sandboxFetch('/api/file/tree', userId, { dirPath, depth });
102
- },
103
-
104
- /**
105
- * Run a git command in the user's sandbox.
106
- */
107
- async gitOperation(userId, gitCommand, cwd) {
108
- return sandboxFetch('/api/git', userId, { gitCommand, cwd });
109
- },
110
-
111
- /**
112
- * Get the WebSocket URL for an interactive shell session.
113
- */
114
- getShellWsUrl(userId, sessionId) {
115
- const wsBase = SANDBOX_SERVICE_URL.replace(/^http/, 'ws');
116
- return `${wsBase}/shell?secret=${encodeURIComponent(SANDBOX_SERVICE_SECRET)}&userId=${userId}&sessionId=${sessionId || 'default'}`;
117
- },
118
- };
119
-
120
- export { sandboxClient, SANDBOX_SERVICE_URL };
@@ -1,154 +0,0 @@
1
- /**
2
- * Browser AI Service — Stagehand integration for AI-driven browser automation.
3
- * Lazy-loads @browserbasehq/stagehand (cloud-only dep, installed via nixpacks).
4
- * Follows the composio.js pattern: lazy SDK, per-session instances, availability check.
5
- */
6
-
7
- let Stagehand = null;
8
- let sdkReady = null;
9
-
10
- // Lazy-load Stagehand SDK (cloud-only dependency)
11
- sdkReady = (async () => {
12
- try {
13
- const mod = await import('@browserbasehq/stagehand');
14
- Stagehand = mod.Stagehand || mod.default;
15
- } catch {
16
- // SDK not installed — browser AI features unavailable (local installs)
17
- }
18
- })();
19
-
20
- // Per-session Stagehand instance cache
21
- const instances = new Map();
22
-
23
- /**
24
- * Check if the Stagehand SDK is available.
25
- */
26
- async function isAvailable() {
27
- await sdkReady;
28
- return !!Stagehand;
29
- }
30
-
31
- /**
32
- * Get or create a Stagehand instance for a session.
33
- */
34
- async function getOrCreate(sessionId, cdpUrl) {
35
- if (instances.has(sessionId)) return instances.get(sessionId);
36
-
37
- await sdkReady;
38
- if (!Stagehand) throw new Error('Browser AI not available — Stagehand SDK not installed');
39
-
40
- const stagehand = new Stagehand({
41
- env: 'LOCAL',
42
- enableCaching: true,
43
- localBrowserLaunchOptions: {
44
- cdpUrl,
45
- },
46
- });
47
- await stagehand.init();
48
-
49
- instances.set(sessionId, stagehand);
50
- return stagehand;
51
- }
52
-
53
- /**
54
- * Execute a single AI action on the page.
55
- * Chat mode: user says "click the login button" → this executes it.
56
- */
57
- async function act(sessionId, cdpUrl, instruction) {
58
- const stagehand = await getOrCreate(sessionId, cdpUrl);
59
- const page = stagehand.page;
60
- const result = await page.act({ action: instruction });
61
- return { success: true, result, url: page.url() };
62
- }
63
-
64
- /**
65
- * Extract structured data from the current page.
66
- */
67
- async function extract(sessionId, cdpUrl, instruction, schema) {
68
- const stagehand = await getOrCreate(sessionId, cdpUrl);
69
- const page = stagehand.page;
70
- const opts = { instruction };
71
- if (schema) opts.schema = schema;
72
- const result = await page.extract(opts);
73
- return { success: true, data: result };
74
- }
75
-
76
- /**
77
- * Observe the current page — returns available actions/elements.
78
- */
79
- async function observe(sessionId, cdpUrl, instruction) {
80
- const stagehand = await getOrCreate(sessionId, cdpUrl);
81
- const page = stagehand.page;
82
- const opts = instruction ? { instruction } : {};
83
- const result = await page.observe(opts);
84
- return { success: true, observations: result };
85
- }
86
-
87
- /**
88
- * Run an autonomous agent that pursues a goal across multiple steps.
89
- * Streams each step to the onStep callback (for SSE).
90
- */
91
- async function autonomousGoal(sessionId, cdpUrl, goal, maxSteps = 10, onStep) {
92
- const stagehand = await getOrCreate(sessionId, cdpUrl);
93
- const page = stagehand.page;
94
-
95
- for (let step = 0; step < maxSteps; step++) {
96
- try {
97
- // Observe current state
98
- const observations = await page.observe();
99
- onStep({ step, type: 'observe', data: observations, url: page.url(), timestamp: Date.now() });
100
-
101
- // Execute next action toward goal
102
- const actionResult = await page.act({ action: `Working towards this goal: ${goal}` });
103
- onStep({ step, type: 'act', data: actionResult, url: page.url(), timestamp: Date.now() });
104
-
105
- // Check if done
106
- const check = await page.extract({
107
- instruction: `Has this goal been achieved: "${goal}"? Answer with done=true or done=false and a brief reason.`,
108
- });
109
- onStep({ step, type: 'check', data: check, timestamp: Date.now() });
110
-
111
- if (check && check.done) break;
112
- } catch (err) {
113
- onStep({ step, type: 'error', message: err.message, timestamp: Date.now() });
114
- break;
115
- }
116
- }
117
- }
118
-
119
- /**
120
- * Get console errors from the browser via CDP.
121
- */
122
- async function getConsoleErrors(sessionId, cdpUrl) {
123
- const stagehand = await getOrCreate(sessionId, cdpUrl);
124
- const page = stagehand.page;
125
-
126
- // Collect console errors using page.evaluate
127
- const errors = await page.evaluate(() => {
128
- // Check for any errors the page might have stored
129
- return (window.__upfynErrors || []).slice(-50);
130
- }).catch(() => []);
131
-
132
- return errors;
133
- }
134
-
135
- /**
136
- * Release a Stagehand instance for a session.
137
- */
138
- async function release(sessionId) {
139
- const instance = instances.get(sessionId);
140
- if (instance) {
141
- try { await instance.close(); } catch { /* ignore */ }
142
- instances.delete(sessionId);
143
- }
144
- }
145
-
146
- export {
147
- isAvailable,
148
- act,
149
- extract,
150
- observe,
151
- autonomousGoal,
152
- getConsoleErrors,
153
- release,
154
- };