upfynai-code 2.9.1 → 2.9.3

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 (229) hide show
  1. package/README.md +91 -66
  2. package/client/dist/api-docs.html +838 -0
  3. package/client/dist/assets/AppContent-BXZDeSIC.js +545 -0
  4. package/client/dist/assets/CanvasFullScreen-mnpCnLZ9.js +1 -0
  5. package/client/dist/assets/CanvasWorkspace-4CqmjAVQ.js +163 -0
  6. package/client/dist/assets/DashboardPanel-zFIFlw56.js +1 -0
  7. package/client/dist/assets/FileTree-B0c_GaB3.js +1 -0
  8. package/client/dist/assets/GitPanel-DUP4zVU4.js +2 -0
  9. package/client/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  10. package/client/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  11. package/client/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  12. package/client/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  13. package/client/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  14. package/client/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  15. package/client/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  16. package/client/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  17. package/client/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  18. package/client/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  19. package/client/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  20. package/client/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  21. package/client/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  22. package/client/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  23. package/client/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  24. package/client/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  25. package/client/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  26. package/client/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  27. package/client/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  28. package/client/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  29. package/client/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  30. package/client/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  31. package/client/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  32. package/client/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  33. package/client/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  34. package/client/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  35. package/client/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  36. package/client/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  37. package/client/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  38. package/client/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  39. package/client/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  40. package/client/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  41. package/client/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  42. package/client/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  43. package/client/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  44. package/client/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  45. package/client/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  46. package/client/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  47. package/client/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  48. package/client/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  49. package/client/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  50. package/client/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  51. package/client/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  52. package/client/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  53. package/client/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  54. package/client/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  55. package/client/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  56. package/client/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  57. package/client/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  58. package/client/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  59. package/client/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  60. package/client/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  61. package/client/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  62. package/client/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  63. package/client/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  64. package/client/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  65. package/client/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  66. package/client/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  67. package/client/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  68. package/client/dist/assets/LoginModal-BRycfsyD.js +13 -0
  69. package/client/dist/assets/MarkdownPreview-DHmk3qzu.js +1 -0
  70. package/client/dist/assets/MermaidBlock-BuBc_G-F.js +2 -0
  71. package/client/dist/assets/Onboarding-BcnaZZ0o.js +1 -0
  72. package/client/dist/assets/PreviewPanel-CqCa92Tf.js +32 -0
  73. package/client/dist/assets/SetupForm-S0g6u5yT.js +1 -0
  74. package/client/dist/assets/WorkflowsPanel-CouH9JDO.js +1 -0
  75. package/client/dist/assets/index-BFuqS0tY.css +1 -0
  76. package/client/dist/assets/index-CNDcVl2g.js +68 -0
  77. package/client/dist/assets/pdf-CE_K4jFx.js +12 -0
  78. package/client/dist/assets/vendor-canvas-BZV40eAE.css +1 -0
  79. package/client/dist/assets/vendor-canvas-D39yWul6.js +49 -0
  80. package/client/dist/assets/vendor-codemirror-CbtmxxaB.js +35 -0
  81. package/client/dist/assets/vendor-diff-DNQpbhrT.js +69 -0
  82. package/client/dist/assets/vendor-i18n-DCFGyhQR.js +1 -0
  83. package/client/dist/assets/vendor-icons-BaD0x9SL.js +711 -0
  84. package/client/dist/assets/vendor-markdown-CimbIo6Y.js +296 -0
  85. package/client/dist/assets/vendor-mermaid-CH7SGc99.js +2556 -0
  86. package/client/dist/assets/vendor-react-96lCPsRK.js +67 -0
  87. package/client/dist/assets/vendor-syntax-DuHI9Ok6.js +16 -0
  88. package/client/dist/assets/vendor-xterm-CZq1hqo1.js +66 -0
  89. package/client/dist/assets/vendor-xterm-qxJ8_QYu.css +32 -0
  90. package/client/dist/clear-cache.html +85 -0
  91. package/client/dist/convert-icons.md +53 -0
  92. package/client/dist/favicon.png +0 -0
  93. package/client/dist/favicon.svg +5 -0
  94. package/client/dist/generate-icons.js +49 -0
  95. package/client/dist/icons/claude-ai-icon.svg +1 -0
  96. package/client/dist/icons/codex-white.svg +3 -0
  97. package/client/dist/icons/codex.svg +3 -0
  98. package/client/dist/icons/cursor-white.svg +12 -0
  99. package/client/dist/icons/cursor.svg +1 -0
  100. package/client/dist/icons/icon-128x128.png +0 -0
  101. package/client/dist/icons/icon-128x128.svg +5 -0
  102. package/client/dist/icons/icon-144x144.png +0 -0
  103. package/client/dist/icons/icon-144x144.svg +5 -0
  104. package/client/dist/icons/icon-152x152.png +0 -0
  105. package/client/dist/icons/icon-152x152.svg +5 -0
  106. package/client/dist/icons/icon-192x192.png +0 -0
  107. package/client/dist/icons/icon-192x192.svg +5 -0
  108. package/client/dist/icons/icon-384x384.png +0 -0
  109. package/client/dist/icons/icon-384x384.svg +5 -0
  110. package/client/dist/icons/icon-512x512.png +0 -0
  111. package/client/dist/icons/icon-512x512.svg +5 -0
  112. package/client/dist/icons/icon-72x72.png +0 -0
  113. package/client/dist/icons/icon-72x72.svg +5 -0
  114. package/client/dist/icons/icon-96x96.png +0 -0
  115. package/client/dist/icons/icon-96x96.svg +5 -0
  116. package/client/dist/icons/icon-template.svg +5 -0
  117. package/client/dist/index.html +119 -0
  118. package/client/dist/logo-128.png +0 -0
  119. package/client/dist/logo-256.png +0 -0
  120. package/client/dist/logo-32.png +0 -0
  121. package/client/dist/logo-512.png +0 -0
  122. package/client/dist/logo-64.png +0 -0
  123. package/client/dist/logo.svg +14 -0
  124. package/client/dist/manifest.json +61 -0
  125. package/client/dist/mcp-docs.html +108 -0
  126. package/client/dist/offline.html +84 -0
  127. package/client/dist/screenshots/cli-selection.png +0 -0
  128. package/client/dist/screenshots/desktop-main.png +0 -0
  129. package/client/dist/screenshots/mobile-chat.png +0 -0
  130. package/client/dist/screenshots/tools-modal.png +0 -0
  131. package/client/dist/sw.js +82 -0
  132. package/commands/upfynai-connect.md +59 -0
  133. package/commands/upfynai-disconnect.md +31 -0
  134. package/commands/upfynai-doctor.md +99 -0
  135. package/commands/upfynai-export.md +49 -0
  136. package/commands/upfynai-local.md +82 -0
  137. package/commands/upfynai-status.md +75 -0
  138. package/commands/upfynai-stop.md +49 -0
  139. package/commands/upfynai-uninstall.md +58 -0
  140. package/commands/upfynai.md +69 -0
  141. package/package.json +143 -82
  142. package/scripts/build-client.js +17 -0
  143. package/scripts/fix-node-pty.js +67 -0
  144. package/scripts/install-commands.js +78 -0
  145. package/server/agent-loop.js +242 -0
  146. package/server/auto-compact.js +99 -0
  147. package/server/claude-sdk.js +797 -0
  148. package/server/cli-ui.js +785 -0
  149. package/server/cli.js +596 -0
  150. package/server/constants/config.js +31 -0
  151. package/server/cursor-cli.js +270 -0
  152. package/server/database/auth.db +0 -0
  153. package/server/database/db.js +1391 -0
  154. package/server/database/init.sql +70 -0
  155. package/server/index.js +3799 -0
  156. package/server/load-env.js +26 -0
  157. package/server/mcp-server.js +621 -0
  158. package/server/middleware/auth.js +176 -0
  159. package/server/middleware/relayHelpers.js +44 -0
  160. package/server/middleware/sandboxRouter.js +174 -0
  161. package/server/openai-codex.js +403 -0
  162. package/server/openrouter.js +137 -0
  163. package/server/projects.js +1807 -0
  164. package/server/provider-factory.js +174 -0
  165. package/server/relay-client.js +379 -0
  166. package/server/routes/agent.js +1226 -0
  167. package/server/routes/auth.js +554 -0
  168. package/server/routes/canvas.js +53 -0
  169. package/server/routes/cli-auth.js +263 -0
  170. package/server/routes/codex.js +396 -0
  171. package/server/routes/commands.js +707 -0
  172. package/server/routes/composio.js +176 -0
  173. package/server/routes/cursor.js +770 -0
  174. package/server/routes/dashboard.js +295 -0
  175. package/server/routes/git.js +1208 -0
  176. package/server/routes/keys.js +34 -0
  177. package/server/routes/mcp-utils.js +48 -0
  178. package/server/routes/mcp.js +661 -0
  179. package/server/routes/payments.js +227 -0
  180. package/server/routes/projects.js +655 -0
  181. package/server/routes/sessions.js +146 -0
  182. package/server/routes/settings.js +261 -0
  183. package/server/routes/taskmaster.js +1928 -0
  184. package/server/routes/user.js +106 -0
  185. package/server/routes/vapi-chat.js +624 -0
  186. package/server/routes/voice.js +235 -0
  187. package/server/routes/webhooks.js +166 -0
  188. package/server/routes/workflows.js +312 -0
  189. package/server/sandbox.js +120 -0
  190. package/server/services/composio.js +204 -0
  191. package/server/services/sessionRegistry.js +139 -0
  192. package/server/services/whisperService.js +84 -0
  193. package/server/services/workflowScheduler.js +206 -0
  194. package/server/tests/relay-flow.test.js +570 -0
  195. package/server/tests/sessions.test.js +259 -0
  196. package/server/utils/commandParser.js +303 -0
  197. package/server/utils/email.js +61 -0
  198. package/server/utils/gitConfig.js +24 -0
  199. package/server/utils/mcp-detector.js +198 -0
  200. package/server/utils/taskmaster-websocket.js +129 -0
  201. package/shared/integrationCatalog.d.ts +12 -0
  202. package/shared/integrationCatalog.js +172 -0
  203. package/shared/modelConstants.js +96 -0
  204. package/bin/cli.js +0 -97
  205. package/dist/agents/claude.js +0 -229
  206. package/dist/agents/codex.js +0 -48
  207. package/dist/agents/cursor.js +0 -48
  208. package/dist/agents/detect.js +0 -51
  209. package/dist/agents/exec.js +0 -31
  210. package/dist/agents/files.js +0 -105
  211. package/dist/agents/git.js +0 -18
  212. package/dist/agents/gitagent.js +0 -67
  213. package/dist/agents/index.js +0 -88
  214. package/dist/agents/shell.js +0 -38
  215. package/dist/agents/utils.js +0 -136
  216. package/scripts/postinstall.js +0 -9
  217. package/scripts/prepublish.js +0 -58
  218. package/src/animation.js +0 -228
  219. package/src/auth.js +0 -122
  220. package/src/config.js +0 -40
  221. package/src/connect.js +0 -416
  222. package/src/launch.js +0 -78
  223. package/src/mcp.js +0 -57
  224. package/src/permissions.js +0 -140
  225. package/src/persistent-shell.js +0 -261
  226. package/src/server.js +0 -54
  227. /package/{dist → shared}/gitagent/index.js +0 -0
  228. /package/{dist → shared}/gitagent/parser.js +0 -0
  229. /package/{dist → shared}/gitagent/prompt-builder.js +0 -0
