upfynai-code 3.0.3 → 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 (243) hide show
  1. package/README.md +66 -91
  2. package/bin/cli.js +191 -0
  3. package/{client/dist → dist/client}/api-docs.html +838 -838
  4. package/{client/dist/assets/AppContent-Bvg0CPCO.js → dist/client/assets/AppContent-BofJquUs.js} +43 -43
  5. package/dist/client/assets/BrowserPanel-CSvD4jOX.js +2 -0
  6. package/dist/client/assets/CanvasFullScreen-onRfarpc.js +1 -0
  7. package/dist/client/assets/CanvasWorkspace-DvGKdL-k.js +259 -0
  8. package/dist/client/assets/DashboardPanel-DqAHbXDO.js +1 -0
  9. package/dist/client/assets/FileTree-BE0h-9M9.js +1 -0
  10. package/{client/dist/assets/GitPanel-RtyZUIWS.js → dist/client/assets/GitPanel-DdeJ0bp5.js} +2 -2
  11. package/{client/dist/assets/LoginModal-BWep8a6g.js → dist/client/assets/LoginModal-BP0pCTrH.js} +3 -3
  12. package/{client/dist/assets/MarkdownPreview-DHmk3qzu.js → dist/client/assets/MarkdownPreview-CESjI261.js} +1 -1
  13. package/dist/client/assets/MermaidBlock-D0rfEhrT.js +2 -0
  14. package/dist/client/assets/Onboarding-B2zQy-_6.js +1 -0
  15. package/dist/client/assets/SetupForm-Be7-WBe-.js +1 -0
  16. package/dist/client/assets/WorkflowsPanel-CusLbVJ6.js +1 -0
  17. package/{client/dist/assets/index-C5ptjuTl.js → dist/client/assets/index-BQy15irW.js} +25 -25
  18. package/dist/client/assets/index-CS0fDqEC.js +1 -0
  19. package/dist/client/assets/index-DYLSCCCp.css +1 -0
  20. package/dist/client/assets/vendor-canvas-QWTduIvM.js +23 -0
  21. package/{client/dist/assets/vendor-codemirror-CbtmxxaB.js → dist/client/assets/vendor-codemirror-D2ALgpaX.js} +1 -1
  22. package/{client/dist/assets/vendor-icons-BaD0x9SL.js → dist/client/assets/vendor-icons-kix3Gb31.js} +178 -138
  23. package/{client/dist/assets/vendor-mermaid-CH7SGc99.js → dist/client/assets/vendor-mermaid-CS3J4_Bz.js} +329 -326
  24. package/{client/dist/assets/vendor-syntax-DuHI9Ok6.js → dist/client/assets/vendor-syntax-LS_Nt30I.js} +1 -1
  25. package/{client/dist → dist/client}/clear-cache.html +85 -85
  26. package/dist/client/favicon.png +0 -0
  27. package/dist/client/favicon.svg +15 -0
  28. package/{client/dist → dist/client}/index.html +17 -17
  29. package/{client/dist → dist/client}/manifest.json +15 -15
  30. package/{client/dist → dist/client}/mcp-docs.html +108 -108
  31. package/{client/dist → dist/client}/offline.html +84 -84
  32. package/{client/dist → dist/client}/sw.js +82 -82
  33. package/package.json +55 -104
  34. package/scripts/postinstall.js +9 -0
  35. package/scripts/prepublish.js +77 -0
  36. package/src/animation.js +228 -0
  37. package/src/auth.js +142 -0
  38. package/src/config.js +40 -0
  39. package/src/connect.js +416 -0
  40. package/src/launch.js +81 -0
  41. package/src/mcp.js +57 -0
  42. package/src/permissions.js +140 -0
  43. package/src/persistent-shell.js +261 -0
  44. package/src/server.js +54 -0
  45. package/client/dist/assets/CanvasFullScreen-BdiJ35aq.js +0 -1
  46. package/client/dist/assets/CanvasWorkspace-Bk9R9_e0.js +0 -163
  47. package/client/dist/assets/DashboardPanel-CblJfTGi.js +0 -1
  48. package/client/dist/assets/FileTree-BDUnBheV.js +0 -1
  49. package/client/dist/assets/MermaidBlock-BuBc_G-F.js +0 -2
  50. package/client/dist/assets/Onboarding-Drnlt75a.js +0 -1
  51. package/client/dist/assets/SetupForm-CtCKitZG.js +0 -1
  52. package/client/dist/assets/WorkflowsPanel-B2mIXDvD.js +0 -1
  53. package/client/dist/assets/index-BFuqS0tY.css +0 -1
  54. package/client/dist/assets/vendor-canvas-D39yWul6.js +0 -49
  55. package/client/dist/favicon.png +0 -0
  56. package/client/dist/favicon.svg +0 -5
  57. package/commands/upfynai-connect.md +0 -59
  58. package/commands/upfynai-disconnect.md +0 -31
  59. package/commands/upfynai-doctor.md +0 -99
  60. package/commands/upfynai-export.md +0 -49
  61. package/commands/upfynai-local.md +0 -82
  62. package/commands/upfynai-status.md +0 -75
  63. package/commands/upfynai-stop.md +0 -49
  64. package/commands/upfynai-uninstall.md +0 -58
  65. package/commands/upfynai.md +0 -69
  66. package/scripts/build-client.js +0 -17
  67. package/scripts/fix-node-pty.js +0 -67
  68. package/scripts/install-commands.js +0 -78
  69. package/server/agent-loop.js +0 -242
  70. package/server/auto-compact.js +0 -99
  71. package/server/claude-sdk.js +0 -797
  72. package/server/cli-ui.js +0 -798
  73. package/server/cli.js +0 -751
  74. package/server/constants/config.js +0 -31
  75. package/server/cursor-cli.js +0 -270
  76. package/server/database/auth.db +0 -0
  77. package/server/database/db.js +0 -1451
  78. package/server/database/init.sql +0 -70
  79. package/server/index.js +0 -3814
  80. package/server/load-env.js +0 -26
  81. package/server/mcp-server.js +0 -621
  82. package/server/middleware/auth.js +0 -181
  83. package/server/middleware/relayHelpers.js +0 -44
  84. package/server/middleware/sandboxRouter.js +0 -174
  85. package/server/openai-codex.js +0 -403
  86. package/server/openrouter.js +0 -137
  87. package/server/projects.js +0 -1807
  88. package/server/provider-factory.js +0 -174
  89. package/server/relay-client.js +0 -390
  90. package/server/routes/agent.js +0 -1234
  91. package/server/routes/auth.js +0 -559
  92. package/server/routes/canvas.js +0 -53
  93. package/server/routes/cli-auth.js +0 -263
  94. package/server/routes/codex.js +0 -396
  95. package/server/routes/commands.js +0 -707
  96. package/server/routes/composio.js +0 -176
  97. package/server/routes/cursor.js +0 -770
  98. package/server/routes/dashboard.js +0 -295
  99. package/server/routes/git.js +0 -1208
  100. package/server/routes/keys.js +0 -34
  101. package/server/routes/mcp-utils.js +0 -48
  102. package/server/routes/mcp.js +0 -661
  103. package/server/routes/payments.js +0 -227
  104. package/server/routes/projects.js +0 -655
  105. package/server/routes/sessions.js +0 -146
  106. package/server/routes/settings.js +0 -261
  107. package/server/routes/taskmaster.js +0 -1928
  108. package/server/routes/user.js +0 -106
  109. package/server/routes/vapi-chat.js +0 -624
  110. package/server/routes/voice.js +0 -235
  111. package/server/routes/webhooks.js +0 -166
  112. package/server/routes/workflows.js +0 -312
  113. package/server/sandbox.js +0 -120
  114. package/server/services/composio.js +0 -204
  115. package/server/services/sessionRegistry.js +0 -139
  116. package/server/services/whisperService.js +0 -84
  117. package/server/services/workflowScheduler.js +0 -211
  118. package/server/tests/relay-flow.test.js +0 -570
  119. package/server/tests/sessions.test.js +0 -259
  120. package/server/utils/commandParser.js +0 -303
  121. package/server/utils/email.js +0 -66
  122. package/server/utils/gitConfig.js +0 -24
  123. package/server/utils/mcp-detector.js +0 -198
  124. package/server/utils/taskmaster-websocket.js +0 -129
  125. package/shared/integrationCatalog.d.ts +0 -12
  126. package/shared/integrationCatalog.js +0 -172
  127. package/shared/modelConstants.js +0 -96
  128. /package/{shared → dist}/agents/claude.js +0 -0
  129. /package/{shared → dist}/agents/codex.js +0 -0
  130. /package/{shared → dist}/agents/cursor.js +0 -0
  131. /package/{shared → dist}/agents/detect.js +0 -0
  132. /package/{shared → dist}/agents/exec.js +0 -0
  133. /package/{shared → dist}/agents/files.js +0 -0
  134. /package/{shared → dist}/agents/git.js +0 -0
  135. /package/{shared → dist}/agents/gitagent.js +0 -0
  136. /package/{shared → dist}/agents/index.js +0 -0
  137. /package/{shared → dist}/agents/shell.js +0 -0
  138. /package/{shared → dist}/agents/utils.js +0 -0
  139. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  140. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  141. /package/{client/dist → dist/client}/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  142. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  143. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  144. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  145. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  146. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  147. /package/{client/dist → dist/client}/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  148. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  149. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  150. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  151. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  152. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  153. /package/{client/dist → dist/client}/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  154. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  155. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  156. /package/{client/dist → dist/client}/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  157. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  158. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  159. /package/{client/dist → dist/client}/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  160. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  161. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  162. /package/{client/dist → dist/client}/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  163. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  164. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  165. /package/{client/dist → dist/client}/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  166. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  167. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  168. /package/{client/dist → dist/client}/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  169. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  170. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  171. /package/{client/dist → dist/client}/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  172. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  173. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  174. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  175. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  176. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  177. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  178. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  179. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  180. /package/{client/dist → dist/client}/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  181. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  182. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  183. /package/{client/dist → dist/client}/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  184. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  185. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  186. /package/{client/dist → dist/client}/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  187. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  188. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  189. /package/{client/dist → dist/client}/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  190. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  191. /package/{client/dist → dist/client}/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  192. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  193. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  194. /package/{client/dist → dist/client}/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  195. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  196. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  197. /package/{client/dist → dist/client}/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  198. /package/{client/dist → dist/client}/assets/PreviewPanel-CqCa92Tf.js +0 -0
  199. /package/{client/dist → dist/client}/assets/pdf-CE_K4jFx.js +0 -0
  200. /package/{client/dist → dist/client}/assets/vendor-canvas-BZV40eAE.css +0 -0
  201. /package/{client/dist → dist/client}/assets/vendor-diff-DNQpbhrT.js +0 -0
  202. /package/{client/dist → dist/client}/assets/vendor-i18n-DCFGyhQR.js +0 -0
  203. /package/{client/dist → dist/client}/assets/vendor-markdown-CimbIo6Y.js +0 -0
  204. /package/{client/dist → dist/client}/assets/vendor-react-96lCPsRK.js +0 -0
  205. /package/{client/dist → dist/client}/assets/vendor-xterm-CZq1hqo1.js +0 -0
  206. /package/{client/dist → dist/client}/assets/vendor-xterm-qxJ8_QYu.css +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}/screenshots/cli-selection.png +0 -0
  238. /package/{client/dist → dist/client}/screenshots/desktop-main.png +0 -0
  239. /package/{client/dist → dist/client}/screenshots/mobile-chat.png +0 -0
  240. /package/{client/dist → dist/client}/screenshots/tools-modal.png +0 -0
  241. /package/{shared → dist}/gitagent/index.js +0 -0
  242. /package/{shared → dist}/gitagent/parser.js +0 -0
  243. /package/{shared → dist}/gitagent/prompt-builder.js +0 -0
