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
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Persistent Shell Singleton
3
+ * Ported from opencode-ai/opencode internal/llm/tools/shell/shell.go
4
+ *
5
+ * Maintains a single long-running shell process across commands.
6
+ * - Commands are queued and executed sequentially
7
+ * - Environment and cwd persist between commands
8
+ * - Child processes can be killed on abort
9
+ * - Shell respawns automatically if it dies
10
+ */
11
+ import { spawn, execSync } from 'child_process';
12
+ import os from 'os';
13
+ import path from 'path';
14
+ import { promises as fs } from 'fs';
15
+
16
+ let shellInstance = null;
17
+
18
+ /**
19
+ * Get or create the persistent shell singleton.
20
+ * @param {string} workingDir - Initial working directory
21
+ * @returns {PersistentShell}
22
+ */
23
+ export function getPersistentShell(workingDir) {
24
+ if (!shellInstance || !shellInstance.isAlive) {
25
+ shellInstance = new PersistentShell(workingDir || os.homedir());
26
+ }
27
+ return shellInstance;
28
+ }
29
+
30
+ class PersistentShell {
31
+ constructor(cwd) {
32
+ this.cwd = cwd;
33
+ this.isAlive = false;
34
+ this.commandQueue = [];
35
+ this.processing = false;
36
+ this.proc = null;
37
+ this.stdin = null;
38
+ this._start();
39
+ }
40
+
41
+ _start() {
42
+ const isWin = process.platform === 'win32';
43
+ const shellPath = isWin
44
+ ? process.env.COMSPEC || 'cmd.exe'
45
+ : process.env.SHELL || '/bin/bash';
46
+ const shellArgs = isWin ? ['/Q'] : ['-l'];
47
+
48
+ this.proc = spawn(shellPath, shellArgs, {
49
+ cwd: this.cwd,
50
+ env: { ...process.env, GIT_EDITOR: 'true' },
51
+ stdio: ['pipe', 'pipe', 'pipe'],
52
+ shell: false,
53
+ });
54
+
55
+ this.stdin = this.proc.stdin;
56
+ this.isAlive = true;
57
+
58
+ this.proc.on('close', () => {
59
+ this.isAlive = false;
60
+ // Reject any queued commands
61
+ for (const queued of this.commandQueue) {
62
+ queued.reject(new Error('Shell process died'));
63
+ }
64
+ this.commandQueue = [];
65
+ });
66
+
67
+ this.proc.on('error', () => {
68
+ this.isAlive = false;
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Execute a command in the persistent shell.
74
+ * Queued and executed sequentially (opencode pattern: commandQueue channel).
75
+ *
76
+ * @param {string} command - Shell command to execute
77
+ * @param {object} opts - { timeoutMs, abortSignal }
78
+ * @returns {Promise<{ stdout, stderr, exitCode, interrupted, cwd }>}
79
+ */
80
+ exec(command, opts = {}) {
81
+ return new Promise((resolve, reject) => {
82
+ this.commandQueue.push({ command, opts, resolve, reject });
83
+ this._processNext();
84
+ });
85
+ }
86
+
87
+ async _processNext() {
88
+ if (this.processing || this.commandQueue.length === 0) return;
89
+ this.processing = true;
90
+
91
+ const { command, opts, resolve, reject } = this.commandQueue.shift();
92
+
93
+ try {
94
+ const result = await this._execCommand(command, opts);
95
+ resolve(result);
96
+ } catch (err) {
97
+ reject(err);
98
+ } finally {
99
+ this.processing = false;
100
+ // Process next in queue
101
+ if (this.commandQueue.length > 0) {
102
+ this._processNext();
103
+ }
104
+ }
105
+ }
106
+
107
+ async _execCommand(command, { timeoutMs = 60000, abortSignal } = {}) {
108
+ if (!this.isAlive) {
109
+ throw new Error('Shell is not alive');
110
+ }
111
+
112
+ const isWin = process.platform === 'win32';
113
+ const tmpDir = os.tmpdir();
114
+ const ts = Date.now() + '-' + Math.random().toString(36).slice(2, 8);
115
+ const stdoutFile = path.join(tmpDir, `uc-stdout-${ts}`);
116
+ const stderrFile = path.join(tmpDir, `uc-stderr-${ts}`);
117
+ const statusFile = path.join(tmpDir, `uc-status-${ts}`);
118
+ const cwdFile = path.join(tmpDir, `uc-cwd-${ts}`);
119
+
120
+ try {
121
+ // Build the shell command that redirects output to temp files
122
+ // Exact same pattern as opencode's shell.go execCommand
123
+ let fullCommand;
124
+ if (isWin) {
125
+ // Windows cmd.exe variant
126
+ fullCommand = [
127
+ `${command} > "${stdoutFile}" 2> "${stderrFile}"`,
128
+ `echo %ERRORLEVEL% > "${statusFile}"`,
129
+ `cd > "${cwdFile}"`,
130
+ ].join(' & ');
131
+ } else {
132
+ // Unix bash variant (identical to opencode)
133
+ const escaped = command.replace(/'/g, "'\\''");
134
+ fullCommand = [
135
+ `eval '${escaped}' < /dev/null > '${stdoutFile}' 2> '${stderrFile}'`,
136
+ `EXEC_EXIT_CODE=$?`,
137
+ `pwd > '${cwdFile}'`,
138
+ `echo $EXEC_EXIT_CODE > '${statusFile}'`,
139
+ ].join('\n');
140
+ }
141
+
142
+ this.stdin.write(fullCommand + '\n');
143
+
144
+ // Poll for status file (same polling pattern as opencode)
145
+ let interrupted = false;
146
+ const startTime = Date.now();
147
+
148
+ await new Promise((done, fail) => {
149
+ const poll = setInterval(async () => {
150
+ // Check abort
151
+ if (abortSignal?.aborted) {
152
+ this.killChildren();
153
+ interrupted = true;
154
+ clearInterval(poll);
155
+ done();
156
+ return;
157
+ }
158
+
159
+ // Check timeout
160
+ if (timeoutMs > 0 && Date.now() - startTime > timeoutMs) {
161
+ this.killChildren();
162
+ interrupted = true;
163
+ clearInterval(poll);
164
+ done();
165
+ return;
166
+ }
167
+
168
+ // Check if status file exists and has content
169
+ try {
170
+ const stat = await fs.stat(statusFile);
171
+ if (stat.size > 0) {
172
+ clearInterval(poll);
173
+ done();
174
+ }
175
+ } catch {
176
+ // File doesn't exist yet
177
+ }
178
+ }, 50);
179
+ });
180
+
181
+ // Read results from temp files
182
+ const stdout = await this._readFileOrEmpty(stdoutFile);
183
+ const stderr = await this._readFileOrEmpty(stderrFile);
184
+ const exitCodeStr = await this._readFileOrEmpty(statusFile);
185
+ const newCwd = await this._readFileOrEmpty(cwdFile);
186
+
187
+ let exitCode = 0;
188
+ if (exitCodeStr.trim()) {
189
+ exitCode = parseInt(exitCodeStr.trim(), 10) || 0;
190
+ } else if (interrupted) {
191
+ exitCode = 143;
192
+ }
193
+
194
+ if (newCwd.trim()) {
195
+ this.cwd = newCwd.trim();
196
+ }
197
+
198
+ return {
199
+ stdout: stdout,
200
+ stderr: interrupted ? stderr + '\nCommand execution timed out or was interrupted' : stderr,
201
+ exitCode,
202
+ interrupted,
203
+ cwd: this.cwd,
204
+ };
205
+ } finally {
206
+ // Cleanup temp files
207
+ await Promise.all([
208
+ fs.unlink(stdoutFile).catch(() => {}),
209
+ fs.unlink(stderrFile).catch(() => {}),
210
+ fs.unlink(statusFile).catch(() => {}),
211
+ fs.unlink(cwdFile).catch(() => {}),
212
+ ]);
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Kill child processes of the shell (opencode pattern: killChildren).
218
+ * On Unix: pgrep -P <pid> → SIGTERM each child.
219
+ * On Windows: taskkill /PID /T.
220
+ */
221
+ killChildren() {
222
+ if (!this.proc?.pid) return;
223
+ try {
224
+ if (process.platform === 'win32') {
225
+ execSync(`taskkill /PID ${this.proc.pid} /T /F`, { stdio: 'ignore', timeout: 5000 });
226
+ // Respawn since taskkill kills the parent too on Windows
227
+ this._start();
228
+ } else {
229
+ const output = execSync(`pgrep -P ${this.proc.pid}`, { encoding: 'utf8', timeout: 5000 });
230
+ for (const line of output.split('\n')) {
231
+ const pid = parseInt(line.trim(), 10);
232
+ if (pid > 0) {
233
+ try { process.kill(pid, 'SIGTERM'); } catch { /* already dead */ }
234
+ }
235
+ }
236
+ }
237
+ } catch {
238
+ // pgrep/taskkill failed — no children to kill
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Close the persistent shell.
244
+ */
245
+ close() {
246
+ if (!this.isAlive) return;
247
+ try {
248
+ this.stdin.write('exit\n');
249
+ this.proc.kill('SIGTERM');
250
+ } catch { /* ignore */ }
251
+ this.isAlive = false;
252
+ }
253
+
254
+ async _readFileOrEmpty(filePath) {
255
+ try {
256
+ return await fs.readFile(filePath, 'utf8');
257
+ } catch {
258
+ return '';
259
+ }
260
+ }
261
+ }
package/src/server.js ADDED
@@ -0,0 +1,54 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { dirname, join } from 'path';
3
+ import { existsSync } from 'fs';
4
+ import express from 'express';
5
+ import { createProxyMiddleware } from 'http-proxy-middleware';
6
+ import open from 'open';
7
+ import chalk from 'chalk';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const DIST_DIR = join(__dirname, '..', 'dist');
11
+
12
+ export async function startServer(port, serverUrl, token) {
13
+ if (!existsSync(DIST_DIR)) {
14
+ console.log(chalk.red('\n Error: No built frontend found at dist/.'));
15
+ console.log(chalk.dim(' Run the build-frontend script first, or use the default mode (no --local flag).\n'));
16
+ process.exit(1);
17
+ }
18
+
19
+ const app = express();
20
+
21
+ // Proxy API calls to the remote Vercel backend
22
+ app.use('/api', createProxyMiddleware({
23
+ target: serverUrl,
24
+ changeOrigin: true,
25
+ headers: {
26
+ Authorization: `Bearer ${token}`,
27
+ },
28
+ }));
29
+
30
+ // Serve the built React frontend
31
+ app.use(express.static(DIST_DIR));
32
+
33
+ // SPA fallback — serve index.html for all non-API, non-static routes
34
+ app.get('*', (req, res) => {
35
+ res.sendFile(join(DIST_DIR, 'index.html'));
36
+ });
37
+
38
+ return new Promise((resolve) => {
39
+ const server = app.listen(port, async () => {
40
+ const url = `http://localhost:${port}?token=${encodeURIComponent(token)}`;
41
+ console.log(chalk.green(`\n Local server running at ${chalk.bold(`http://localhost:${port}`)}`));
42
+ console.log(chalk.dim(' API proxy active. Press Ctrl+C to stop.\n'));
43
+ await open(url);
44
+ resolve(server);
45
+ });
46
+
47
+ const shutdown = () => {
48
+ console.log(chalk.dim('\n Shutting down...'));
49
+ server.close(() => process.exit(0));
50
+ };
51
+ process.on('SIGINT', shutdown);
52
+ process.on('SIGTERM', shutdown);
53
+ });
54
+ }