upfynai-code 3.0.4 → 3.1.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 (246) hide show
  1. package/README.md +66 -91
  2. package/bin/cli.js +191 -0
  3. package/{client/dist/assets/AppContent-CwrTP6TW.js → dist/client/assets/AppContent-BofJquUs.js} +4 -4
  4. package/{client/dist/assets/BrowserPanel-0TLEl-IC.js → dist/client/assets/BrowserPanel-CSvD4jOX.js} +2 -2
  5. package/dist/client/assets/CanvasFullScreen-onRfarpc.js +1 -0
  6. package/dist/client/assets/CanvasWorkspace-DvGKdL-k.js +259 -0
  7. package/dist/client/assets/DashboardPanel-DqAHbXDO.js +1 -0
  8. package/dist/client/assets/FileTree-BE0h-9M9.js +1 -0
  9. package/{client/dist/assets/GitPanel-C_xFM-N2.js → dist/client/assets/GitPanel-DdeJ0bp5.js} +2 -2
  10. package/{client/dist/assets/LoginModal-CImJHRjX.js → dist/client/assets/LoginModal-BP0pCTrH.js} +3 -3
  11. package/dist/client/assets/MermaidBlock-D0rfEhrT.js +2 -0
  12. package/dist/client/assets/Onboarding-B2zQy-_6.js +1 -0
  13. package/dist/client/assets/SetupForm-Be7-WBe-.js +1 -0
  14. package/dist/client/assets/WorkflowsPanel-CusLbVJ6.js +1 -0
  15. package/{client/dist/assets/index-HaY-3pK1.js → dist/client/assets/index-BQy15irW.js} +24 -24
  16. package/dist/client/assets/index-CS0fDqEC.js +1 -0
  17. package/dist/client/assets/index-DYLSCCCp.css +1 -0
  18. package/dist/client/assets/vendor-canvas-QWTduIvM.js +23 -0
  19. package/{client/dist/assets/vendor-icons-GyYE35HP.js → dist/client/assets/vendor-icons-kix3Gb31.js} +1 -1
  20. package/{client/dist/assets/vendor-mermaid-DucWyDEe.js → dist/client/assets/vendor-mermaid-CS3J4_Bz.js} +329 -326
  21. package/dist/client/favicon.png +0 -0
  22. package/dist/client/favicon.svg +15 -0
  23. package/{client/dist → dist/client}/index.html +3 -3
  24. package/{client/dist → dist/client}/manifest.json +12 -12
  25. package/package.json +55 -104
  26. package/scripts/postinstall.js +9 -0
  27. package/scripts/prepublish.js +77 -0
  28. package/src/animation.js +228 -0
  29. package/src/auth.js +142 -0
  30. package/src/config.js +40 -0
  31. package/src/connect.js +416 -0
  32. package/src/launch.js +81 -0
  33. package/src/mcp.js +57 -0
  34. package/src/permissions.js +140 -0
  35. package/src/persistent-shell.js +261 -0
  36. package/src/server.js +54 -0
  37. package/client/dist/assets/CanvasFullScreen-D1GWQsGL.js +0 -1
  38. package/client/dist/assets/CanvasWorkspace-D7ORj358.js +0 -163
  39. package/client/dist/assets/DashboardPanel-BV7ybUDe.js +0 -1
  40. package/client/dist/assets/FileTree-5qfhBqdE.js +0 -1
  41. package/client/dist/assets/MermaidBlock-BFM21cwe.js +0 -2
  42. package/client/dist/assets/Onboarding-B3cteLu2.js +0 -1
  43. package/client/dist/assets/SetupForm-P6dsYgHO.js +0 -1
  44. package/client/dist/assets/WorkflowsPanel-CBoN80kc.js +0 -1
  45. package/client/dist/assets/index-46kkVu2i.css +0 -1
  46. package/client/dist/assets/vendor-canvas-DvHJ_Pn2.js +0 -49
  47. package/client/dist/favicon.png +0 -0
  48. package/client/dist/favicon.svg +0 -5
  49. package/commands/upfynai-connect.md +0 -59
  50. package/commands/upfynai-disconnect.md +0 -31
  51. package/commands/upfynai-doctor.md +0 -99
  52. package/commands/upfynai-export.md +0 -49
  53. package/commands/upfynai-local.md +0 -82
  54. package/commands/upfynai-status.md +0 -75
  55. package/commands/upfynai-stop.md +0 -49
  56. package/commands/upfynai-uninstall.md +0 -58
  57. package/commands/upfynai.md +0 -69
  58. package/scripts/build-client.js +0 -17
  59. package/scripts/fix-node-pty.js +0 -67
  60. package/scripts/install-commands.js +0 -78
  61. package/server/agent-loop.js +0 -242
  62. package/server/auto-compact.js +0 -99
  63. package/server/browser.js +0 -131
  64. package/server/claude-sdk.js +0 -797
  65. package/server/cli-ui.js +0 -798
  66. package/server/cli.js +0 -751
  67. package/server/constants/config.js +0 -31
  68. package/server/cursor-cli.js +0 -270
  69. package/server/database/auth.db +0 -0
  70. package/server/database/db.js +0 -1547
  71. package/server/database/init.sql +0 -70
  72. package/server/index.js +0 -3813
  73. package/server/load-env.js +0 -26
  74. package/server/mcp-server.js +0 -621
  75. package/server/middleware/auth.js +0 -184
  76. package/server/middleware/relayHelpers.js +0 -44
  77. package/server/middleware/sandboxRouter.js +0 -174
  78. package/server/openai-codex.js +0 -403
  79. package/server/openrouter.js +0 -137
  80. package/server/projects.js +0 -1807
  81. package/server/provider-factory.js +0 -174
  82. package/server/relay-client.js +0 -390
  83. package/server/routes/agent.js +0 -1234
  84. package/server/routes/auth.js +0 -559
  85. package/server/routes/browser.js +0 -419
  86. package/server/routes/canvas.js +0 -53
  87. package/server/routes/cli-auth.js +0 -263
  88. package/server/routes/codex.js +0 -396
  89. package/server/routes/commands.js +0 -707
  90. package/server/routes/composio.js +0 -176
  91. package/server/routes/cursor.js +0 -770
  92. package/server/routes/dashboard.js +0 -295
  93. package/server/routes/git.js +0 -1208
  94. package/server/routes/keys.js +0 -34
  95. package/server/routes/mcp-utils.js +0 -48
  96. package/server/routes/mcp.js +0 -661
  97. package/server/routes/payments.js +0 -227
  98. package/server/routes/projects.js +0 -754
  99. package/server/routes/sessions.js +0 -146
  100. package/server/routes/settings.js +0 -261
  101. package/server/routes/taskmaster.js +0 -1928
  102. package/server/routes/user.js +0 -106
  103. package/server/routes/vapi-chat.js +0 -624
  104. package/server/routes/voice.js +0 -235
  105. package/server/routes/webhooks.js +0 -166
  106. package/server/routes/workflows.js +0 -312
  107. package/server/sandbox.js +0 -120
  108. package/server/services/browser-ai.js +0 -154
  109. package/server/services/composio.js +0 -204
  110. package/server/services/sessionRegistry.js +0 -139
  111. package/server/services/whisperService.js +0 -84
  112. package/server/services/workflowScheduler.js +0 -211
  113. package/server/tests/relay-flow.test.js +0 -570
  114. package/server/tests/sessions.test.js +0 -259
  115. package/server/utils/commandParser.js +0 -303
  116. package/server/utils/email.js +0 -66
  117. package/server/utils/gitConfig.js +0 -24
  118. package/server/utils/mcp-detector.js +0 -198
  119. package/server/utils/taskmaster-websocket.js +0 -129
  120. package/shared/integrationCatalog.d.ts +0 -12
  121. package/shared/integrationCatalog.js +0 -172
  122. package/shared/modelConstants.js +0 -96
  123. /package/{shared → dist}/agents/claude.js +0 -0
  124. /package/{shared → dist}/agents/codex.js +0 -0
  125. /package/{shared → dist}/agents/cursor.js +0 -0
  126. /package/{shared → dist}/agents/detect.js +0 -0
  127. /package/{shared → dist}/agents/exec.js +0 -0
  128. /package/{shared → dist}/agents/files.js +0 -0
  129. /package/{shared → dist}/agents/git.js +0 -0
  130. /package/{shared → dist}/agents/gitagent.js +0 -0
  131. /package/{shared → dist}/agents/index.js +0 -0
  132. /package/{shared → dist}/agents/shell.js +0 -0
  133. /package/{shared → dist}/agents/utils.js +0 -0
  134. /package/{client/dist → dist/client}/api-docs.html +0 -0
  135. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  136. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  137. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  138. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  139. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  140. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  141. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  142. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  143. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  144. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  145. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  146. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  147. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  148. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  149. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  150. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  151. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  152. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  153. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  154. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  155. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  156. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  157. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  158. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  159. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  160. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  161. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  162. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  163. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  164. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  165. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  166. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  167. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  168. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  169. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  170. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  171. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  172. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  173. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  174. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  175. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  176. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  177. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  178. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  179. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  180. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  181. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  182. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  183. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  184. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  185. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  186. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  187. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  188. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  189. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  190. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  191. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  192. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  193. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  194. /package/{client/dist → dist/client}/assets/MarkdownPreview-CESjI261.js +0 -0
  195. /package/{client/dist → dist/client}/assets/PreviewPanel-CqCa92Tf.js +0 -0
  196. /package/{client/dist → dist/client}/assets/pdf-CE_K4jFx.js +0 -0
  197. /package/{client/dist → dist/client}/assets/vendor-canvas-BZV40eAE.css +0 -0
  198. /package/{client/dist → dist/client}/assets/vendor-codemirror-D2ALgpaX.js +0 -0
  199. /package/{client/dist → dist/client}/assets/vendor-diff-DNQpbhrT.js +0 -0
  200. /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
  201. /package/{client/dist → dist/client}/assets/vendor-markdown-CimbIo6Y.js +0 -0
  202. /package/{client/dist → dist/client}/assets/vendor-react-96lCPsRK.js +0 -0
  203. /package/{client/dist → dist/client}/assets/vendor-syntax-LS_Nt30I.js +0 -0
  204. /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
  205. /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +0 -0
  206. /package/{client/dist → dist/client}/clear-cache.html +0 -0
  207. /package/{client/dist → dist/client}/convert-icons.md +0 -0
  208. /package/{client/dist → dist/client}/generate-icons.js +0 -0
  209. /package/{client/dist → dist/client}/icons/claude-ai-icon.svg +0 -0
  210. /package/{client/dist → dist/client}/icons/codex-white.svg +0 -0
  211. /package/{client/dist → dist/client}/icons/codex.svg +0 -0
  212. /package/{client/dist → dist/client}/icons/cursor-white.svg +0 -0
  213. /package/{client/dist → dist/client}/icons/cursor.svg +0 -0
  214. /package/{client/dist → dist/client}/icons/icon-128x128.png +0 -0
  215. /package/{client/dist → dist/client}/icons/icon-128x128.svg +0 -0
  216. /package/{client/dist → dist/client}/icons/icon-144x144.png +0 -0
  217. /package/{client/dist → dist/client}/icons/icon-144x144.svg +0 -0
  218. /package/{client/dist → dist/client}/icons/icon-152x152.png +0 -0
  219. /package/{client/dist → dist/client}/icons/icon-152x152.svg +0 -0
  220. /package/{client/dist → dist/client}/icons/icon-192x192.png +0 -0
  221. /package/{client/dist → dist/client}/icons/icon-192x192.svg +0 -0
  222. /package/{client/dist → dist/client}/icons/icon-384x384.png +0 -0
  223. /package/{client/dist → dist/client}/icons/icon-384x384.svg +0 -0
  224. /package/{client/dist → dist/client}/icons/icon-512x512.png +0 -0
  225. /package/{client/dist → dist/client}/icons/icon-512x512.svg +0 -0
  226. /package/{client/dist → dist/client}/icons/icon-72x72.png +0 -0
  227. /package/{client/dist → dist/client}/icons/icon-72x72.svg +0 -0
  228. /package/{client/dist → dist/client}/icons/icon-96x96.png +0 -0
  229. /package/{client/dist → dist/client}/icons/icon-96x96.svg +0 -0
  230. /package/{client/dist → dist/client}/icons/icon-template.svg +0 -0
  231. /package/{client/dist → dist/client}/logo-128.png +0 -0
  232. /package/{client/dist → dist/client}/logo-256.png +0 -0
  233. /package/{client/dist → dist/client}/logo-32.png +0 -0
  234. /package/{client/dist → dist/client}/logo-512.png +0 -0
  235. /package/{client/dist → dist/client}/logo-64.png +0 -0
  236. /package/{client/dist → dist/client}/logo.svg +0 -0
  237. /package/{client/dist → dist/client}/mcp-docs.html +0 -0
  238. /package/{client/dist → dist/client}/offline.html +0 -0
  239. /package/{client/dist → dist/client}/screenshots/cli-selection.png +0 -0
  240. /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
  241. /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
  242. /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
  243. /package/{client/dist → dist/client}/sw.js +0 -0
  244. /package/{shared → dist}/gitagent/index.js +0 -0
  245. /package/{shared → dist}/gitagent/parser.js +0 -0
  246. /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
- };