@@ -1,707 +0,0 @@
1
- import express from 'express';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import os from 'os';
6
- import matter from 'gray-matter';
7
- import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
-
12
- const router = express.Router();
13
-
14
- /**
15
- * Recursively scan directory for command files (.md)
16
- * @param {string} dir - Directory to scan
17
- * @param {string} baseDir - Base directory for relative paths
18
- * @param {string} namespace - Namespace for commands (e.g., 'project', 'user')
19
- * @returns {Promise<Array>} Array of command objects
20
- */
21
- async function scanCommandsDirectory(dir, baseDir, namespace) {
22
- const commands = [];
23
-
24
- try {
25
- // Check if directory exists
26
- await fs.access(dir);
27
-
28
- const entries = await fs.readdir(dir, { withFileTypes: true });
29
-
30
- for (const entry of entries) {
31
- const fullPath = path.join(dir, entry.name);
32
-
33
- if (entry.isDirectory()) {
34
- // Recursively scan subdirectories
35
- const subCommands = await scanCommandsDirectory(fullPath, baseDir, namespace);
36
- commands.push(...subCommands);
37
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
38
- // Parse markdown file for metadata
39
- try {
40
- const content = await fs.readFile(fullPath, 'utf8');
41
- const { data: frontmatter, content: commandContent } = matter(content);
42
-
43
- // Calculate relative path from baseDir for command name
44
- const relativePath = path.relative(baseDir, fullPath);
45
- // Remove .md extension and convert to command name
46
- const commandName = '/' + relativePath.replace(/\.md$/, '').replace(/\\/g, '/');
47
-
48
- // Extract description from frontmatter or first line of content
49
- let description = frontmatter.description || '';
50
- if (!description) {
51
- const firstLine = commandContent.trim().split('\n')[0];
52
- description = firstLine.replace(/^#+\s*/, '').trim();
53
- }
54
-
55
- commands.push({
56
- name: commandName,
57
- path: fullPath,
58
- relativePath,
59
- description,
60
- namespace,
61
- metadata: frontmatter
62
- });
63
- } catch (err) {
64
- }
65
- }
66
- }
67
- } catch (err) {
68
- // Directory doesn't exist or can't be accessed - this is okay
69
- if (err.code !== 'ENOENT' && err.code !== 'EACCES') {
70
- }
71
- }
72
-
73
- return commands;
74
- }
75
-
76
- /**
77
- * Extract .md file paths from a relay file-tree response
78
- * @param {Array} tree - Tree structure from relay
79
- * @param {string} baseDir - Base directory path
80
- * @param {string} prefix - Current relative path prefix
81
- * @returns {Array} Array of { fullPath, relativePath }
82
- */
83
- function extractMdFiles(tree, baseDir, prefix = '') {
84
- const files = [];
85
- if (!Array.isArray(tree)) return files;
86
- for (const item of tree) {
87
- const name = item.name || item.path || '';
88
- const relativePath = prefix ? `${prefix}/${name}` : name;
89
- if (item.type === 'directory' || item.children) {
90
- files.push(...extractMdFiles(item.children || [], baseDir, relativePath));
91
- } else if (name.endsWith('.md')) {
92
- const fullPath = baseDir.endsWith('/') ? `${baseDir}${relativePath}` : `${baseDir}/${relativePath}`;
93
- files.push({ fullPath, relativePath });
94
- }
95
- }
96
- return files;
97
- }
98
-
99
- /**
100
- * Built-in commands that are always available
101
- */
102
- const builtInCommands = [
103
- {
104
- name: '/help',
105
- description: 'Show help documentation for Upfyn-Code',
106
- namespace: 'builtin',
107
- metadata: { type: 'builtin' }
108
- },
109
- {
110
- name: '/clear',
111
- description: 'Clear the conversation history',
112
- namespace: 'builtin',
113
- metadata: { type: 'builtin' }
114
- },
115
- {
116
- name: '/model',
117
- description: 'Switch or view the current AI model',
118
- namespace: 'builtin',
119
- metadata: { type: 'builtin' }
120
- },
121
- {
122
- name: '/cost',
123
- description: 'Display token usage and cost information',
124
- namespace: 'builtin',
125
- metadata: { type: 'builtin' }
126
- },
127
- {
128
- name: '/memory',
129
- description: 'Open CLAUDE.md memory file for editing',
130
- namespace: 'builtin',
131
- metadata: { type: 'builtin' }
132
- },
133
- {
134
- name: '/config',
135
- description: 'Open settings and configuration',
136
- namespace: 'builtin',
137
- metadata: { type: 'builtin' }
138
- },
139
- {
140
- name: '/status',
141
- description: 'Show system status and version information',
142
- namespace: 'builtin',
143
- metadata: { type: 'builtin' }
144
- },
145
- {
146
- name: '/rewind',
147
- description: 'Rewind the conversation to a previous state',
148
- namespace: 'builtin',
149
- metadata: { type: 'builtin' }
150
- }
151
- ];
152
-
153
- /**
154
- * Built-in command handlers
155
- * Each handler returns { type: 'builtin', action: string, data: any }
156
- */
157
- const builtInHandlers = {
158
- '/help': async (args, context) => {
159
- const helpText = `# Upfyn-Code Commands
160
-
161
- ## Built-in Commands
162
-
163
- ${builtInCommands.map(cmd => `### ${cmd.name}
164
- ${cmd.description}
165
- `).join('\n')}
166
-
167
- ## Custom Commands
168
-
169
- Custom commands can be created in:
170
- - Project: \`.claude/commands/\` (project-specific)
171
- - User: \`~/.claude/commands/\` (available in all projects)
172
-
173
- ### Command Syntax
174
-
175
- - **Arguments**: Use \`$ARGUMENTS\` for all args or \`$1\`, \`$2\`, etc. for positional
176
- - **File Includes**: Use \`@filename\` to include file contents
177
- - **Bash Commands**: Use \`!command\` to execute bash commands
178
-
179
- ### Examples
180
-
181
- \`\`\`markdown
182
- /mycommand arg1 arg2
183
- \`\`\`
184
- `;
185
-
186
- return {
187
- type: 'builtin',
188
- action: 'help',
189
- data: {
190
- content: helpText,
191
- format: 'markdown'
192
- }
193
- };
194
- },
195
-
196
- '/clear': async (args, context) => {
197
- return {
198
- type: 'builtin',
199
- action: 'clear',
200
- data: {
201
- message: 'Conversation history cleared'
202
- }
203
- };
204
- },
205
-
206
- '/model': async (args, context) => {
207
- // Read available models from centralized constants
208
- const availableModels = {
209
- claude: CLAUDE_MODELS.OPTIONS.map(o => o.value),
210
- cursor: CURSOR_MODELS.OPTIONS.map(o => o.value),
211
- codex: CODEX_MODELS.OPTIONS.map(o => o.value)
212
- };
213
-
214
- const currentProvider = context?.provider || 'claude';
215
- const currentModel = context?.model || CLAUDE_MODELS.DEFAULT;
216
-
217
- return {
218
- type: 'builtin',
219
- action: 'model',
220
- data: {
221
- current: {
222
- provider: currentProvider,
223
- model: currentModel
224
- },
225
- available: availableModels,
226
- message: args.length > 0
227
- ? `Switching to model: ${args[0]}`
228
- : `Current model: ${currentModel}`
229
- }
230
- };
231
- },
232
-
233
- '/cost': async (args, context) => {
234
- const tokenUsage = context?.tokenUsage || {};
235
- const provider = context?.provider || 'claude';
236
- const model =
237
- context?.model ||
238
- (provider === 'cursor'
239
- ? CURSOR_MODELS.DEFAULT
240
- : provider === 'codex'
241
- ? CODEX_MODELS.DEFAULT
242
- : CLAUDE_MODELS.DEFAULT);
243
-
244
- const used = Number(tokenUsage.used ?? tokenUsage.totalUsed ?? tokenUsage.total_tokens ?? 0) || 0;
245
- const total =
246
- Number(
247
- tokenUsage.total ??
248
- tokenUsage.contextWindow ??
249
- parseInt(process.env.CONTEXT_WINDOW || '160000', 10),
250
- ) || 160000;
251
- const percentage = total > 0 ? Number(((used / total) * 100).toFixed(1)) : 0;
252
-
253
- const inputTokensRaw =
254
- Number(
255
- tokenUsage.inputTokens ??
256
- tokenUsage.input ??
257
- tokenUsage.cumulativeInputTokens ??
258
- tokenUsage.promptTokens ??
259
- 0,
260
- ) || 0;
261
- const outputTokens =
262
- Number(
263
- tokenUsage.outputTokens ??
264
- tokenUsage.output ??
265
- tokenUsage.cumulativeOutputTokens ??
266
- tokenUsage.completionTokens ??
267
- 0,
268
- ) || 0;
269
- const cacheTokens =
270
- Number(
271
- tokenUsage.cacheReadTokens ??
272
- tokenUsage.cacheCreationTokens ??
273
- tokenUsage.cacheTokens ??
274
- tokenUsage.cachedTokens ??
275
- 0,
276
- ) || 0;
277
-
278
- // If we only have total used tokens, treat them as input for display/estimation.
279
- const inputTokens =
280
- inputTokensRaw > 0 || outputTokens > 0 || cacheTokens > 0 ? inputTokensRaw + cacheTokens : used;
281
-
282
- // Rough default rates by provider (USD / 1M tokens).
283
- const pricingByProvider = {
284
- claude: { input: 3, output: 15 },
285
- cursor: { input: 3, output: 15 },
286
- codex: { input: 1.5, output: 6 },
287
- };
288
- const rates = pricingByProvider[provider] || pricingByProvider.claude;
289
-
290
- const inputCost = (inputTokens / 1_000_000) * rates.input;
291
- const outputCost = (outputTokens / 1_000_000) * rates.output;
292
- const totalCost = inputCost + outputCost;
293
-
294
- return {
295
- type: 'builtin',
296
- action: 'cost',
297
- data: {
298
- tokenUsage: {
299
- used,
300
- total,
301
- percentage,
302
- },
303
- cost: {
304
- input: inputCost.toFixed(4),
305
- output: outputCost.toFixed(4),
306
- total: totalCost.toFixed(4),
307
- },
308
- model,
309
- },
310
- };
311
- },
312
-
313
- '/status': async (args, context) => {
314
- // Read version from package.json
315
- const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json');
316
- let version = 'unknown';
317
- let packageName = 'upfynai-code';
318
-
319
- try {
320
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
321
- version = packageJson.version;
322
- packageName = packageJson.name;
323
- } catch (err) {
324
- }
325
-
326
- const uptime = process.uptime();
327
- const uptimeMinutes = Math.floor(uptime / 60);
328
- const uptimeHours = Math.floor(uptimeMinutes / 60);
329
- const uptimeFormatted = uptimeHours > 0
330
- ? `${uptimeHours}h ${uptimeMinutes % 60}m`
331
- : `${uptimeMinutes}m`;
332
-
333
- return {
334
- type: 'builtin',
335
- action: 'status',
336
- data: {
337
- version,
338
- packageName,
339
- uptime: uptimeFormatted,
340
- uptimeSeconds: Math.floor(uptime),
341
- model: context?.model || 'claude-sonnet-4.5',
342
- provider: context?.provider || 'claude',
343
- nodeVersion: process.version,
344
- platform: process.platform
345
- }
346
- };
347
- },
348
-
349
- '/memory': async (args, context) => {
350
- const projectPath = context?.projectPath;
351
-
352
- if (!projectPath) {
353
- return {
354
- type: 'builtin',
355
- action: 'memory',
356
- data: {
357
- error: 'No project selected',
358
- message: 'Please select a project to access its CLAUDE.md file'
359
- }
360
- };
361
- }
362
-
363
- const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
364
-
365
- // Check if CLAUDE.md exists
366
- let exists = false;
367
- try {
368
- await fs.access(claudeMdPath);
369
- exists = true;
370
- } catch (err) {
371
- // File doesn't exist
372
- }
373
-
374
- return {
375
- type: 'builtin',
376
- action: 'memory',
377
- data: {
378
- path: claudeMdPath,
379
- exists,
380
- message: exists
381
- ? `Opening CLAUDE.md at ${claudeMdPath}`
382
- : `CLAUDE.md not found at ${claudeMdPath}. Create it to store project-specific instructions.`
383
- }
384
- };
385
- },
386
-
387
- '/config': async (args, context) => {
388
- return {
389
- type: 'builtin',
390
- action: 'config',
391
- data: {
392
- message: 'Opening settings...'
393
- }
394
- };
395
- },
396
-
397
- '/rewind': async (args, context) => {
398
- const steps = args[0] ? parseInt(args[0]) : 1;
399
-
400
- if (isNaN(steps) || steps < 1) {
401
- return {
402
- type: 'builtin',
403
- action: 'rewind',
404
- data: {
405
- error: 'Invalid steps parameter',
406
- message: 'Usage: /rewind [number] - Rewind conversation by N steps (default: 1)'
407
- }
408
- };
409
- }
410
-
411
- return {
412
- type: 'builtin',
413
- action: 'rewind',
414
- data: {
415
- steps,
416
- message: `Rewinding conversation by ${steps} step${steps > 1 ? 's' : ''}...`
417
- }
418
- };
419
- }
420
- };
421
-
422
- /**
423
- * POST /api/commands/list
424
- * List all available commands from project and user directories
425
- */
426
- router.post('/list', async (req, res) => {
427
- try {
428
- const { projectPath } = req.body;
429
- const allCommands = [...builtInCommands];
430
-
431
- // Cloud mode: scan command directories on user's machine via relay
432
- if (req.isCloud) {
433
- if (!req.requireRelay()) return;
434
- try {
435
- const dirsToScan = [];
436
- if (projectPath) dirsToScan.push({ path: `${projectPath}/.claude/commands`, namespace: 'project' });
437
- dirsToScan.push({ path: '~/.claude/commands', namespace: 'user' });
438
-
439
- for (const dir of dirsToScan) {
440
- try {
441
- const result = await req.sendRelay('file-tree', { dirPath: dir.path, depth: 5 }, 15000);
442
- const tree = result.tree || result.files || [];
443
- // Extract .md files from tree
444
- const mdFiles = extractMdFiles(tree, dir.path);
445
- for (const file of mdFiles) {
446
- try {
447
- const fileResult = await req.sendRelay('file-read', { filePath: file.fullPath }, 10000);
448
- const { data: frontmatter, content: commandContent } = matter(fileResult.content);
449
- let description = frontmatter.description || '';
450
- if (!description) {
451
- const firstLine = commandContent.trim().split('\n')[0];
452
- description = firstLine.replace(/^#+\s*/, '').trim();
453
- }
454
- allCommands.push({
455
- name: '/' + file.relativePath.replace(/\.md$/, '').replace(/\\/g, '/'),
456
- path: file.fullPath,
457
- relativePath: file.relativePath,
458
- description,
459
- namespace: dir.namespace,
460
- metadata: frontmatter
461
- });
462
- } catch { /* skip unreadable files */ }
463
- }
464
- } catch { /* directory doesn't exist */ }
465
- }
466
-
467
- const customCommands = allCommands.filter(cmd => cmd.namespace !== 'builtin');
468
- customCommands.sort((a, b) => a.name.localeCompare(b.name));
469
- return res.json({ builtIn: builtInCommands, custom: customCommands, count: allCommands.length });
470
- } catch (err) {
471
- return res.status(500).json({ error: 'Failed to list commands via relay', message: err.message });
472
- }
473
- }
474
-
475
- // Local mode: scan project-level commands (.claude/commands/)
476
- if (projectPath) {
477
- const projectCommandsDir = path.join(projectPath, '.claude', 'commands');
478
- const projectCommands = await scanCommandsDirectory(
479
- projectCommandsDir,
480
- projectCommandsDir,
481
- 'project'
482
- );
483
- allCommands.push(...projectCommands);
484
- }
485
-
486
- // Scan user-level commands (~/.claude/commands/)
487
- const homeDir = os.homedir();
488
- const userCommandsDir = path.join(homeDir, '.claude', 'commands');
489
- const userCommands = await scanCommandsDirectory(
490
- userCommandsDir,
491
- userCommandsDir,
492
- 'user'
493
- );
494
- allCommands.push(...userCommands);
495
-
496
- // Separate built-in and custom commands
497
- const customCommands = allCommands.filter(cmd => cmd.namespace !== 'builtin');
498
-
499
- // Sort commands alphabetically by name
500
- customCommands.sort((a, b) => a.name.localeCompare(b.name));
501
-
502
- res.json({
503
- builtIn: builtInCommands,
504
- custom: customCommands,
505
- count: allCommands.length
506
- });
507
- } catch (error) {
508
- // command list error
509
- res.status(500).json({
510
- error: 'Failed to list commands',
511
- message: 'An error occurred'
512
- });
513
- }
514
- });
515
-
516
- /**
517
- * POST /api/commands/load
518
- * Load a specific command file and return its content and metadata
519
- */
520
- router.post('/load', async (req, res) => {
521
- try {
522
- const { commandPath } = req.body;
523
-
524
- if (!commandPath) {
525
- return res.status(400).json({
526
- error: 'Command path is required'
527
- });
528
- }
529
-
530
- // Cloud mode: read command file from user's machine via relay
531
- if (req.isCloud) {
532
- if (!req.requireRelay()) return;
533
- // Validate path contains .claude/commands
534
- if (!commandPath.includes('.claude/commands') && !commandPath.includes('.claude\\commands')) {
535
- return res.status(403).json({ error: 'Access denied', message: 'Command must be in .claude/commands directory' });
536
- }
537
- try {
538
- const result = await req.sendRelay('file-read', { filePath: commandPath }, 10000);
539
- const { data: metadata, content: commandContent } = matter(result.content);
540
- return res.json({ path: commandPath, metadata, content: commandContent });
541
- } catch (err) {
542
- return res.status(404).json({ error: 'Command not found', message: err.message });
543
- }
544
- }
545
-
546
- // Security: Prevent path traversal
547
- const resolvedPath = path.resolve(commandPath);
548
- if (!resolvedPath.startsWith(path.resolve(os.homedir())) &&
549
- !resolvedPath.includes('.claude/commands')) {
550
- return res.status(403).json({
551
- error: 'Access denied',
552
- message: 'Command must be in .claude/commands directory'
553
- });
554
- }
555
-
556
- // Read and parse the command file
557
- const content = await fs.readFile(commandPath, 'utf8');
558
- const { data: metadata, content: commandContent } = matter(content);
559
-
560
- res.json({
561
- path: commandPath,
562
- metadata,
563
- content: commandContent
564
- });
565
- } catch (error) {
566
- if (error.code === 'ENOENT') {
567
- return res.status(404).json({
568
- error: 'Command not found',
569
- message: `Command file not found: ${req.body.commandPath}`
570
- });
571
- }
572
-
573
- // command load error
574
- res.status(500).json({
575
- error: 'Failed to load command',
576
- message: 'An error occurred'
577
- });
578
- }
579
- });
580
-
581
- /**
582
- * POST /api/commands/execute
583
- * Execute a command with argument replacement
584
- * This endpoint prepares the command content but doesn't execute bash commands yet
585
- * (that will be handled in the command parser utility)
586
- */
587
- router.post('/execute', async (req, res) => {
588
- try {
589
- const { commandName, commandPath, args = [], context = {} } = req.body;
590
-
591
- if (!commandName) {
592
- return res.status(400).json({
593
- error: 'Command name is required'
594
- });
595
- }
596
-
597
- // Handle built-in commands
598
- const handler = builtInHandlers[commandName];
599
- if (handler) {
600
- try {
601
- const result = await handler(args, context);
602
- return res.json({
603
- ...result,
604
- command: commandName
605
- });
606
- } catch (error) {
607
- // built-in command error
608
- return res.status(500).json({
609
- error: 'Command execution failed',
610
- message: 'An error occurred',
611
- command: commandName
612
- });
613
- }
614
- }
615
-
616
- // Handle custom commands
617
- if (!commandPath) {
618
- return res.status(400).json({
619
- error: 'Command path is required for custom commands'
620
- });
621
- }
622
-
623
- // Cloud mode: read and execute custom command from user's machine
624
- if (req.isCloud) {
625
- if (!req.requireRelay()) return;
626
- if (!commandPath.includes('.claude/commands') && !commandPath.includes('.claude\\commands')) {
627
- return res.status(403).json({ error: 'Access denied', message: 'Command must be in .claude/commands directory' });
628
- }
629
- try {
630
- const result = await req.sendRelay('file-read', { filePath: commandPath }, 10000);
631
- const { data: metadata, content: commandContent } = matter(result.content);
632
- let processedContent = commandContent;
633
- const argsString = args.join(' ');
634
- processedContent = processedContent.replace(/\$ARGUMENTS/g, argsString);
635
- args.forEach((arg, index) => {
636
- const placeholder = `$${index + 1}`;
637
- processedContent = processedContent.replace(new RegExp(`\\${placeholder}\\b`, 'g'), arg);
638
- });
639
- return res.json({
640
- type: 'custom', command: commandName, content: processedContent, metadata,
641
- hasFileIncludes: processedContent.includes('@'),
642
- hasBashCommands: processedContent.includes('!')
643
- });
644
- } catch (err) {
645
- return res.status(404).json({ error: 'Command not found', message: err.message });
646
- }
647
- }
648
-
649
- // Load command content
650
- // Security: validate commandPath is within allowed directories
651
- {
652
- const resolvedPath = path.resolve(commandPath);
653
- const userBase = path.resolve(path.join(os.homedir(), '.claude', 'commands'));
654
- const projectBase = context?.projectPath
655
- ? path.resolve(path.join(context.projectPath, '.claude', 'commands'))
656
- : null;
657
- const isUnder = (base) => {
658
- const rel = path.relative(base, resolvedPath);
659
- return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
660
- };
661
- if (!(isUnder(userBase) || (projectBase && isUnder(projectBase)))) {
662
- return res.status(403).json({
663
- error: 'Access denied',
664
- message: 'Command must be in .claude/commands directory'
665
- });
666
- }
667
- }
668
- const content = await fs.readFile(commandPath, 'utf8');
669
- const { data: metadata, content: commandContent } = matter(content);
670
- // Basic argument replacement (will be enhanced in command parser utility)
671
- let processedContent = commandContent;
672
-
673
- // Replace $ARGUMENTS with all arguments joined
674
- const argsString = args.join(' ');
675
- processedContent = processedContent.replace(/\$ARGUMENTS/g, argsString);
676
-
677
- // Replace $1, $2, etc. with positional arguments
678
- args.forEach((arg, index) => {
679
- const placeholder = `$${index + 1}`;
680
- processedContent = processedContent.replace(new RegExp(`\\${placeholder}\\b`, 'g'), arg);
681
- });
682
-
683
- res.json({
684
- type: 'custom',
685
- command: commandName,
686
- content: processedContent,
687
- metadata,
688
- hasFileIncludes: processedContent.includes('@'),
689
- hasBashCommands: processedContent.includes('!')
690
- });
691
- } catch (error) {
692
- if (error.code === 'ENOENT') {
693
- return res.status(404).json({
694
- error: 'Command not found',
695
- message: `Command file not found: ${req.body.commandPath}`
696
- });
697
- }
698
-
699
- // command execution error
700
- res.status(500).json({
701
- error: 'Failed to execute command',
702
- message: 'An error occurred'
703
- });
704
- }
705
- });
706
-
707
- export default router;