@@ -1,140 +0,0 @@
1
- /**
2
- * Permission-Gated Tool Execution
3
- * Ported from opencode-ai/opencode internal/permission/permission.go
4
- *
5
- * Splits relay actions into safe (auto-approve) and dangerous (require browser approval).
6
- * Permission requests are sent to the server → browser, and we wait for the response.
7
- */
8
-
9
- // Safe actions — auto-approved, read-only (opencode pattern: tools without permission flag)
10
- const SAFE_ACTIONS = new Set([
11
- 'file-read',
12
- 'file-tree',
13
- 'browse-dirs',
14
- 'validate-path',
15
- 'detect-agents',
16
- ]);
17
-
18
- // Dangerous actions — require user approval (opencode pattern: tools with permission flag)
19
- const DANGEROUS_ACTIONS = new Set([
20
- 'shell-command',
21
- 'file-write',
22
- 'create-folder',
23
- 'git-operation',
24
- ]);
25
-
26
- // Safe shell command prefixes — auto-approved even within shell-command
27
- // (opencode pattern: safe bash commands bypass permission)
28
- const SAFE_SHELL_PREFIXES = [
29
- 'ls', 'dir', 'pwd', 'echo', 'cat', 'head', 'tail', 'wc',
30
- 'git status', 'git log', 'git diff', 'git branch', 'git remote',
31
- 'node --version', 'npm --version', 'python --version',
32
- 'which', 'where', 'type', 'whoami', 'hostname',
33
- 'date', 'uptime', 'df', 'free',
34
- ];
35
-
36
- // Pending permission requests: requestId → { resolve }
37
- const pendingPermissions = new Map();
38
-
39
- /**
40
- * Check if an action needs user permission.
41
- * @param {string} action - Relay action type
42
- * @param {object} payload - Action payload
43
- * @returns {boolean}
44
- */
45
- export function needsPermission(action, payload) {
46
- if (SAFE_ACTIONS.has(action)) return false;
47
- if (!DANGEROUS_ACTIONS.has(action)) return false; // unknown actions handled elsewhere
48
-
49
- // Shell commands: check if it's a safe read-only command
50
- if (action === 'shell-command' && payload?.command) {
51
- const cmd = payload.command.trim().toLowerCase();
52
- if (SAFE_SHELL_PREFIXES.some(prefix => cmd.startsWith(prefix))) {
53
- return false;
54
- }
55
- }
56
-
57
- return true;
58
- }
59
-
60
- /**
61
- * Request permission from the browser via the relay WebSocket.
62
- * Blocks until the user approves or denies.
63
- *
64
- * @param {WebSocket} ws - Relay WebSocket connection
65
- * @param {string} requestId - Relay request ID
66
- * @param {string} action - Action being requested
67
- * @param {object} payload - Action payload
68
- * @param {number} timeoutMs - Timeout for waiting (default 60s)
69
- * @returns {Promise<boolean>} - true if approved, false if denied
70
- */
71
- export function requestPermission(ws, requestId, action, payload, timeoutMs = 60000) {
72
- return new Promise((resolve) => {
73
- const permId = `perm-${requestId}`;
74
-
75
- const timeout = setTimeout(() => {
76
- pendingPermissions.delete(permId);
77
- resolve(false); // Timeout = deny
78
- }, timeoutMs);
79
-
80
- pendingPermissions.set(permId, {
81
- resolve: (approved) => {
82
- clearTimeout(timeout);
83
- pendingPermissions.delete(permId);
84
- resolve(approved);
85
- },
86
- });
87
-
88
- // Send permission request to server → browser
89
- ws.send(JSON.stringify({
90
- type: 'relay-permission-request',
91
- requestId,
92
- permissionId: permId,
93
- action,
94
- description: describeAction(action, payload),
95
- payload: sanitizePayload(action, payload),
96
- }));
97
- });
98
- }
99
-
100
- /**
101
- * Handle a permission response from the server.
102
- * @param {object} data - { permissionId, approved }
103
- */
104
- export function handlePermissionResponse(data) {
105
- const pending = pendingPermissions.get(data.permissionId);
106
- if (pending) {
107
- pending.resolve(Boolean(data.approved));
108
- }
109
- }
110
-
111
- /**
112
- * Generate a human-readable description of the action.
113
- */
114
- function describeAction(action, payload) {
115
- switch (action) {
116
- case 'shell-command':
117
- return `Execute command: ${payload?.command || '(unknown)'}`;
118
- case 'file-write':
119
- return `Write to file: ${payload?.filePath || '(unknown)'}`;
120
- case 'create-folder':
121
- return `Create folder: ${payload?.folderPath || '(unknown)'}`;
122
- case 'git-operation':
123
- return `Run git: ${payload?.gitCommand || '(unknown)'}`;
124
- default:
125
- return `${action}: ${JSON.stringify(payload || {}).slice(0, 200)}`;
126
- }
127
- }
128
-
129
- /**
130
- * Sanitize payload for display — remove large content fields.
131
- */
132
- function sanitizePayload(action, payload) {
133
- if (!payload) return {};
134
- const safe = { ...payload };
135
- // Don't send file content to browser for permission display
136
- if (safe.content && safe.content.length > 500) {
137
- safe.content = safe.content.slice(0, 500) + `... (${safe.content.length} chars total)`;
138
- }
139
- return safe;
140
- }
@@ -1,261 +0,0 @@
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 DELETED
@@ -1,54 +0,0 @@
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
- }
File without changes
File without changes
File without changes