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,419 +0,0 @@
1
- /**
2
- * Browser Routes — REST API for Steel browser sessions + Stagehand AI.
3
- * Cloud-only (browser service runs on Railway).
4
- */
5
-
6
- import { Router } from 'express';
7
- import { browserClient } from '../browser.js';
8
-
9
- const router = Router();
10
-
11
- // ── SSRF protection ─────────────────────────────────────────────────────────
12
-
13
- function isUrlSafe(url) {
14
- try {
15
- const parsed = new URL(url);
16
- if (!['http:', 'https:'].includes(parsed.protocol)) return false;
17
- // Strip brackets from IPv6 literals
18
- const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
19
- // Block localhost and loopback (IPv4 + IPv6)
20
- if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0') return false;
21
- if (hostname === '::1' || hostname === '0000:0000:0000:0000:0000:0000:0000:0001') return false;
22
- if (hostname.startsWith('::ffff:127.') || hostname.startsWith('::ffff:0.')) return false;
23
- // Block metadata endpoints (IPv4 + IPv6-mapped)
24
- if (hostname === '169.254.169.254' || hostname === '::ffff:a9fe:a9fe') return false;
25
- // Block RFC1918 private ranges
26
- if (hostname.startsWith('10.') || hostname.startsWith('192.168.')) return false;
27
- if (/^172\.(1[6-9]|2\d|3[01])\./.test(hostname)) return false;
28
- // Block IPv6 private ranges (fc00::/7, fe80::/10)
29
- if (/^f[cd][0-9a-f]{2}:/.test(hostname)) return false; // fc00::/7 unique local
30
- if (/^fe[89ab][0-9a-f]:/.test(hostname)) return false; // fe80::/10 link-local
31
- return true;
32
- } catch {
33
- return false;
34
- }
35
- }
36
-
37
- // Allow sandbox-internal URLs (these are safe — same Railway network)
38
- function isUrlSafeOrSandbox(url) {
39
- if (isUrlSafe(url)) return true;
40
- // Allow sandbox service URLs — compare origins, not string prefix
41
- const sandboxUrl = process.env.SANDBOX_SERVICE_URL || '';
42
- if (sandboxUrl) {
43
- try {
44
- const parsedUrl = new URL(url);
45
- const parsedSandbox = new URL(sandboxUrl);
46
- if (parsedUrl.origin === parsedSandbox.origin) return true;
47
- } catch { /* invalid URL */ }
48
- }
49
- return false;
50
- }
51
-
52
- // ── Lazy imports for cloud-only deps ─────────────────────────────────────────
53
-
54
- let browserAI = null;
55
- const loadBrowserAI = async () => {
56
- if (browserAI) return browserAI;
57
- try {
58
- browserAI = await import('../services/browser-ai.js');
59
- return browserAI;
60
- } catch {
61
- return null;
62
- }
63
- };
64
-
65
- let browserSessionDb = null;
66
- const loadDb = async () => {
67
- if (browserSessionDb) return browserSessionDb;
68
- try {
69
- const mod = await import('../database/db.js');
70
- browserSessionDb = mod.browserSessionDb;
71
- return browserSessionDb;
72
- } catch {
73
- return null;
74
- }
75
- };
76
-
77
- // ── Status ──────────────────────────────────────────────────────────────────
78
-
79
- router.get('/status', async (req, res) => {
80
- const available = await browserClient.isAvailable();
81
- const ai = await loadBrowserAI();
82
- const aiAvailable = ai ? await ai.isAvailable() : false;
83
-
84
- res.json({
85
- available,
86
- aiAvailable,
87
- serviceUrl: available ? '(connected)' : '(not reachable)',
88
- });
89
- });
90
-
91
- // ── Session Management ──────────────────────────────────────────────────────
92
-
93
- // POST /api/browser/sessions — create a new browser session
94
- router.post('/sessions', async (req, res) => {
95
- const userId = req.user.id;
96
- const db = await loadDb();
97
-
98
- try {
99
- // Check for existing active session
100
- if (db) {
101
- const existing = await db.getActive(userId);
102
- if (existing) {
103
- // Return existing session instead of creating new one
104
- return res.json({
105
- sessionId: existing.steel_session_id,
106
- viewerUrl: existing.viewer_url,
107
- cdpUrl: existing.cdp_url,
108
- status: 'active',
109
- reused: true,
110
- });
111
- }
112
- }
113
-
114
- const { blockAds, dimensions, timeout } = req.body || {};
115
- const session = await browserClient.createSession(userId, {
116
- blockAds,
117
- dimensions,
118
- timeout,
119
- });
120
-
121
- const viewerUrl = browserClient.getSessionViewerUrl(session.id);
122
- const cdpUrl = browserClient.getCdpWsUrl(session.id);
123
-
124
- // Save to DB
125
- if (db) {
126
- await db.create(userId, session.id, viewerUrl, cdpUrl);
127
- }
128
-
129
- res.json({
130
- sessionId: session.id,
131
- viewerUrl,
132
- cdpUrl,
133
- status: 'active',
134
- });
135
- } catch {
136
- res.status(500).json({ error: 'Failed to create browser session' });
137
- }
138
- });
139
-
140
- // GET /api/browser/sessions — list user's sessions
141
- router.get('/sessions', async (req, res) => {
142
- const db = await loadDb();
143
- if (!db) return res.json({ sessions: [] });
144
-
145
- try {
146
- const sessions = await db.listByUser(req.user.id);
147
- res.json({ sessions });
148
- } catch {
149
- res.status(500).json({ error: 'Failed to list sessions' });
150
- }
151
- });
152
-
153
- // GET /api/browser/sessions/:id — get session details
154
- router.get('/sessions/:id', async (req, res) => {
155
- const db = await loadDb();
156
- if (!db) return res.status(404).json({ error: 'Session not found' });
157
-
158
- try {
159
- const session = await db.getBySessionId(req.user.id, req.params.id);
160
- if (!session) return res.status(404).json({ error: 'Session not found' });
161
-
162
- await db.updateAccess(req.user.id, req.params.id);
163
-
164
- res.json({
165
- sessionId: session.steel_session_id,
166
- viewerUrl: session.viewer_url,
167
- cdpUrl: session.cdp_url,
168
- status: session.status,
169
- createdAt: session.created_at,
170
- lastAccessed: session.last_accessed,
171
- });
172
- } catch {
173
- res.status(500).json({ error: 'Failed to get session' });
174
- }
175
- });
176
-
177
- // DELETE /api/browser/sessions/:id — release session
178
- router.delete('/sessions/:id', async (req, res) => {
179
- const userId = req.user.id;
180
- const sessionId = req.params.id;
181
-
182
- try {
183
- // Verify ownership first
184
- const db = await loadDb();
185
- if (db) {
186
- const session = await db.getBySessionId(userId, sessionId);
187
- if (!session) return res.status(404).json({ error: 'Session not found' });
188
- }
189
-
190
- // Release Stagehand instance
191
- const ai = await loadBrowserAI();
192
- if (ai) await ai.release(sessionId);
193
-
194
- // Release Steel session
195
- await browserClient.releaseSession(userId, sessionId).catch(() => {});
196
-
197
- // Deactivate in DB (user-scoped)
198
- if (db) await db.deactivate(userId, sessionId);
199
-
200
- res.json({ success: true });
201
- } catch {
202
- res.status(500).json({ error: 'Failed to close session' });
203
- }
204
- });
205
-
206
- // ── Navigation ──────────────────────────────────────────────────────────────
207
-
208
- // POST /api/browser/sessions/:id/navigate
209
- router.post('/sessions/:id/navigate', async (req, res) => {
210
- const { url } = req.body;
211
- if (!url || !isUrlSafeOrSandbox(url)) {
212
- return res.status(400).json({ error: 'Invalid or blocked URL' });
213
- }
214
-
215
- try {
216
- const db = await loadDb();
217
- const session = db ? await db.getBySessionId(req.user.id, req.params.id) : null;
218
- if (db && !session) return res.status(404).json({ error: 'Session not found' });
219
-
220
- if (db) await db.updateAccess(req.user.id, req.params.id);
221
-
222
- // Use Stagehand/Playwright to navigate
223
- const ai = await loadBrowserAI();
224
- if (ai && await ai.isAvailable() && session?.cdp_url) {
225
- await ai.act(req.params.id, session.cdp_url, `Navigate to ${url}`);
226
- return res.json({ success: true, url });
227
- }
228
-
229
- res.json({ success: true, url, note: 'Navigation sent — viewer will update' });
230
- } catch {
231
- res.status(500).json({ error: 'Navigation failed' });
232
- }
233
- });
234
-
235
- // POST /api/browser/sessions/:id/sandbox-preview — open sandbox dev server
236
- router.post('/sessions/:id/sandbox-preview', async (req, res) => {
237
- const portNum = parseInt(req.body?.port, 10);
238
- if (!portNum || portNum < 1 || portNum > 65535) {
239
- return res.status(400).json({ error: 'Invalid port number' });
240
- }
241
-
242
- try {
243
- const db = await loadDb();
244
- const session = db ? await db.getBySessionId(req.user.id, req.params.id) : null;
245
- if (db && !session) return res.status(404).json({ error: 'Session not found' });
246
-
247
- const sandboxUrl = browserClient.getSandboxPreviewUrl(req.user.id, portNum);
248
-
249
- const ai = await loadBrowserAI();
250
- if (ai && await ai.isAvailable() && session?.cdp_url) {
251
- await ai.act(req.params.id, session.cdp_url, `Navigate to ${sandboxUrl}`);
252
- }
253
-
254
- if (db) await db.updateAccess(req.user.id, req.params.id);
255
-
256
- res.json({ success: true, url: sandboxUrl, port: portNum });
257
- } catch {
258
- res.status(500).json({ error: 'Failed to open sandbox preview' });
259
- }
260
- });
261
-
262
- // POST /api/browser/sessions/:id/screenshot
263
- router.post('/sessions/:id/screenshot', async (req, res) => {
264
- try {
265
- // Verify ownership
266
- const db = await loadDb();
267
- if (db) {
268
- const session = await db.getBySessionId(req.user.id, req.params.id);
269
- if (!session) return res.status(404).json({ error: 'Session not found' });
270
- }
271
- const result = await browserClient.screenshot(req.user.id, req.params.id);
272
- res.json(result);
273
- } catch {
274
- res.status(500).json({ error: 'Screenshot failed' });
275
- }
276
- });
277
-
278
- // ── AI Actions ──────────────────────────────────────────────────────────────
279
-
280
- // POST /api/browser/sessions/:id/ai/act — single AI action (chat mode)
281
- router.post('/sessions/:id/ai/act', async (req, res) => {
282
- const { instruction } = req.body;
283
- if (!instruction) return res.status(400).json({ error: 'instruction is required' });
284
-
285
- const ai = await loadBrowserAI();
286
- if (!ai || !(await ai.isAvailable())) {
287
- return res.status(503).json({ error: 'Browser AI not available' });
288
- }
289
-
290
- try {
291
- const db = await loadDb();
292
- const session = db ? await db.getBySessionId(req.user.id, req.params.id) : null;
293
- if (!session?.cdp_url) return res.status(404).json({ error: 'Session not found or missing CDP URL' });
294
-
295
- if (db) await db.updateAccess(req.user.id, req.params.id);
296
-
297
- const result = await ai.act(req.params.id, session.cdp_url, instruction);
298
- res.json(result);
299
- } catch {
300
- res.status(500).json({ error: 'AI action failed' });
301
- }
302
- });
303
-
304
- // POST /api/browser/sessions/:id/ai/extract
305
- router.post('/sessions/:id/ai/extract', async (req, res) => {
306
- const { instruction, schema } = req.body;
307
- if (!instruction) return res.status(400).json({ error: 'instruction is required' });
308
-
309
- const ai = await loadBrowserAI();
310
- if (!ai || !(await ai.isAvailable())) {
311
- return res.status(503).json({ error: 'Browser AI not available' });
312
- }
313
-
314
- try {
315
- const db = await loadDb();
316
- const session = db ? await db.getBySessionId(req.user.id, req.params.id) : null;
317
- if (!session?.cdp_url) return res.status(404).json({ error: 'Session not found' });
318
-
319
- if (db) await db.updateAccess(req.user.id, req.params.id);
320
-
321
- const result = await ai.extract(req.params.id, session.cdp_url, instruction, schema);
322
- res.json(result);
323
- } catch {
324
- res.status(500).json({ error: 'AI extraction failed' });
325
- }
326
- });
327
-
328
- // POST /api/browser/sessions/:id/ai/observe
329
- router.post('/sessions/:id/ai/observe', async (req, res) => {
330
- const { instruction } = req.body;
331
-
332
- const ai = await loadBrowserAI();
333
- if (!ai || !(await ai.isAvailable())) {
334
- return res.status(503).json({ error: 'Browser AI not available' });
335
- }
336
-
337
- try {
338
- const db = await loadDb();
339
- const session = db ? await db.getBySessionId(req.user.id, req.params.id) : null;
340
- if (!session?.cdp_url) return res.status(404).json({ error: 'Session not found' });
341
-
342
- const result = await ai.observe(req.params.id, session.cdp_url, instruction);
343
- res.json(result);
344
- } catch {
345
- res.status(500).json({ error: 'AI observation failed' });
346
- }
347
- });
348
-
349
- // GET /api/browser/sessions/:id/ai/autonomous — autonomous agent with SSE streaming
350
- router.get('/sessions/:id/ai/autonomous', async (req, res) => {
351
- const { goal, maxSteps } = req.query;
352
- if (!goal) {
353
- res.status(400).json({ error: 'goal is required' });
354
- return;
355
- }
356
-
357
- const ai = await loadBrowserAI();
358
- if (!ai || !(await ai.isAvailable())) {
359
- res.status(503).json({ error: 'Browser AI not available' });
360
- return;
361
- }
362
-
363
- const db = await loadDb();
364
- const session = db ? await db.getBySessionId(req.user.id, req.params.id) : null;
365
- if (!session?.cdp_url) {
366
- res.status(404).json({ error: 'Session not found' });
367
- return;
368
- }
369
-
370
- // SSE setup
371
- res.setHeader('Content-Type', 'text/event-stream');
372
- res.setHeader('Cache-Control', 'no-cache');
373
- res.setHeader('Connection', 'keep-alive');
374
- res.flushHeaders();
375
-
376
- const sendEvent = (data) => {
377
- res.write(`data: ${JSON.stringify(data)}\n\n`);
378
- };
379
-
380
- try {
381
- if (db) await db.updateAccess(req.user.id, req.params.id);
382
-
383
- const steps = Math.min(Math.max(parseInt(maxSteps) || 10, 1), 25);
384
- await ai.autonomousGoal(
385
- req.params.id,
386
- session.cdp_url,
387
- goal.slice(0, 500), // Truncate goal to prevent prompt injection via excessively long input
388
- steps,
389
- (stepData) => sendEvent(stepData)
390
- );
391
-
392
- sendEvent({ type: 'done' });
393
- } catch {
394
- sendEvent({ type: 'error', message: 'Autonomous run failed' });
395
- }
396
-
397
- res.end();
398
- });
399
-
400
- // GET /api/browser/sessions/:id/console-errors — get browser console errors
401
- router.get('/sessions/:id/console-errors', async (req, res) => {
402
- const ai = await loadBrowserAI();
403
- if (!ai || !(await ai.isAvailable())) {
404
- return res.json({ errors: [] });
405
- }
406
-
407
- try {
408
- const db = await loadDb();
409
- const session = db ? await db.getBySessionId(req.user.id, req.params.id) : null;
410
- if (!session?.cdp_url) return res.json({ errors: [] });
411
-
412
- const errors = await ai.getConsoleErrors(req.params.id, session.cdp_url);
413
- res.json({ errors });
414
- } catch {
415
- res.json({ errors: [] });
416
- }
417
- });
418
-
419
- export default router;
@@ -1,53 +0,0 @@
1
- import express from 'express';
2
- import { canvasDb } from '../database/db.js';
3
-
4
- const router = express.Router();
5
-
6
- // GET /api/canvas/:projectName — load canvas for a project
7
- router.get('/:projectName', async (req, res) => {
8
- try {
9
- const userId = req.user.id;
10
- const { projectName } = req.params;
11
-
12
- const canvas = await canvasDb.load(userId, decodeURIComponent(projectName));
13
- if (!canvas) {
14
- return res.json({ elements: [], appState: {}, updatedAt: null });
15
- }
16
- res.json(canvas);
17
- } catch (err) {
18
- res.status(500).json({ error: 'Failed to load canvas' });
19
- }
20
- });
21
-
22
- // POST /api/canvas/:projectName — save canvas for a project
23
- router.post('/:projectName', async (req, res) => {
24
- try {
25
- const userId = req.user.id;
26
- const { projectName } = req.params;
27
- const { elements, appState } = req.body;
28
-
29
- if (!Array.isArray(elements)) {
30
- return res.status(400).json({ error: 'elements must be an array' });
31
- }
32
-
33
- await canvasDb.save(userId, decodeURIComponent(projectName), elements, appState || {});
34
- res.json({ ok: true });
35
- } catch (err) {
36
- res.status(500).json({ error: 'Failed to save canvas' });
37
- }
38
- });
39
-
40
- // DELETE /api/canvas/:projectName — delete canvas for a project
41
- router.delete('/:projectName', async (req, res) => {
42
- try {
43
- const userId = req.user.id;
44
- const { projectName } = req.params;
45
-
46
- const deleted = await canvasDb.delete(userId, decodeURIComponent(projectName));
47
- res.json({ ok: true, deleted });
48
- } catch (err) {
49
- res.status(500).json({ error: 'Failed to delete canvas' });
50
- }
51
- });
52
-
53
- export default router;