@cloudcli-ai/cloudcli 1.36.3 → 1.37.1

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 (569) hide show
  1. package/dist/assets/index-D3jfzuKN.css +32 -0
  2. package/dist/assets/index-DTMWjJj8.js +1550 -0
  3. package/dist/index.html +2 -2
  4. package/dist-server/server/constants/config.js.map +1 -1
  5. package/dist-server/server/index.js +73 -1224
  6. package/dist-server/server/index.js.map +1 -1
  7. package/dist-server/server/load-env.js +17 -4
  8. package/dist-server/server/load-env.js.map +1 -1
  9. package/dist-server/server/modules/agent/agent.module.js +37 -0
  10. package/dist-server/server/modules/agent/agent.module.js.map +1 -0
  11. package/dist-server/server/modules/agent/agent.routes.js +1161 -0
  12. package/dist-server/server/modules/agent/agent.routes.js.map +1 -0
  13. package/dist-server/server/modules/agent/index.js +3 -0
  14. package/dist-server/server/modules/agent/index.js.map +1 -0
  15. package/dist-server/server/modules/agent/tests/agent.routes.test.js +176 -0
  16. package/dist-server/server/modules/agent/tests/agent.routes.test.js.map +1 -0
  17. package/dist-server/server/modules/assets/assets.routes.js +56 -15
  18. package/dist-server/server/modules/assets/assets.routes.js.map +1 -1
  19. package/dist-server/server/modules/assets/index.js +2 -2
  20. package/dist-server/server/modules/assets/index.js.map +1 -1
  21. package/dist-server/server/modules/assets/services/image-assets.service.js +39 -1
  22. package/dist-server/server/modules/assets/services/image-assets.service.js.map +1 -1
  23. package/dist-server/server/modules/assets/tests/image-assets.service.test.js +21 -1
  24. package/dist-server/server/modules/assets/tests/image-assets.service.test.js.map +1 -1
  25. package/dist-server/server/{middleware/auth.js → modules/auth/auth.middleware.js} +30 -8
  26. package/dist-server/server/modules/auth/auth.middleware.js.map +1 -0
  27. package/dist-server/server/modules/auth/auth.module.js +29 -0
  28. package/dist-server/server/modules/auth/auth.module.js.map +1 -0
  29. package/dist-server/server/modules/auth/auth.routes.js +45 -0
  30. package/dist-server/server/modules/auth/auth.routes.js.map +1 -0
  31. package/dist-server/server/modules/auth/auth.service.js +113 -0
  32. package/dist-server/server/modules/auth/auth.service.js.map +1 -0
  33. package/dist-server/server/modules/auth/index.js +9 -0
  34. package/dist-server/server/modules/auth/index.js.map +1 -0
  35. package/dist-server/server/modules/auth/tests/auth.service.test.js +80 -0
  36. package/dist-server/server/modules/auth/tests/auth.service.test.js.map +1 -0
  37. package/dist-server/server/{browser-use-mcp.js → modules/browser-use/browser-use-mcp.js} +3 -1
  38. package/dist-server/server/modules/browser-use/browser-use-mcp.js.map +1 -0
  39. package/dist-server/server/modules/browser-use/browser-use.service.js +3 -4
  40. package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -1
  41. package/dist-server/server/modules/browser-use/index.js +9 -0
  42. package/dist-server/server/modules/browser-use/index.js.map +1 -0
  43. package/dist-server/server/modules/cli/cli.js +11 -0
  44. package/dist-server/server/modules/cli/cli.js.map +1 -0
  45. package/dist-server/server/modules/cli/cli.module.js +86 -0
  46. package/dist-server/server/modules/cli/cli.module.js.map +1 -0
  47. package/dist-server/server/modules/cli/cli.service.js +244 -0
  48. package/dist-server/server/modules/cli/cli.service.js.map +1 -0
  49. package/dist-server/server/modules/cli/index.js +3 -0
  50. package/dist-server/server/modules/cli/index.js.map +1 -0
  51. package/dist-server/server/modules/cli/sandbox.service.js +332 -0
  52. package/dist-server/server/modules/cli/sandbox.service.js.map +1 -0
  53. package/dist-server/server/modules/cli/tests/cli.service.test.js +78 -0
  54. package/dist-server/server/modules/cli/tests/cli.service.test.js.map +1 -0
  55. package/dist-server/server/modules/cli/tests/sandbox.service.test.js +55 -0
  56. package/dist-server/server/modules/cli/tests/sandbox.service.test.js.map +1 -0
  57. package/dist-server/server/modules/commands/commands.module.js +20 -0
  58. package/dist-server/server/modules/commands/commands.module.js.map +1 -0
  59. package/dist-server/server/modules/commands/commands.routes.js +497 -0
  60. package/dist-server/server/modules/commands/commands.routes.js.map +1 -0
  61. package/dist-server/server/modules/commands/index.js +3 -0
  62. package/dist-server/server/modules/commands/index.js.map +1 -0
  63. package/dist-server/server/modules/commands/tests/commands.test.js +91 -0
  64. package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -0
  65. package/dist-server/server/modules/database/index.js +3 -0
  66. package/dist-server/server/modules/database/index.js.map +1 -1
  67. package/dist-server/server/modules/database/migrations.js +31 -1
  68. package/dist-server/server/modules/database/migrations.js.map +1 -1
  69. package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
  70. package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
  71. package/dist-server/server/modules/database/repositories/sessions.db.js +76 -7
  72. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  73. package/dist-server/server/modules/database/schema.js +29 -0
  74. package/dist-server/server/modules/database/schema.js.map +1 -1
  75. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
  76. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
  77. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -1
  78. package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
  79. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +22 -0
  80. package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
  81. package/dist-server/server/modules/file-tree/file-tree.module.js +95 -0
  82. package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -0
  83. package/dist-server/server/modules/file-tree/file-tree.routes.js +208 -0
  84. package/dist-server/server/modules/file-tree/file-tree.routes.js.map +1 -0
  85. package/dist-server/server/modules/file-tree/file-tree.service.js +576 -0
  86. package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -0
  87. package/dist-server/server/modules/file-tree/index.js +3 -0
  88. package/dist-server/server/modules/file-tree/index.js.map +1 -0
  89. package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js +123 -0
  90. package/dist-server/server/modules/file-tree/tests/file-tree.routes.test.js.map +1 -0
  91. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +260 -0
  92. package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -0
  93. package/dist-server/server/modules/git/git-parsing.service.js +61 -0
  94. package/dist-server/server/modules/git/git-parsing.service.js.map +1 -0
  95. package/dist-server/server/modules/git/git.module.js +14 -0
  96. package/dist-server/server/modules/git/git.module.js.map +1 -0
  97. package/dist-server/server/modules/git/git.routes.js +1339 -0
  98. package/dist-server/server/modules/git/git.routes.js.map +1 -0
  99. package/dist-server/server/modules/git/index.js +3 -0
  100. package/dist-server/server/modules/git/index.js.map +1 -0
  101. package/dist-server/server/modules/git/tests/git-init.routes.test.js +48 -0
  102. package/dist-server/server/modules/git/tests/git-init.routes.test.js.map +1 -0
  103. package/dist-server/server/{routes → modules/git/tests}/git.test.js +1 -1
  104. package/dist-server/server/modules/git/tests/git.test.js.map +1 -0
  105. package/dist-server/server/modules/notifications/index.js +15 -1
  106. package/dist-server/server/modules/notifications/index.js.map +1 -1
  107. package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js +44 -0
  108. package/dist-server/server/modules/notifications/tests/notification-orchestrator.integration.test.js.map +1 -0
  109. package/dist-server/server/{services/vapid-keys.js → modules/notifications/vapid-keys.service.js} +3 -2
  110. package/dist-server/server/modules/notifications/vapid-keys.service.js.map +1 -0
  111. package/dist-server/server/modules/plugins/index.js +9 -0
  112. package/dist-server/server/modules/plugins/index.js.map +1 -0
  113. package/dist-server/server/{utils/plugin-process-manager.js → modules/plugins/plugin-process.service.js} +3 -2
  114. package/dist-server/server/modules/plugins/plugin-process.service.js.map +1 -0
  115. package/dist-server/server/{utils/plugin-loader.js → modules/plugins/plugin-registry.service.js} +2 -1
  116. package/dist-server/server/modules/plugins/plugin-registry.service.js.map +1 -0
  117. package/dist-server/server/modules/plugins/plugins.module.js +30 -0
  118. package/dist-server/server/modules/plugins/plugins.module.js.map +1 -0
  119. package/dist-server/server/modules/plugins/plugins.routes.js +70 -0
  120. package/dist-server/server/modules/plugins/plugins.routes.js.map +1 -0
  121. package/dist-server/server/modules/plugins/plugins.service.js +123 -0
  122. package/dist-server/server/modules/plugins/plugins.service.js.map +1 -0
  123. package/dist-server/server/modules/plugins/tests/plugins.service.test.js +28 -0
  124. package/dist-server/server/modules/plugins/tests/plugins.service.test.js.map +1 -0
  125. package/dist-server/server/modules/projects/index.js +5 -0
  126. package/dist-server/server/modules/projects/index.js.map +1 -1
  127. package/dist-server/server/modules/providers/index.js +3 -0
  128. package/dist-server/server/modules/providers/index.js.map +1 -1
  129. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +6 -0
  130. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  131. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +47 -18
  132. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
  133. package/dist-server/server/{claude-sdk.js → modules/providers/list/claude/claude-runtime.provider.js} +69 -55
  134. package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -0
  135. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +6 -4
  136. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  137. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +23 -4
  138. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  139. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  140. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  141. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +59 -75
  142. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
  143. package/dist-server/server/{openai-codex.js → modules/providers/list/codex/codex-runtime.provider.js} +48 -34
  144. package/dist-server/server/modules/providers/list/codex/codex-runtime.provider.js.map +1 -0
  145. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +1 -54
  146. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  147. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +290 -6
  148. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  149. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +5 -0
  150. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
  151. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  152. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  153. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +33 -673
  154. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
  155. package/dist-server/server/{cursor-cli.js → modules/providers/list/cursor/cursor-runtime.provider.js} +40 -28
  156. package/dist-server/server/modules/providers/list/cursor/cursor-runtime.provider.js.map +1 -0
  157. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +21 -14
  158. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  159. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
  160. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
  161. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +112 -333
  162. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
  163. package/dist-server/server/{opencode-cli.js → modules/providers/list/opencode/opencode-runtime.provider.js} +38 -25
  164. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js.map +1 -0
  165. package/dist-server/server/{opencode-cli.test.js → modules/providers/list/opencode/opencode-runtime.provider.test.js} +27 -4
  166. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js.map +1 -0
  167. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +0 -12
  168. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
  169. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +11 -5
  170. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
  171. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +2 -0
  172. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -1
  173. package/dist-server/server/modules/providers/provider.routes.js +169 -37
  174. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  175. package/dist-server/server/modules/providers/services/provider-capabilities.service.js +5 -1
  176. package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -1
  177. package/dist-server/server/modules/providers/services/provider-models.service.js +247 -184
  178. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
  179. package/dist-server/server/modules/providers/services/provider-runtime.service.js +66 -0
  180. package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -0
  181. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +268 -0
  182. package/dist-server/server/modules/providers/services/provider-token-usage.service.js.map +1 -0
  183. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +12 -4
  184. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  185. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +2 -0
  186. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  187. package/dist-server/server/modules/providers/services/sessions.service.js +116 -3
  188. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  189. package/dist-server/server/modules/providers/shared/base/abstract.provider.js +2 -3
  190. package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
  191. package/dist-server/server/modules/providers/tests/claude-auth.test.js +122 -0
  192. package/dist-server/server/modules/providers/tests/claude-auth.test.js.map +1 -0
  193. package/dist-server/server/modules/providers/tests/claude-sessions.test.js +44 -0
  194. package/dist-server/server/modules/providers/tests/claude-sessions.test.js.map +1 -0
  195. package/dist-server/server/modules/providers/tests/codex-sessions.test.js +76 -4
  196. package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
  197. package/dist-server/server/modules/providers/tests/opencode-models.test.js +15 -126
  198. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
  199. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +5 -5
  200. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
  201. package/dist-server/server/modules/providers/tests/{provider-image-history.test.js → provider-attachment-history.test.js} +60 -2
  202. package/dist-server/server/modules/providers/tests/provider-attachment-history.test.js.map +1 -0
  203. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +241 -271
  204. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
  205. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +126 -0
  206. package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -0
  207. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +166 -0
  208. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -0
  209. package/dist-server/server/modules/providers/tests/provider.routes.test.js +126 -0
  210. package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
  211. package/dist-server/server/modules/providers/tests/sessions-details.test.js +62 -0
  212. package/dist-server/server/modules/providers/tests/sessions-details.test.js.map +1 -0
  213. package/dist-server/server/modules/providers/tests/sessions.service.test.js +87 -0
  214. package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
  215. package/dist-server/server/modules/providers/tests/skills.test.js +4 -2
  216. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  217. package/dist-server/server/modules/settings/index.js +3 -0
  218. package/dist-server/server/modules/settings/index.js.map +1 -0
  219. package/dist-server/server/modules/settings/settings.module.js +35 -0
  220. package/dist-server/server/modules/settings/settings.module.js.map +1 -0
  221. package/dist-server/server/modules/settings/settings.routes.js +34 -0
  222. package/dist-server/server/modules/settings/settings.routes.js.map +1 -0
  223. package/dist-server/server/modules/settings/settings.service.js +116 -0
  224. package/dist-server/server/modules/settings/settings.service.js.map +1 -0
  225. package/dist-server/server/modules/settings/tests/settings.service.test.js +48 -0
  226. package/dist-server/server/modules/settings/tests/settings.service.test.js.map +1 -0
  227. package/dist-server/server/modules/system/index.js +3 -0
  228. package/dist-server/server/modules/system/index.js.map +1 -0
  229. package/dist-server/server/modules/system/system.module.js +44 -0
  230. package/dist-server/server/modules/system/system.module.js.map +1 -0
  231. package/dist-server/server/modules/system/system.routes.js +16 -0
  232. package/dist-server/server/modules/system/system.routes.js.map +1 -0
  233. package/dist-server/server/modules/system/system.service.js +45 -0
  234. package/dist-server/server/modules/system/system.service.js.map +1 -0
  235. package/dist-server/server/modules/system/tests/system.service.test.js +100 -0
  236. package/dist-server/server/modules/system/tests/system.service.test.js.map +1 -0
  237. package/dist-server/server/modules/taskmaster/index.js +3 -0
  238. package/dist-server/server/modules/taskmaster/index.js.map +1 -0
  239. package/dist-server/server/modules/taskmaster/taskmaster.module.js +20 -0
  240. package/dist-server/server/modules/taskmaster/taskmaster.module.js.map +1 -0
  241. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +1372 -0
  242. package/dist-server/server/modules/taskmaster/taskmaster.routes.js.map +1 -0
  243. package/dist-server/server/modules/taskmaster/taskmaster.service.js +107 -0
  244. package/dist-server/server/modules/taskmaster/taskmaster.service.js.map +1 -0
  245. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js +132 -0
  246. package/dist-server/server/modules/taskmaster/tests/taskmaster.routes.test.js.map +1 -0
  247. package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js +85 -0
  248. package/dist-server/server/modules/taskmaster/tests/taskmaster.service.test.js.map +1 -0
  249. package/dist-server/server/modules/user/index.js +3 -0
  250. package/dist-server/server/modules/user/index.js.map +1 -0
  251. package/dist-server/server/modules/user/tests/user.service.test.js +53 -0
  252. package/dist-server/server/modules/user/tests/user.service.test.js.map +1 -0
  253. package/dist-server/server/modules/user/user.module.js +47 -0
  254. package/dist-server/server/modules/user/user.module.js.map +1 -0
  255. package/dist-server/server/modules/user/user.routes.js +44 -0
  256. package/dist-server/server/modules/user/user.routes.js.map +1 -0
  257. package/dist-server/server/modules/user/user.service.js +59 -0
  258. package/dist-server/server/modules/user/user.service.js.map +1 -0
  259. package/dist-server/server/modules/voice/index.js +3 -0
  260. package/dist-server/server/modules/voice/index.js.map +1 -0
  261. package/dist-server/server/modules/voice/tests/voice.service.test.js +86 -0
  262. package/dist-server/server/modules/voice/tests/voice.service.test.js.map +1 -0
  263. package/dist-server/server/modules/voice/voice.module.js +44 -0
  264. package/dist-server/server/modules/voice/voice.module.js.map +1 -0
  265. package/dist-server/server/modules/voice/voice.routes.js +87 -0
  266. package/dist-server/server/modules/voice/voice.routes.js.map +1 -0
  267. package/dist-server/server/modules/voice/voice.service.js +148 -0
  268. package/dist-server/server/modules/voice/voice.service.js.map +1 -0
  269. package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +2 -2
  270. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +44 -29
  271. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  272. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +76 -8
  273. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  274. package/dist-server/server/modules/websocket/services/websocket-server.service.js +51 -20
  275. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
  276. package/dist-server/server/modules/websocket/tests/{chat-image-filter.test.js → chat-attachment-filter.test.js} +10 -3
  277. package/dist-server/server/modules/websocket/tests/chat-attachment-filter.test.js.map +1 -0
  278. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js +97 -0
  279. package/dist-server/server/modules/websocket/tests/shell-websocket.service.test.js.map +1 -0
  280. package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js +61 -0
  281. package/dist-server/server/modules/websocket/tests/websocket-heartbeat.service.test.js.map +1 -0
  282. package/dist-server/server/modules/worktrees/index.js +3 -0
  283. package/dist-server/server/modules/worktrees/index.js.map +1 -0
  284. package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js +37 -0
  285. package/dist-server/server/modules/worktrees/services/worktree-create-and-open.service.js.map +1 -0
  286. package/dist-server/server/modules/worktrees/services/worktree-create.service.js +67 -0
  287. package/dist-server/server/modules/worktrees/services/worktree-create.service.js.map +1 -0
  288. package/dist-server/server/modules/worktrees/services/worktree-git.service.js +172 -0
  289. package/dist-server/server/modules/worktrees/services/worktree-git.service.js.map +1 -0
  290. package/dist-server/server/modules/worktrees/services/worktree-list.service.js +91 -0
  291. package/dist-server/server/modules/worktrees/services/worktree-list.service.js.map +1 -0
  292. package/dist-server/server/modules/worktrees/services/worktree-merge.service.js +118 -0
  293. package/dist-server/server/modules/worktrees/services/worktree-merge.service.js.map +1 -0
  294. package/dist-server/server/modules/worktrees/services/worktree-open.service.js +58 -0
  295. package/dist-server/server/modules/worktrees/services/worktree-open.service.js.map +1 -0
  296. package/dist-server/server/modules/worktrees/services/worktree-remove.service.js +65 -0
  297. package/dist-server/server/modules/worktrees/services/worktree-remove.service.js.map +1 -0
  298. package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js +58 -0
  299. package/dist-server/server/modules/worktrees/tests/worktree-create-and-open.service.test.js.map +1 -0
  300. package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js +85 -0
  301. package/dist-server/server/modules/worktrees/tests/worktree-create.service.test.js.map +1 -0
  302. package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js +58 -0
  303. package/dist-server/server/modules/worktrees/tests/worktree-git.service.test.js.map +1 -0
  304. package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js +34 -0
  305. package/dist-server/server/modules/worktrees/tests/worktree-list.service.test.js.map +1 -0
  306. package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js +137 -0
  307. package/dist-server/server/modules/worktrees/tests/worktree-merge.service.test.js.map +1 -0
  308. package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js +99 -0
  309. package/dist-server/server/modules/worktrees/tests/worktree-open.service.test.js.map +1 -0
  310. package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js +157 -0
  311. package/dist-server/server/modules/worktrees/tests/worktree-remove.service.test.js.map +1 -0
  312. package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js +167 -0
  313. package/dist-server/server/modules/worktrees/tests/worktrees.routes.test.js.map +1 -0
  314. package/dist-server/server/modules/worktrees/worktrees.module.js +97 -0
  315. package/dist-server/server/modules/worktrees/worktrees.module.js.map +1 -0
  316. package/dist-server/server/modules/worktrees/worktrees.routes.js +84 -0
  317. package/dist-server/server/modules/worktrees/worktrees.routes.js.map +1 -0
  318. package/dist-server/server/shared/image-attachments.js +90 -17
  319. package/dist-server/server/shared/image-attachments.js.map +1 -1
  320. package/dist-server/server/shared/tests/image-attachments.test.js +43 -1
  321. package/dist-server/server/shared/tests/image-attachments.test.js.map +1 -1
  322. package/dist-server/server/shared/utils.js +66 -142
  323. package/dist-server/server/shared/utils.js.map +1 -1
  324. package/package.json +9 -5
  325. package/server/index.ts +401 -0
  326. package/server/{load-env.js → load-env.ts} +18 -5
  327. package/server/modules/agent/agent.module.ts +51 -0
  328. package/server/modules/agent/agent.routes.ts +1287 -0
  329. package/server/modules/agent/index.ts +2 -0
  330. package/server/modules/agent/tests/agent.routes.test.ts +205 -0
  331. package/server/modules/assets/assets.routes.ts +63 -16
  332. package/server/modules/assets/index.ts +2 -2
  333. package/server/modules/assets/services/image-assets.service.ts +46 -1
  334. package/server/modules/assets/tests/image-assets.service.test.ts +28 -0
  335. package/server/{middleware/auth.js → modules/auth/auth.middleware.ts} +38 -7
  336. package/server/modules/auth/auth.module.ts +38 -0
  337. package/server/modules/auth/auth.routes.ts +57 -0
  338. package/server/modules/auth/auth.service.ts +155 -0
  339. package/server/modules/auth/index.ts +9 -0
  340. package/server/modules/auth/tests/auth.service.test.ts +95 -0
  341. package/server/{browser-use-mcp.ts → modules/browser-use/browser-use-mcp.ts} +3 -1
  342. package/server/modules/browser-use/browser-use.service.ts +3 -4
  343. package/server/modules/browser-use/index.ts +8 -0
  344. package/server/modules/cli/cli.module.ts +96 -0
  345. package/server/modules/cli/cli.service.ts +281 -0
  346. package/server/modules/cli/cli.ts +13 -0
  347. package/server/modules/cli/index.ts +2 -0
  348. package/server/modules/cli/sandbox.service.ts +413 -0
  349. package/server/modules/cli/tests/cli.service.test.ts +91 -0
  350. package/server/modules/cli/tests/sandbox.service.test.ts +61 -0
  351. package/server/modules/commands/commands.module.ts +22 -0
  352. package/server/{routes/commands.js → modules/commands/commands.routes.ts} +46 -38
  353. package/server/modules/commands/index.ts +2 -0
  354. package/server/modules/commands/tests/commands.test.ts +114 -0
  355. package/server/modules/database/index.ts +3 -0
  356. package/server/modules/database/migrations.ts +35 -0
  357. package/server/modules/database/repositories/provider-models.ts +168 -0
  358. package/server/modules/database/repositories/sessions.db.ts +103 -8
  359. package/server/modules/database/schema.ts +30 -0
  360. package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
  361. package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -1
  362. package/server/modules/database/tests/sessions.db.integration.test.ts +32 -0
  363. package/server/modules/file-tree/file-tree.module.ts +116 -0
  364. package/server/modules/file-tree/file-tree.routes.ts +258 -0
  365. package/server/modules/file-tree/file-tree.service.ts +664 -0
  366. package/server/modules/file-tree/index.ts +2 -0
  367. package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -0
  368. package/server/modules/file-tree/tests/file-tree.service.test.ts +317 -0
  369. package/server/modules/git/git-parsing.service.ts +66 -0
  370. package/server/modules/git/git.module.ts +22 -0
  371. package/server/{routes/git.js → modules/git/git.routes.ts} +91 -132
  372. package/server/modules/git/index.ts +2 -0
  373. package/server/modules/git/tests/git-init.routes.test.ts +52 -0
  374. package/server/{routes/git.test.js → modules/git/tests/git.test.ts} +3 -3
  375. package/server/modules/notifications/index.ts +9 -0
  376. package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -0
  377. package/server/{services/vapid-keys.js → modules/notifications/vapid-keys.service.ts} +3 -1
  378. package/server/modules/plugins/index.ts +9 -0
  379. package/server/{utils/plugin-process-manager.js → modules/plugins/plugin-process.service.ts} +3 -1
  380. package/server/{utils/plugin-loader.js → modules/plugins/plugin-registry.service.ts} +1 -0
  381. package/server/modules/plugins/plugins.module.ts +33 -0
  382. package/server/modules/plugins/plugins.routes.ts +66 -0
  383. package/server/modules/plugins/plugins.service.ts +147 -0
  384. package/server/modules/plugins/tests/plugins.service.test.ts +32 -0
  385. package/server/modules/projects/index.ts +5 -0
  386. package/server/modules/providers/README.md +41 -9
  387. package/server/modules/providers/index.ts +4 -0
  388. package/server/modules/providers/list/claude/claude-auth.provider.ts +8 -0
  389. package/server/modules/providers/list/claude/claude-models.provider.ts +47 -26
  390. package/server/{claude-sdk.js → modules/providers/list/claude/claude-runtime.provider.js} +73 -59
  391. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +6 -4
  392. package/server/modules/providers/list/claude/claude-sessions.provider.ts +27 -4
  393. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  394. package/server/modules/providers/list/codex/codex-models.provider.ts +58 -108
  395. package/server/{openai-codex.js → modules/providers/list/codex/codex-runtime.provider.js} +54 -36
  396. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +1 -60
  397. package/server/modules/providers/list/codex/codex-sessions.provider.ts +331 -7
  398. package/server/modules/providers/list/codex/codex-skills.provider.ts +5 -0
  399. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  400. package/server/modules/providers/list/cursor/cursor-models.provider.ts +32 -719
  401. package/server/{cursor-cli.js → modules/providers/list/cursor/cursor-runtime.provider.js} +58 -27
  402. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +26 -15
  403. package/server/modules/providers/list/cursor/cursor.provider.ts +3 -0
  404. package/server/modules/providers/list/opencode/opencode-models.provider.ts +112 -423
  405. package/server/{opencode-cli.js → modules/providers/list/opencode/opencode-runtime.provider.js} +56 -28
  406. package/server/{opencode-cli.test.js → modules/providers/list/opencode/opencode-runtime.provider.test.js} +41 -3
  407. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +0 -13
  408. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +13 -5
  409. package/server/modules/providers/list/opencode/opencode.provider.ts +3 -0
  410. package/server/modules/providers/provider.routes.ts +228 -39
  411. package/server/modules/providers/services/provider-capabilities.service.ts +7 -1
  412. package/server/modules/providers/services/provider-models.service.ts +312 -269
  413. package/server/modules/providers/services/provider-runtime.service.ts +108 -0
  414. package/server/modules/providers/services/provider-token-usage.service.ts +357 -0
  415. package/server/modules/providers/services/session-conversations-search.service.ts +12 -4
  416. package/server/modules/providers/services/sessions-watcher.service.ts +2 -0
  417. package/server/modules/providers/services/sessions.service.ts +166 -3
  418. package/server/modules/providers/shared/base/abstract.provider.ts +4 -3
  419. package/server/modules/providers/tests/claude-auth.test.ts +150 -0
  420. package/server/modules/providers/tests/claude-sessions.test.ts +61 -0
  421. package/server/modules/providers/tests/codex-sessions.test.ts +83 -4
  422. package/server/modules/providers/tests/opencode-models.test.ts +34 -135
  423. package/server/modules/providers/tests/opencode-sessions.test.ts +5 -5
  424. package/server/modules/providers/tests/{provider-image-history.test.ts → provider-attachment-history.test.ts} +68 -1
  425. package/server/modules/providers/tests/provider-models.service.test.ts +300 -296
  426. package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -0
  427. package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -0
  428. package/server/modules/providers/tests/provider.routes.test.ts +183 -0
  429. package/server/modules/providers/tests/sessions-details.test.ts +73 -0
  430. package/server/modules/providers/tests/sessions.service.test.ts +127 -0
  431. package/server/modules/providers/tests/skills.test.ts +9 -2
  432. package/server/modules/settings/index.ts +2 -0
  433. package/server/modules/settings/settings.module.ts +50 -0
  434. package/server/modules/settings/settings.routes.ts +51 -0
  435. package/server/modules/settings/settings.service.ts +186 -0
  436. package/server/modules/settings/tests/settings.service.test.ts +54 -0
  437. package/server/modules/system/index.ts +2 -0
  438. package/server/modules/system/system.module.ts +62 -0
  439. package/server/modules/system/system.routes.ts +21 -0
  440. package/server/modules/system/system.service.ts +76 -0
  441. package/server/modules/system/tests/system.service.test.ts +118 -0
  442. package/server/modules/taskmaster/index.ts +2 -0
  443. package/server/modules/taskmaster/taskmaster.module.ts +24 -0
  444. package/server/modules/taskmaster/taskmaster.routes.ts +1482 -0
  445. package/server/modules/taskmaster/taskmaster.service.ts +132 -0
  446. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -0
  447. package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -0
  448. package/server/modules/user/index.ts +2 -0
  449. package/server/modules/user/tests/user.service.test.ts +61 -0
  450. package/server/modules/user/user.module.ts +58 -0
  451. package/server/modules/user/user.routes.ts +50 -0
  452. package/server/modules/user/user.service.ts +85 -0
  453. package/server/modules/voice/index.ts +2 -0
  454. package/server/modules/voice/tests/voice.service.test.ts +98 -0
  455. package/server/modules/voice/voice.module.ts +47 -0
  456. package/server/modules/voice/voice.routes.ts +113 -0
  457. package/server/modules/voice/voice.service.ts +192 -0
  458. package/server/modules/websocket/README.md +7 -7
  459. package/server/modules/websocket/services/chat-session-writer.service.ts +2 -2
  460. package/server/modules/websocket/services/chat-websocket.service.ts +77 -61
  461. package/server/modules/websocket/services/shell-websocket.service.ts +88 -12
  462. package/server/modules/websocket/services/websocket-server.service.ts +63 -19
  463. package/server/modules/websocket/tests/{chat-image-filter.test.ts → chat-attachment-filter.test.ts} +17 -2
  464. package/server/modules/websocket/tests/shell-websocket.service.test.ts +119 -0
  465. package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -0
  466. package/server/modules/worktrees/index.ts +2 -0
  467. package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -0
  468. package/server/modules/worktrees/services/worktree-create.service.ts +104 -0
  469. package/server/modules/worktrees/services/worktree-git.service.ts +207 -0
  470. package/server/modules/worktrees/services/worktree-list.service.ts +143 -0
  471. package/server/modules/worktrees/services/worktree-merge.service.ts +157 -0
  472. package/server/modules/worktrees/services/worktree-open.service.ts +85 -0
  473. package/server/modules/worktrees/services/worktree-remove.service.ts +88 -0
  474. package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -0
  475. package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -0
  476. package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -0
  477. package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -0
  478. package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -0
  479. package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -0
  480. package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -0
  481. package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -0
  482. package/server/modules/worktrees/worktrees.module.ts +113 -0
  483. package/server/modules/worktrees/worktrees.routes.ts +116 -0
  484. package/server/shared/image-attachments.ts +112 -18
  485. package/server/shared/interfaces.ts +33 -25
  486. package/server/shared/tests/image-attachments.test.ts +55 -0
  487. package/server/shared/types.ts +685 -36
  488. package/server/shared/utils.ts +77 -212
  489. package/dist/assets/index-CcCsKitq.js +0 -1423
  490. package/dist/assets/index-DLmhQ15L.css +0 -32
  491. package/dist-server/server/browser-use-mcp.js.map +0 -1
  492. package/dist-server/server/claude-sdk.js.map +0 -1
  493. package/dist-server/server/cli.js +0 -647
  494. package/dist-server/server/cli.js.map +0 -1
  495. package/dist-server/server/cursor-cli.js.map +0 -1
  496. package/dist-server/server/middleware/auth.js.map +0 -1
  497. package/dist-server/server/modules/providers/tests/provider-image-history.test.js.map +0 -1
  498. package/dist-server/server/modules/websocket/tests/chat-image-filter.test.js.map +0 -1
  499. package/dist-server/server/openai-codex.js.map +0 -1
  500. package/dist-server/server/opencode-cli.js.map +0 -1
  501. package/dist-server/server/opencode-cli.test.js.map +0 -1
  502. package/dist-server/server/routes/agent.js +0 -1155
  503. package/dist-server/server/routes/agent.js.map +0 -1
  504. package/dist-server/server/routes/auth.js +0 -119
  505. package/dist-server/server/routes/auth.js.map +0 -1
  506. package/dist-server/server/routes/commands.js +0 -492
  507. package/dist-server/server/routes/commands.js.map +0 -1
  508. package/dist-server/server/routes/cursor.js +0 -49
  509. package/dist-server/server/routes/cursor.js.map +0 -1
  510. package/dist-server/server/routes/git.js +0 -1389
  511. package/dist-server/server/routes/git.js.map +0 -1
  512. package/dist-server/server/routes/git.test.js.map +0 -1
  513. package/dist-server/server/routes/mcp-utils.js +0 -29
  514. package/dist-server/server/routes/mcp-utils.js.map +0 -1
  515. package/dist-server/server/routes/plugins.js +0 -266
  516. package/dist-server/server/routes/plugins.js.map +0 -1
  517. package/dist-server/server/routes/settings.js +0 -249
  518. package/dist-server/server/routes/settings.js.map +0 -1
  519. package/dist-server/server/routes/taskmaster.js +0 -1362
  520. package/dist-server/server/routes/taskmaster.js.map +0 -1
  521. package/dist-server/server/routes/tests/commands.test.js +0 -76
  522. package/dist-server/server/routes/tests/commands.test.js.map +0 -1
  523. package/dist-server/server/routes/user.js +0 -116
  524. package/dist-server/server/routes/user.js.map +0 -1
  525. package/dist-server/server/services/notification-orchestrator.js +0 -2
  526. package/dist-server/server/services/notification-orchestrator.js.map +0 -1
  527. package/dist-server/server/services/tests/notification-orchestrator.test.js +0 -64
  528. package/dist-server/server/services/tests/notification-orchestrator.test.js.map +0 -1
  529. package/dist-server/server/services/vapid-keys.js.map +0 -1
  530. package/dist-server/server/utils/colors.js +0 -20
  531. package/dist-server/server/utils/colors.js.map +0 -1
  532. package/dist-server/server/utils/commandParser.js +0 -255
  533. package/dist-server/server/utils/commandParser.js.map +0 -1
  534. package/dist-server/server/utils/gitConfig.js +0 -37
  535. package/dist-server/server/utils/gitConfig.js.map +0 -1
  536. package/dist-server/server/utils/mcp-detector.js +0 -134
  537. package/dist-server/server/utils/mcp-detector.js.map +0 -1
  538. package/dist-server/server/utils/plugin-loader.js.map +0 -1
  539. package/dist-server/server/utils/plugin-process-manager.js.map +0 -1
  540. package/dist-server/server/utils/runtime-paths.js +0 -30
  541. package/dist-server/server/utils/runtime-paths.js.map +0 -1
  542. package/dist-server/server/utils/taskmaster-websocket.js +0 -124
  543. package/dist-server/server/utils/taskmaster-websocket.js.map +0 -1
  544. package/dist-server/server/utils/url-detection.js +0 -58
  545. package/dist-server/server/utils/url-detection.js.map +0 -1
  546. package/dist-server/server/voice-proxy.js +0 -226
  547. package/dist-server/server/voice-proxy.js.map +0 -1
  548. package/server/cli.js +0 -695
  549. package/server/index.js +0 -1651
  550. package/server/routes/agent.js +0 -1257
  551. package/server/routes/auth.js +0 -137
  552. package/server/routes/cursor.js +0 -52
  553. package/server/routes/mcp-utils.js +0 -31
  554. package/server/routes/plugins.js +0 -307
  555. package/server/routes/settings.js +0 -282
  556. package/server/routes/taskmaster.js +0 -1470
  557. package/server/routes/tests/commands.test.js +0 -82
  558. package/server/routes/user.js +0 -124
  559. package/server/services/notification-orchestrator.js +0 -7
  560. package/server/services/tests/notification-orchestrator.test.js +0 -80
  561. package/server/utils/colors.js +0 -21
  562. package/server/utils/commandParser.js +0 -305
  563. package/server/utils/gitConfig.js +0 -35
  564. package/server/utils/mcp-detector.js +0 -147
  565. package/server/utils/runtime-paths.js +0 -37
  566. package/server/utils/taskmaster-websocket.js +0 -135
  567. package/server/utils/url-detection.js +0 -71
  568. package/server/voice-proxy.js +0 -224
  569. /package/server/constants/{config.js → config.ts} +0 -0
package/server/index.js DELETED
@@ -1,1651 +0,0 @@
1
- #!/usr/bin/env node
2
- // Load environment variables before other imports execute
3
- import './load-env.js';
4
- import fs, { promises as fsPromises } from 'fs';
5
- import path from 'path';
6
- import os from 'os';
7
- import http from 'http';
8
-
9
- // cross-spawn is a drop-in for child_process.spawn that resolves .cmd
10
- // shims/PATHEXT on Windows and delegates to the native spawn elsewhere.
11
- import spawn from 'cross-spawn';
12
- import express from 'express';
13
- import cors from 'cors';
14
- import mime from 'mime-types';
15
- import Database from 'better-sqlite3';
16
-
17
- import { AppError, WORKSPACES_ROOT, getOpenCodeDatabasePath, validateWorkspacePath } from '@/shared/utils.js';
18
- import { closeSessionsWatcher, initializeSessionsWatcher } from '@/modules/providers/index.js';
19
- import { createWebSocketServer } from '@/modules/websocket/index.js';
20
-
21
- import { getConnectableHost } from '../shared/networkHosts.js';
22
-
23
- import { findAppRoot, getModuleDir } from './utils/runtime-paths.js';
24
- import {
25
- queryClaudeSDK,
26
- abortClaudeSDKSession,
27
- resolveToolApproval,
28
- getPendingApprovalsForSession,
29
- } from './claude-sdk.js';
30
- import {
31
- spawnCursor,
32
- abortCursorSession,
33
- } from './cursor-cli.js';
34
- import {
35
- queryCodex,
36
- abortCodexSession,
37
- } from './openai-codex.js';
38
- import {
39
- spawnOpenCode,
40
- abortOpenCodeSession,
41
- } from './opencode-cli.js';
42
- import {
43
- stripAnsiSequences,
44
- normalizeDetectedUrl,
45
- extractUrlsFromText,
46
- shouldAutoOpenUrlFromOutput,
47
- } from './utils/url-detection.js';
48
- import gitRoutes from './routes/git.js';
49
- import authRoutes from './routes/auth.js';
50
- import cursorRoutes from './routes/cursor.js';
51
- import taskmasterRoutes from './routes/taskmaster.js';
52
- import mcpUtilsRoutes from './routes/mcp-utils.js';
53
- import commandsRoutes from './routes/commands.js';
54
- import settingsRoutes from './routes/settings.js';
55
- import agentRoutes from './routes/agent.js';
56
- import projectModuleRoutes from './modules/projects/projects.routes.js';
57
- import notificationRoutes from './modules/notifications/notifications.routes.js';
58
- import userRoutes from './routes/user.js';
59
- import pluginsRoutes from './routes/plugins.js';
60
- import providerRoutes from './modules/providers/provider.routes.js';
61
- import voiceRoutes from './voice-proxy.js';
62
- import browserUseRoutes from './modules/browser-use/browser-use.routes.js';
63
- import { assetsRoutes } from './modules/assets/index.js';
64
- import browserUseMcpRoutes from './modules/browser-use/browser-use-mcp.routes.js';
65
- import { browserUseService } from './modules/browser-use/browser-use.service.js';
66
- import { startEnabledPluginServers, stopAllPlugins, getPluginPort } from './utils/plugin-process-manager.js';
67
- import { initializeDatabase, projectsDb, sessionsDb } from './modules/database/index.js';
68
- import { configureWebPush } from './services/vapid-keys.js';
69
- import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
70
- import { IS_PLATFORM } from './constants/config.js';
71
- import { c } from './utils/colors.js';
72
-
73
- const __dirname = getModuleDir(import.meta.url);
74
- // The server source runs from /server, while the compiled output runs from /dist-server/server.
75
- // Resolving the app root once keeps every repo-level lookup below aligned across both layouts.
76
- const APP_ROOT = findAppRoot(__dirname);
77
- const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm';
78
- // Version of the code that is actually running, captured once at process
79
- // startup. This intentionally does NOT re-read package.json per request: after
80
- // an update replaces the files on disk, package.json reflects the NEW version
81
- // while this long-lived process still runs the OLD code. The frontend bundle is
82
- // rebuilt on update, so a mismatch between this value and the frontend's
83
- // build-time version means the server was updated but not restarted.
84
- const RUNNING_VERSION = (() => {
85
- try {
86
- return JSON.parse(fs.readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8')).version || null;
87
- } catch {
88
- return null;
89
- }
90
- })();
91
- const MAX_FILE_UPLOAD_SIZE_MB = 200;
92
- const MAX_FILE_UPLOAD_SIZE_BYTES = MAX_FILE_UPLOAD_SIZE_MB * 1024 * 1024;
93
- const MAX_FILE_UPLOAD_COUNT = 20;
94
-
95
- console.log('SERVER_PORT from env:', process.env.SERVER_PORT);
96
-
97
- function readUsageNumber(value) {
98
- const parsed = Number(value);
99
- return Number.isFinite(parsed) ? parsed : 0;
100
- }
101
-
102
- const app = express();
103
- const server = http.createServer(app);
104
-
105
- // Single WebSocket server that handles chat, shell, and plugin proxy paths.
106
- const wss = createWebSocketServer(server, {
107
- verifyClient: {
108
- isPlatform: IS_PLATFORM,
109
- authenticateWebSocket,
110
- },
111
- chat: {
112
- spawnFns: {
113
- claude: queryClaudeSDK,
114
- cursor: spawnCursor,
115
- codex: queryCodex,
116
- opencode: spawnOpenCode,
117
- },
118
- abortFns: {
119
- claude: abortClaudeSDKSession,
120
- cursor: abortCursorSession,
121
- codex: abortCodexSession,
122
- opencode: abortOpenCodeSession,
123
- },
124
- resolveToolApproval,
125
- getPendingApprovalsForSession,
126
- },
127
- shell: {
128
- resolveProviderSessionId: (sessionId, provider) => {
129
- const dbSession = sessionsDb.getSessionById(sessionId);
130
- if (dbSession) {
131
- return dbSession.provider_session_id ?? null;
132
- }
133
-
134
- return null;
135
- },
136
- stripAnsiSequences,
137
- normalizeDetectedUrl,
138
- extractUrlsFromText,
139
- shouldAutoOpenUrlFromOutput,
140
- },
141
- getPluginPort,
142
- });
143
-
144
- // Make WebSocket server available to routes
145
- app.locals.wss = wss;
146
-
147
- app.use(cors({ exposedHeaders: ['X-Refreshed-Token'] }));
148
- app.use(express.json({
149
- limit: '50mb',
150
- type: (req) => {
151
- // Skip multipart/form-data requests (for file uploads like images)
152
- const contentType = req.headers['content-type'] || '';
153
- if (contentType.includes('multipart/form-data')) {
154
- return false;
155
- }
156
- return contentType.includes('json');
157
- }
158
- }));
159
- app.use(express.urlencoded({ limit: '50mb', extended: true }));
160
-
161
- // Public health check endpoint (no authentication required)
162
- app.get('/health', (req, res) => {
163
- res.json({
164
- status: 'ok',
165
- timestamp: new Date().toISOString(),
166
- installMode,
167
- version: RUNNING_VERSION
168
- });
169
- });
170
-
171
- // Optional API key validation (if configured)
172
- app.use('/api', validateApiKey);
173
-
174
- // Authentication routes (public)
175
- app.use('/api/auth', authRoutes);
176
-
177
- // Projects API Routes (protected)
178
- app.use('/api/projects', authenticateToken, projectModuleRoutes);
179
-
180
- // Chat image asset upload/serving (global ~/.cloudcli/assets store, protected)
181
- app.use('/api/assets', authenticateToken, assetsRoutes);
182
-
183
- // Git API Routes (protected)
184
- app.use('/api/git', authenticateToken, gitRoutes);
185
-
186
- // Cursor API Routes (protected)
187
- app.use('/api/cursor', authenticateToken, cursorRoutes);
188
-
189
- // TaskMaster API Routes (protected)
190
- app.use('/api/taskmaster', authenticateToken, taskmasterRoutes);
191
-
192
- // MCP utilities
193
- app.use('/api/mcp-utils', authenticateToken, mcpUtilsRoutes);
194
-
195
- // Commands API Routes (protected)
196
- app.use('/api/commands', authenticateToken, commandsRoutes);
197
-
198
- // Settings API Routes (protected)
199
- app.use('/api/settings', authenticateToken, settingsRoutes);
200
-
201
- app.use('/api/notifications', authenticateToken, notificationRoutes);
202
-
203
- // User API Routes (protected)
204
- app.use('/api/user', authenticateToken, userRoutes);
205
-
206
- // Plugins API Routes (protected)
207
- app.use('/api/plugins', authenticateToken, pluginsRoutes);
208
-
209
- // Browser MCP bridge API (local token protected)
210
- app.use('/api/browser-use-mcp', browserUseMcpRoutes);
211
-
212
- // Browser API Routes (protected)
213
- app.use('/api/browser-use', authenticateToken, browserUseRoutes);
214
-
215
- // Unified provider MCP routes (protected)
216
- app.use('/api/providers', authenticateToken, providerRoutes);
217
-
218
- // Agent API Routes (uses API key authentication)
219
- app.use('/api/agent', agentRoutes);
220
-
221
- app.use('/api/voice', authenticateToken, voiceRoutes);
222
-
223
- // Serve public files (like api-docs.html)
224
- app.use(express.static(path.join(APP_ROOT, 'public')));
225
-
226
- // Static files served after API routes
227
- // Add cache control: HTML files should not be cached, but assets can be cached
228
- app.use(express.static(path.join(APP_ROOT, 'dist'), {
229
- setHeaders: (res, filePath) => {
230
- if (filePath.endsWith('.html')) {
231
- // Prevent HTML caching to avoid service worker issues after builds
232
- res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
233
- res.setHeader('Pragma', 'no-cache');
234
- res.setHeader('Expires', '0');
235
- } else if (filePath.match(/\.(js|css|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|ico)$/)) {
236
- // Cache static assets for 1 year (they have hashed names)
237
- res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
238
- }
239
- }
240
- }));
241
-
242
- // API Routes (protected)
243
- // /api/config endpoint removed - no longer needed
244
- // Frontend now uses window.location for WebSocket URLs
245
-
246
- // System update endpoint
247
- app.post('/api/system/update', authenticateToken, async (req, res) => {
248
- try {
249
- // Get the project root directory (parent of server directory)
250
- const projectRoot = APP_ROOT;
251
-
252
- console.log('Starting system update from directory:', projectRoot);
253
-
254
- // Platform deployments use their own update workflow from the project root.
255
- const updateCommand = IS_PLATFORM
256
- // In platform, husky and dev dependencies are not needed
257
- ? 'npm run update:platform'
258
- : installMode === 'git'
259
- ? 'git checkout main && git pull && npm install'
260
- : 'npm install -g @cloudcli-ai/cloudcli@latest';
261
-
262
- const updateCwd = IS_PLATFORM || installMode === 'git'
263
- ? projectRoot
264
- : os.homedir();
265
-
266
- const child = spawn('sh', ['-c', updateCommand], {
267
- cwd: updateCwd,
268
- env: process.env
269
- });
270
-
271
- let output = '';
272
- let errorOutput = '';
273
-
274
- child.stdout.on('data', (data) => {
275
- const text = data.toString();
276
- output += text;
277
- console.log('Update output:', text);
278
- });
279
-
280
- child.stderr.on('data', (data) => {
281
- const text = data.toString();
282
- errorOutput += text;
283
- console.error('Update error:', text);
284
- });
285
-
286
- child.on('close', (code) => {
287
- if (code === 0) {
288
- res.json({
289
- success: true,
290
- output: output || 'Update completed successfully',
291
- message: 'Update completed. Please restart the server to apply changes.'
292
- });
293
- } else {
294
- res.status(500).json({
295
- success: false,
296
- error: 'Update command failed',
297
- output: output,
298
- errorOutput: errorOutput
299
- });
300
- }
301
- });
302
-
303
- child.on('error', (error) => {
304
- console.error('Update process error:', error);
305
- res.status(500).json({
306
- success: false,
307
- error: error.message
308
- });
309
- });
310
-
311
- } catch (error) {
312
- console.error('System update error:', error);
313
- res.status(500).json({
314
- success: false,
315
- error: error.message
316
- });
317
- }
318
- });
319
-
320
- const expandWorkspacePath = (inputPath) => {
321
- if (!inputPath) return inputPath;
322
- if (inputPath === '~') {
323
- return WORKSPACES_ROOT;
324
- }
325
- if (inputPath.startsWith('~/') || inputPath.startsWith('~\\')) {
326
- return path.join(WORKSPACES_ROOT, inputPath.slice(2));
327
- }
328
- return inputPath;
329
- };
330
-
331
- // Browse filesystem endpoint for project suggestions - uses existing getFileTree
332
- app.get('/api/browse-filesystem', authenticateToken, async (req, res) => {
333
- try {
334
- const { path: dirPath } = req.query;
335
-
336
- console.log('[API] Browse filesystem request for path:', dirPath);
337
- console.log('[API] WORKSPACES_ROOT is:', WORKSPACES_ROOT);
338
- // Default to home directory if no path provided
339
- const defaultRoot = WORKSPACES_ROOT;
340
- let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
341
-
342
- // Resolve and normalize the path
343
- targetPath = path.resolve(targetPath);
344
-
345
- // Security check - ensure path is within allowed workspace root
346
- const validation = await validateWorkspacePath(targetPath);
347
- if (!validation.valid) {
348
- return res.status(403).json({ error: validation.error });
349
- }
350
- const resolvedPath = validation.resolvedPath || targetPath;
351
-
352
- // Security check - ensure path is accessible
353
- try {
354
- await fs.promises.access(resolvedPath);
355
- const stats = await fs.promises.stat(resolvedPath);
356
-
357
- if (!stats.isDirectory()) {
358
- return res.status(400).json({ error: 'Path is not a directory' });
359
- }
360
- } catch (err) {
361
- return res.status(404).json({ error: 'Directory not accessible' });
362
- }
363
-
364
- // Use existing getFileTree function with shallow depth (only direct children)
365
- const fileTree = await getFileTree(resolvedPath, 1, 0, false); // maxDepth=1, showHidden=false
366
-
367
- // Filter only directories and format for suggestions
368
- const directories = fileTree
369
- .filter(item => item.type === 'directory')
370
- .map(item => ({
371
- path: item.path,
372
- name: item.name,
373
- type: 'directory'
374
- }))
375
- .sort((a, b) => {
376
- const aHidden = a.name.startsWith('.');
377
- const bHidden = b.name.startsWith('.');
378
- if (aHidden && !bHidden) return 1;
379
- if (!aHidden && bHidden) return -1;
380
- return a.name.localeCompare(b.name);
381
- });
382
-
383
- // Add common directories if browsing home directory
384
- const suggestions = [];
385
- let resolvedWorkspaceRoot = defaultRoot;
386
- try {
387
- resolvedWorkspaceRoot = await fsPromises.realpath(defaultRoot);
388
- } catch (error) {
389
- // Use default root as-is if realpath fails
390
- }
391
- if (resolvedPath === resolvedWorkspaceRoot) {
392
- const commonDirs = ['Desktop', 'Documents', 'Projects', 'Development', 'Dev', 'Code', 'workspace'];
393
- const existingCommon = directories.filter(dir => commonDirs.includes(dir.name));
394
- const otherDirs = directories.filter(dir => !commonDirs.includes(dir.name));
395
-
396
- suggestions.push(...existingCommon, ...otherDirs);
397
- } else {
398
- suggestions.push(...directories);
399
- }
400
-
401
- res.json({
402
- path: resolvedPath,
403
- suggestions: suggestions
404
- });
405
-
406
- } catch (error) {
407
- console.error('Error browsing filesystem:', error);
408
- res.status(500).json({ error: 'Failed to browse filesystem' });
409
- }
410
- });
411
-
412
- app.post('/api/create-folder', authenticateToken, async (req, res) => {
413
- try {
414
- const { path: folderPath } = req.body;
415
- if (!folderPath) {
416
- return res.status(400).json({ error: 'Path is required' });
417
- }
418
- const expandedPath = expandWorkspacePath(folderPath);
419
- const resolvedInput = path.resolve(expandedPath);
420
- const validation = await validateWorkspacePath(resolvedInput);
421
- if (!validation.valid) {
422
- return res.status(403).json({ error: validation.error });
423
- }
424
- const targetPath = validation.resolvedPath || resolvedInput;
425
- const parentDir = path.dirname(targetPath);
426
- try {
427
- await fs.promises.access(parentDir);
428
- } catch (err) {
429
- return res.status(404).json({ error: 'Parent directory does not exist' });
430
- }
431
- try {
432
- await fs.promises.access(targetPath);
433
- return res.status(409).json({ error: 'Folder already exists' });
434
- } catch (err) {
435
- // Folder doesn't exist, which is what we want
436
- }
437
- try {
438
- await fs.promises.mkdir(targetPath, { recursive: false });
439
- res.json({ success: true, path: targetPath });
440
- } catch (mkdirError) {
441
- if (mkdirError.code === 'EEXIST') {
442
- return res.status(409).json({ error: 'Folder already exists' });
443
- }
444
- throw mkdirError;
445
- }
446
- } catch (error) {
447
- console.error('Error creating folder:', error);
448
- res.status(500).json({ error: 'Failed to create folder' });
449
- }
450
- });
451
-
452
- // Read file content endpoint
453
- app.get('/api/projects/:projectId/file', authenticateToken, async (req, res) => {
454
- try {
455
- const { projectId } = req.params;
456
- const { filePath } = req.query;
457
-
458
-
459
- // Security: ensure the requested path is inside the project root
460
- if (!filePath) {
461
- return res.status(400).json({ error: 'Invalid file path' });
462
- }
463
-
464
- // Resolve the absolute project root via the DB-backed helper; the
465
- // caller passes the DB-assigned `projectId`, not a folder name.
466
- const projectRoot = await projectsDb.getProjectPathById(projectId);
467
- if (!projectRoot) {
468
- return res.status(404).json({ error: 'Project not found' });
469
- }
470
-
471
- // Handle both absolute and relative paths
472
- const resolved = path.isAbsolute(filePath)
473
- ? path.resolve(filePath)
474
- : path.resolve(projectRoot, filePath);
475
- const normalizedRoot = path.resolve(projectRoot) + path.sep;
476
- if (!resolved.startsWith(normalizedRoot)) {
477
- return res.status(403).json({ error: 'Path must be under project root' });
478
- }
479
-
480
- const content = await fsPromises.readFile(resolved, 'utf8');
481
- res.json({ content, path: resolved });
482
- } catch (error) {
483
- console.error('Error reading file:', error);
484
- if (error.code === 'ENOENT') {
485
- res.status(404).json({ error: 'File not found' });
486
- } else if (error.code === 'EACCES') {
487
- res.status(403).json({ error: 'Permission denied' });
488
- } else {
489
- res.status(500).json({ error: error.message });
490
- }
491
- }
492
- });
493
-
494
- // Serve raw file bytes for previews and downloads.
495
- app.get('/api/projects/:projectId/files/content', authenticateToken, async (req, res) => {
496
- try {
497
- const { projectId } = req.params;
498
- const { path: filePath } = req.query;
499
-
500
-
501
- // Security: ensure the requested path is inside the project root
502
- if (!filePath) {
503
- return res.status(400).json({ error: 'Invalid file path' });
504
- }
505
-
506
- // Projects are now addressed by DB `projectId`, resolved to their path here.
507
- const projectRoot = await projectsDb.getProjectPathById(projectId);
508
- if (!projectRoot) {
509
- return res.status(404).json({ error: 'Project not found' });
510
- }
511
-
512
- // Match the text reader endpoint so callers can pass either project-relative
513
- // or absolute paths without changing how the bytes are served.
514
- const resolved = path.isAbsolute(filePath)
515
- ? path.resolve(filePath)
516
- : path.resolve(projectRoot, filePath);
517
- const normalizedRoot = path.resolve(projectRoot) + path.sep;
518
- if (!resolved.startsWith(normalizedRoot)) {
519
- return res.status(403).json({ error: 'Path must be under project root' });
520
- }
521
-
522
- // Check if file exists
523
- try {
524
- await fsPromises.access(resolved);
525
- } catch (error) {
526
- return res.status(404).json({ error: 'File not found' });
527
- }
528
-
529
- // Get file extension and set appropriate content type
530
- const mimeType = mime.lookup(resolved) || 'application/octet-stream';
531
- res.setHeader('Content-Type', mimeType);
532
-
533
- // Stream the file
534
- const fileStream = fs.createReadStream(resolved);
535
- fileStream.pipe(res);
536
-
537
- fileStream.on('error', (error) => {
538
- console.error('Error streaming file:', error);
539
- if (!res.headersSent) {
540
- res.status(500).json({ error: 'Error reading file' });
541
- }
542
- });
543
-
544
- } catch (error) {
545
- console.error('Error serving binary file:', error);
546
- if (!res.headersSent) {
547
- res.status(500).json({ error: error.message });
548
- }
549
- }
550
- });
551
-
552
- // Save file content endpoint
553
- app.put('/api/projects/:projectId/file', authenticateToken, async (req, res) => {
554
- try {
555
- const { projectId } = req.params;
556
- const { filePath, content } = req.body;
557
-
558
-
559
- // Security: ensure the requested path is inside the project root
560
- if (!filePath) {
561
- return res.status(400).json({ error: 'Invalid file path' });
562
- }
563
-
564
- if (content === undefined) {
565
- return res.status(400).json({ error: 'Content is required' });
566
- }
567
-
568
- // Projects are now addressed by DB `projectId`, resolved to their path here.
569
- const projectRoot = await projectsDb.getProjectPathById(projectId);
570
- if (!projectRoot) {
571
- return res.status(404).json({ error: 'Project not found' });
572
- }
573
-
574
- // Handle both absolute and relative paths
575
- const resolved = path.isAbsolute(filePath)
576
- ? path.resolve(filePath)
577
- : path.resolve(projectRoot, filePath);
578
- const normalizedRoot = path.resolve(projectRoot) + path.sep;
579
- if (!resolved.startsWith(normalizedRoot)) {
580
- return res.status(403).json({ error: 'Path must be under project root' });
581
- }
582
-
583
- // Write the new content
584
- await fsPromises.writeFile(resolved, content, 'utf8');
585
-
586
- res.json({
587
- success: true,
588
- path: resolved,
589
- message: 'File saved successfully'
590
- });
591
- } catch (error) {
592
- console.error('Error saving file:', error);
593
- if (error.code === 'ENOENT') {
594
- res.status(404).json({ error: 'File or directory not found' });
595
- } else if (error.code === 'EACCES') {
596
- res.status(403).json({ error: 'Permission denied' });
597
- } else {
598
- res.status(500).json({ error: error.message });
599
- }
600
- }
601
- });
602
-
603
- app.get('/api/projects/:projectId/files', authenticateToken, async (req, res) => {
604
- try {
605
-
606
- // Using fsPromises from import
607
-
608
- // Resolve the project's absolute path through the DB (projectId is the
609
- // primary key of the `projects` table after the identifier migration).
610
- const actualPath = await projectsDb.getProjectPathById(req.params.projectId);
611
- if (!actualPath) {
612
- return res.status(404).json({ error: 'Project not found' });
613
- }
614
-
615
- // Check if path exists
616
- try {
617
- await fsPromises.access(actualPath);
618
- } catch (e) {
619
- return res.status(404).json({ error: `Project path not found: ${actualPath}` });
620
- }
621
-
622
- const files = await getFileTree(actualPath, 10, 0, true);
623
- res.json(files);
624
- } catch (error) {
625
- console.error('[ERROR] File tree error:', error.message);
626
- res.status(500).json({ error: error.message });
627
- }
628
- });
629
-
630
- // ============================================================================
631
- // FILE OPERATIONS API ENDPOINTS
632
- // ============================================================================
633
-
634
- /**
635
- * Validate that a path is within the project root
636
- * @param {string} projectRoot - The project root path
637
- * @param {string} targetPath - The path to validate
638
- * @returns {{ valid: boolean, resolved?: string, error?: string }}
639
- */
640
- function validatePathInProject(projectRoot, targetPath) {
641
- const resolved = path.isAbsolute(targetPath)
642
- ? path.resolve(targetPath)
643
- : path.resolve(projectRoot, targetPath);
644
- const normalizedRoot = path.resolve(projectRoot) + path.sep;
645
- if (!resolved.startsWith(normalizedRoot)) {
646
- return { valid: false, error: 'Path must be under project root' };
647
- }
648
- return { valid: true, resolved };
649
- }
650
-
651
- /**
652
- * Validate filename - check for invalid characters
653
- * @param {string} name - The filename to validate
654
- * @returns {{ valid: boolean, error?: string }}
655
- */
656
- function validateFilename(name) {
657
- if (!name || !name.trim()) {
658
- return { valid: false, error: 'Filename cannot be empty' };
659
- }
660
- // Check for invalid characters (Windows + Unix)
661
- const invalidChars = /[<>:"/\\|?*\x00-\x1f]/;
662
- if (invalidChars.test(name)) {
663
- return { valid: false, error: 'Filename contains invalid characters' };
664
- }
665
- // Check for reserved names (Windows)
666
- const reserved = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
667
- if (reserved.test(name)) {
668
- return { valid: false, error: 'Filename is a reserved name' };
669
- }
670
- // Check for dots only
671
- if (/^\.+$/.test(name)) {
672
- return { valid: false, error: 'Filename cannot be only dots' };
673
- }
674
- return { valid: true };
675
- }
676
-
677
- // POST /api/projects/:projectId/files/create - Create new file or directory
678
- app.post('/api/projects/:projectId/files/create', authenticateToken, async (req, res) => {
679
- try {
680
- const { projectId } = req.params;
681
- const { path: parentPath, type, name } = req.body;
682
-
683
- // Validate input
684
- if (!name || !type) {
685
- return res.status(400).json({ error: 'Name and type are required' });
686
- }
687
-
688
- if (!['file', 'directory'].includes(type)) {
689
- return res.status(400).json({ error: 'Type must be "file" or "directory"' });
690
- }
691
-
692
- const nameValidation = validateFilename(name);
693
- if (!nameValidation.valid) {
694
- return res.status(400).json({ error: nameValidation.error });
695
- }
696
-
697
- // Resolve the project directory through the DB using the new projectId.
698
- const projectRoot = await projectsDb.getProjectPathById(projectId);
699
- if (!projectRoot) {
700
- return res.status(404).json({ error: 'Project not found' });
701
- }
702
-
703
- // Build and validate target path
704
- const targetDir = parentPath || '';
705
- const targetPath = targetDir ? path.join(targetDir, name) : name;
706
- const validation = validatePathInProject(projectRoot, targetPath);
707
- if (!validation.valid) {
708
- return res.status(403).json({ error: validation.error });
709
- }
710
-
711
- const resolvedPath = validation.resolved;
712
-
713
- // Check if already exists
714
- try {
715
- await fsPromises.access(resolvedPath);
716
- return res.status(409).json({ error: `${type === 'file' ? 'File' : 'Directory'} already exists` });
717
- } catch {
718
- // Doesn't exist, which is what we want
719
- }
720
-
721
- // Create file or directory
722
- if (type === 'directory') {
723
- await fsPromises.mkdir(resolvedPath, { recursive: false });
724
- } else {
725
- // Ensure parent directory exists
726
- const parentDir = path.dirname(resolvedPath);
727
- try {
728
- await fsPromises.access(parentDir);
729
- } catch {
730
- await fsPromises.mkdir(parentDir, { recursive: true });
731
- }
732
- await fsPromises.writeFile(resolvedPath, '', 'utf8');
733
- }
734
-
735
- res.json({
736
- success: true,
737
- path: resolvedPath,
738
- name,
739
- type,
740
- message: `${type === 'file' ? 'File' : 'Directory'} created successfully`
741
- });
742
- } catch (error) {
743
- console.error('Error creating file/directory:', error);
744
- if (error.code === 'EACCES') {
745
- res.status(403).json({ error: 'Permission denied' });
746
- } else if (error.code === 'ENOENT') {
747
- res.status(404).json({ error: 'Parent directory not found' });
748
- } else {
749
- res.status(500).json({ error: error.message });
750
- }
751
- }
752
- });
753
-
754
- // PUT /api/projects/:projectId/files/rename - Rename file or directory
755
- app.put('/api/projects/:projectId/files/rename', authenticateToken, async (req, res) => {
756
- try {
757
- const { projectId } = req.params;
758
- const { oldPath, newName } = req.body;
759
-
760
- // Validate input
761
- if (!oldPath || !newName) {
762
- return res.status(400).json({ error: 'oldPath and newName are required' });
763
- }
764
-
765
- const nameValidation = validateFilename(newName);
766
- if (!nameValidation.valid) {
767
- return res.status(400).json({ error: nameValidation.error });
768
- }
769
-
770
- // Resolve the project directory through the DB using the new projectId.
771
- const projectRoot = await projectsDb.getProjectPathById(projectId);
772
- if (!projectRoot) {
773
- return res.status(404).json({ error: 'Project not found' });
774
- }
775
-
776
- // Validate old path
777
- const oldValidation = validatePathInProject(projectRoot, oldPath);
778
- if (!oldValidation.valid) {
779
- return res.status(403).json({ error: oldValidation.error });
780
- }
781
-
782
- const resolvedOldPath = oldValidation.resolved;
783
-
784
- // Check if old path exists
785
- try {
786
- await fsPromises.access(resolvedOldPath);
787
- } catch {
788
- return res.status(404).json({ error: 'File or directory not found' });
789
- }
790
-
791
- // Build and validate new path
792
- const parentDir = path.dirname(resolvedOldPath);
793
- const resolvedNewPath = path.join(parentDir, newName);
794
- const newValidation = validatePathInProject(projectRoot, resolvedNewPath);
795
- if (!newValidation.valid) {
796
- return res.status(403).json({ error: newValidation.error });
797
- }
798
-
799
- // Check if new path already exists
800
- try {
801
- await fsPromises.access(resolvedNewPath);
802
- return res.status(409).json({ error: 'A file or directory with this name already exists' });
803
- } catch {
804
- // Doesn't exist, which is what we want
805
- }
806
-
807
- // Rename
808
- await fsPromises.rename(resolvedOldPath, resolvedNewPath);
809
-
810
- res.json({
811
- success: true,
812
- oldPath: resolvedOldPath,
813
- newPath: resolvedNewPath,
814
- newName,
815
- message: 'Renamed successfully'
816
- });
817
- } catch (error) {
818
- console.error('Error renaming file/directory:', error);
819
- if (error.code === 'EACCES') {
820
- res.status(403).json({ error: 'Permission denied' });
821
- } else if (error.code === 'ENOENT') {
822
- res.status(404).json({ error: 'File or directory not found' });
823
- } else if (error.code === 'EXDEV') {
824
- res.status(400).json({ error: 'Cannot move across different filesystems' });
825
- } else {
826
- res.status(500).json({ error: error.message });
827
- }
828
- }
829
- });
830
-
831
- // DELETE /api/projects/:projectId/files - Delete file or directory
832
- app.delete('/api/projects/:projectId/files', authenticateToken, async (req, res) => {
833
- try {
834
- const { projectId } = req.params;
835
- const { path: targetPath, type } = req.body;
836
-
837
- // Validate input
838
- if (!targetPath) {
839
- return res.status(400).json({ error: 'Path is required' });
840
- }
841
-
842
- // Resolve the project directory through the DB using the new projectId.
843
- const projectRoot = await projectsDb.getProjectPathById(projectId);
844
- if (!projectRoot) {
845
- return res.status(404).json({ error: 'Project not found' });
846
- }
847
-
848
- // Validate path
849
- const validation = validatePathInProject(projectRoot, targetPath);
850
- if (!validation.valid) {
851
- return res.status(403).json({ error: validation.error });
852
- }
853
-
854
- const resolvedPath = validation.resolved;
855
-
856
- // Check if path exists and get stats
857
- let stats;
858
- try {
859
- stats = await fsPromises.stat(resolvedPath);
860
- } catch {
861
- return res.status(404).json({ error: 'File or directory not found' });
862
- }
863
-
864
- // Prevent deleting the project root itself
865
- if (resolvedPath === path.resolve(projectRoot)) {
866
- return res.status(403).json({ error: 'Cannot delete project root directory' });
867
- }
868
-
869
- // Delete based on type
870
- if (stats.isDirectory()) {
871
- await fsPromises.rm(resolvedPath, { recursive: true, force: true });
872
- } else {
873
- await fsPromises.unlink(resolvedPath);
874
- }
875
-
876
- res.json({
877
- success: true,
878
- path: resolvedPath,
879
- type: stats.isDirectory() ? 'directory' : 'file',
880
- message: 'Deleted successfully'
881
- });
882
- } catch (error) {
883
- console.error('Error deleting file/directory:', error);
884
- if (error.code === 'EACCES') {
885
- res.status(403).json({ error: 'Permission denied' });
886
- } else if (error.code === 'ENOENT') {
887
- res.status(404).json({ error: 'File or directory not found' });
888
- } else if (error.code === 'ENOTEMPTY') {
889
- res.status(400).json({ error: 'Directory is not empty' });
890
- } else {
891
- res.status(500).json({ error: error.message });
892
- }
893
- }
894
- });
895
-
896
- // POST /api/projects/:projectId/files/upload - Upload files
897
- // Dynamic import of multer for file uploads
898
- const uploadFilesHandler = async (req, res) => {
899
- // Dynamic import of multer
900
- const multer = (await import('multer')).default;
901
-
902
- const uploadMiddleware = multer({
903
- storage: multer.diskStorage({
904
- destination: (req, file, cb) => {
905
- cb(null, os.tmpdir());
906
- },
907
- filename: (req, file, cb) => {
908
- // Use a unique temp name, but preserve original name in file.originalname
909
- // Note: file.originalname may contain path separators for folder uploads
910
- const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
911
- // For temp file, just use a safe unique name without the path
912
- cb(null, `upload-${uniqueSuffix}`);
913
- }
914
- }),
915
- limits: {
916
- fileSize: MAX_FILE_UPLOAD_SIZE_BYTES,
917
- files: MAX_FILE_UPLOAD_COUNT
918
- }
919
- });
920
-
921
- // Use multer middleware
922
- uploadMiddleware.array('files', MAX_FILE_UPLOAD_COUNT)(req, res, async (err) => {
923
- if (err) {
924
- console.error('Multer error:', err);
925
- if (err.code === 'LIMIT_FILE_SIZE') {
926
- return res.status(400).json({ error: `File too large. Maximum size is ${MAX_FILE_UPLOAD_SIZE_MB}MB.` });
927
- }
928
- if (err.code === 'LIMIT_FILE_COUNT') {
929
- return res.status(400).json({ error: `Too many files. Maximum is ${MAX_FILE_UPLOAD_COUNT} files.` });
930
- }
931
- return res.status(500).json({ error: err.message });
932
- }
933
-
934
- try {
935
- const { projectId } = req.params;
936
- const { targetPath, relativePaths, requestedFileCount: requestedFileCountRaw } = req.body;
937
-
938
- // Parse relative paths if provided (for folder uploads)
939
- let filePaths = [];
940
- if (relativePaths) {
941
- try {
942
- filePaths = JSON.parse(relativePaths);
943
- } catch (e) {
944
- console.log('[DEBUG] Failed to parse relativePaths:', relativePaths);
945
- }
946
- }
947
-
948
- console.log('[DEBUG] File upload request:', {
949
- projectId,
950
- targetPath: JSON.stringify(targetPath),
951
- targetPathType: typeof targetPath,
952
- filesCount: req.files?.length,
953
- relativePaths: filePaths
954
- });
955
-
956
- if (!req.files || req.files.length === 0) {
957
- return res.status(400).json({ error: 'No files provided' });
958
- }
959
-
960
- const parsedRequestedFileCount = Number.parseInt(requestedFileCountRaw, 10);
961
- const requestedFileCount = Number.isFinite(parsedRequestedFileCount) && parsedRequestedFileCount > 0
962
- ? parsedRequestedFileCount
963
- : req.files.length;
964
-
965
- // Resolve the project directory through the DB using the new projectId.
966
- const projectRoot = await projectsDb.getProjectPathById(projectId);
967
- if (!projectRoot) {
968
- return res.status(404).json({ error: 'Project not found' });
969
- }
970
-
971
- console.log('[DEBUG] Project root:', projectRoot);
972
-
973
- // Validate and resolve target path
974
- // If targetPath is empty or '.', use project root directly
975
- const targetDir = targetPath || '';
976
- let resolvedTargetDir;
977
-
978
- console.log('[DEBUG] Target dir:', JSON.stringify(targetDir));
979
-
980
- if (!targetDir || targetDir === '.' || targetDir === './') {
981
- // Empty path means upload to project root
982
- resolvedTargetDir = path.resolve(projectRoot);
983
- console.log('[DEBUG] Using project root as target:', resolvedTargetDir);
984
- } else {
985
- const validation = validatePathInProject(projectRoot, targetDir);
986
- if (!validation.valid) {
987
- console.log('[DEBUG] Path validation failed:', validation.error);
988
- return res.status(403).json({ error: validation.error });
989
- }
990
- resolvedTargetDir = validation.resolved;
991
- console.log('[DEBUG] Resolved target dir:', resolvedTargetDir);
992
- }
993
-
994
- // Ensure target directory exists
995
- try {
996
- await fsPromises.access(resolvedTargetDir);
997
- } catch {
998
- await fsPromises.mkdir(resolvedTargetDir, { recursive: true });
999
- }
1000
-
1001
- // Move uploaded files from temp to target directory
1002
- const uploadedFiles = [];
1003
- console.log('[DEBUG] Processing files:', req.files.map(f => ({ originalname: f.originalname, path: f.path })));
1004
- for (let i = 0; i < req.files.length; i++) {
1005
- const file = req.files[i];
1006
- // Use relative path if provided (for folder uploads), otherwise use originalname
1007
- const fileName = (filePaths && filePaths[i]) ? filePaths[i] : file.originalname;
1008
- console.log('[DEBUG] Processing file:', fileName, '(originalname:', file.originalname + ')');
1009
- const destPath = path.join(resolvedTargetDir, fileName);
1010
-
1011
- // Validate destination path
1012
- const destValidation = validatePathInProject(projectRoot, destPath);
1013
- if (!destValidation.valid) {
1014
- console.log('[DEBUG] Destination validation failed for:', destPath);
1015
- // Clean up temp file
1016
- await fsPromises.unlink(file.path).catch(() => {});
1017
- continue;
1018
- }
1019
-
1020
- // Ensure parent directory exists (for nested files from folder upload)
1021
- const parentDir = path.dirname(destPath);
1022
- try {
1023
- await fsPromises.access(parentDir);
1024
- } catch {
1025
- await fsPromises.mkdir(parentDir, { recursive: true });
1026
- }
1027
-
1028
- // Move file (copy + unlink to handle cross-device scenarios)
1029
- await fsPromises.copyFile(file.path, destPath);
1030
- await fsPromises.unlink(file.path);
1031
-
1032
- uploadedFiles.push({
1033
- name: fileName,
1034
- path: destPath,
1035
- size: file.size,
1036
- mimeType: file.mimetype
1037
- });
1038
- }
1039
-
1040
- res.json({
1041
- success: true,
1042
- files: uploadedFiles,
1043
- uploadedCount: uploadedFiles.length,
1044
- requestedFileCount,
1045
- targetPath: resolvedTargetDir,
1046
- message: `Uploaded ${uploadedFiles.length} ${uploadedFiles.length === 1 ? 'file' : 'files'} successfully`
1047
- });
1048
- } catch (error) {
1049
- console.error('Error uploading files:', error);
1050
- // Clean up any remaining temp files
1051
- if (req.files) {
1052
- for (const file of req.files) {
1053
- await fsPromises.unlink(file.path).catch(() => {});
1054
- }
1055
- }
1056
- if (error.code === 'EACCES') {
1057
- res.status(403).json({ error: 'Permission denied' });
1058
- } else {
1059
- res.status(500).json({ error: error.message });
1060
- }
1061
- }
1062
- });
1063
- };
1064
-
1065
- app.post('/api/projects/:projectId/files/upload', authenticateToken, uploadFilesHandler);
1066
-
1067
- // Chat image uploads moved to POST /api/assets/images (server/modules/assets),
1068
- // which stores them in the global ~/.cloudcli/assets folder.
1069
-
1070
- // Get token usage for a specific session. `projectId` is the DB primary key;
1071
- // the Claude branch below resolves it to an absolute path via the DB.
1072
- app.get('/api/projects/:projectId/sessions/:sessionId/token-usage', authenticateToken, async (req, res) => {
1073
- try {
1074
- const { projectId, sessionId } = req.params;
1075
- const homeDir = os.homedir();
1076
-
1077
- // Allow only safe characters in sessionId
1078
- const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9._-]/g, '');
1079
- if (!safeSessionId || safeSessionId !== String(sessionId)) {
1080
- return res.status(400).json({ error: 'Invalid sessionId' });
1081
- }
1082
-
1083
- // Provider artifacts on disk (JSONL file names, OpenCode sqlite rows)
1084
- // are keyed by the provider-native session id, while the caller sends
1085
- // the app-facing id. Resolve provider and id mapping from the indexed
1086
- // session row so the frontend does not choose provider-specific paths.
1087
- const sessionRow = sessionsDb.getSessionById(safeSessionId);
1088
- if (!sessionRow) {
1089
- return res.status(404).json({ error: 'Session not found', sessionId: safeSessionId });
1090
- }
1091
-
1092
- const provider = sessionRow.provider || 'claude';
1093
- const providerNativeSessionId = sessionRow?.provider_session_id || safeSessionId;
1094
-
1095
- // Handle Cursor sessions - they use SQLite and don't have token usage info
1096
- if (provider === 'cursor') {
1097
- return res.json({
1098
- used: 0,
1099
- total: 0,
1100
- inputTokens: 0,
1101
- outputTokens: 0,
1102
- breakdown: { input: 0, output: 0 },
1103
- unsupported: true,
1104
- message: 'Token usage tracking not available for Cursor sessions'
1105
- });
1106
- }
1107
-
1108
- if (provider === 'opencode') {
1109
- const dbPath = getOpenCodeDatabasePath();
1110
- if (!fs.existsSync(dbPath)) {
1111
- return res.status(404).json({ error: 'OpenCode database not found' });
1112
- }
1113
-
1114
- const db = new Database(dbPath, { readonly: true, fileMustExist: true });
1115
- try {
1116
- const columns = db.prepare('PRAGMA table_info(session)').all();
1117
- const columnNames = new Set(columns.map((column) => column.name));
1118
- const requiredColumns = ['tokens_input', 'tokens_output', 'tokens_reasoning', 'tokens_cache_read', 'tokens_cache_write'];
1119
- if (!requiredColumns.every((column) => columnNames.has(column))) {
1120
- return res.json({
1121
- used: 0,
1122
- inputTokens: 0,
1123
- outputTokens: 0,
1124
- breakdown: { input: 0, output: 0 },
1125
- unsupported: true,
1126
- message: 'Token usage tracking is not available in this OpenCode database schema'
1127
- });
1128
- }
1129
-
1130
- const row = db.prepare(`
1131
- SELECT
1132
- tokens_input AS inputTokens,
1133
- tokens_output AS outputTokens,
1134
- tokens_reasoning AS reasoningTokens,
1135
- tokens_cache_read AS cacheReadTokens,
1136
- tokens_cache_write AS cacheWriteTokens
1137
- FROM session
1138
- WHERE id = ?
1139
- `).get(providerNativeSessionId);
1140
-
1141
- if (!row) {
1142
- return res.status(404).json({ error: 'OpenCode session not found', sessionId: safeSessionId });
1143
- }
1144
-
1145
- const inputTokens = Number(row.inputTokens || 0) + Number(row.cacheReadTokens || 0);
1146
- const outputTokens = Number(row.outputTokens || 0);
1147
- const totalUsed = Number(row.inputTokens || 0)
1148
- + outputTokens
1149
- + Number(row.reasoningTokens || 0)
1150
- + Number(row.cacheReadTokens || 0)
1151
- + Number(row.cacheWriteTokens || 0);
1152
-
1153
- return res.json({
1154
- used: totalUsed,
1155
- inputTokens,
1156
- outputTokens,
1157
- breakdown: {
1158
- input: inputTokens,
1159
- output: outputTokens
1160
- }
1161
- });
1162
- } finally {
1163
- db.close();
1164
- }
1165
- }
1166
-
1167
- // Handle Codex sessions
1168
- if (provider === 'codex') {
1169
- const codexSessionsDir = path.join(homeDir, '.codex', 'sessions');
1170
-
1171
- // Find the session file by searching for the session ID
1172
- const findSessionFile = async (dir) => {
1173
- try {
1174
- const entries = await fsPromises.readdir(dir, { withFileTypes: true });
1175
- for (const entry of entries) {
1176
- const fullPath = path.join(dir, entry.name);
1177
- if (entry.isDirectory()) {
1178
- const found = await findSessionFile(fullPath);
1179
- if (found) return found;
1180
- } else if (entry.name.includes(providerNativeSessionId) && entry.name.endsWith('.jsonl')) {
1181
- return fullPath;
1182
- }
1183
- }
1184
- } catch (error) {
1185
- // Skip directories we can't read
1186
- }
1187
- return null;
1188
- };
1189
-
1190
- const sessionFilePath = await findSessionFile(codexSessionsDir);
1191
-
1192
- if (!sessionFilePath) {
1193
- return res.status(404).json({ error: 'Codex session file not found', sessionId: safeSessionId });
1194
- }
1195
-
1196
- // Read and parse the Codex JSONL file
1197
- let fileContent;
1198
- try {
1199
- fileContent = await fsPromises.readFile(sessionFilePath, 'utf8');
1200
- } catch (error) {
1201
- if (error.code === 'ENOENT') {
1202
- return res.status(404).json({ error: 'Session file not found', path: sessionFilePath });
1203
- }
1204
- throw error;
1205
- }
1206
- const lines = fileContent.trim().split('\n');
1207
- let inputTokens = 0;
1208
- let outputTokens = 0;
1209
- let totalTokens = 0;
1210
- let contextWindow = 200000; // Default for Codex/OpenAI
1211
-
1212
- // Find the latest token_count event with info (scan from end)
1213
- for (let i = lines.length - 1; i >= 0; i--) {
1214
- try {
1215
- const entry = JSON.parse(lines[i]);
1216
-
1217
- // Codex stores token info in event_msg with type: "token_count"
1218
- if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) {
1219
- const tokenInfo = entry.payload.info;
1220
- if (tokenInfo.total_token_usage) {
1221
- inputTokens = tokenInfo.total_token_usage.input_tokens || 0;
1222
- outputTokens = tokenInfo.total_token_usage.output_tokens || 0;
1223
- totalTokens = tokenInfo.total_token_usage.total_tokens || inputTokens + outputTokens;
1224
- }
1225
- if (tokenInfo.model_context_window) {
1226
- contextWindow = tokenInfo.model_context_window;
1227
- }
1228
- break; // Stop after finding the latest token count
1229
- }
1230
- } catch (parseError) {
1231
- // Skip lines that can't be parsed
1232
- continue;
1233
- }
1234
- }
1235
-
1236
- return res.json({
1237
- used: totalTokens,
1238
- total: contextWindow,
1239
- inputTokens,
1240
- outputTokens,
1241
- breakdown: {
1242
- input: inputTokens,
1243
- output: outputTokens
1244
- }
1245
- });
1246
- }
1247
-
1248
- // Handle Claude sessions (default)
1249
- // Resolve the project path through the DB using the caller-supplied
1250
- // `projectId`. Legacy code here called extractProjectDirectory with a
1251
- // folder-encoded project name; the migration centralizes that lookup
1252
- // in the projects table.
1253
- const projectPath = await projectsDb.getProjectPathById(projectId);
1254
- if (!projectPath) {
1255
- return res.status(404).json({ error: 'Project not found' });
1256
- }
1257
-
1258
- // Construct the JSONL file path
1259
- // Claude stores session files in ~/.claude/projects/[encoded-project-path]/[session-id].jsonl
1260
- // The encoding replaces any non-alphanumeric character (except -) with -
1261
- const encodedPath = projectPath.replace(/[^a-zA-Z0-9-]/g, '-');
1262
- const projectDir = path.join(homeDir, '.claude', 'projects', encodedPath);
1263
-
1264
- // Prefer the indexed transcript path (already produced by the trusted
1265
- // session synchronizer); fall back to the conventional location
1266
- // derived from the provider-native session id.
1267
- let jsonlPath = sessionRow?.jsonl_path;
1268
- if (!jsonlPath) {
1269
- jsonlPath = path.join(projectDir, `${providerNativeSessionId}.jsonl`);
1270
-
1271
- // Constrain the constructed path to projectDir (the id is
1272
- // caller-influenced in this fallback branch).
1273
- const rel = path.relative(path.resolve(projectDir), path.resolve(jsonlPath));
1274
- if (rel.startsWith('..') || path.isAbsolute(rel)) {
1275
- return res.status(400).json({ error: 'Invalid path' });
1276
- }
1277
- }
1278
-
1279
- // Read and parse the JSONL file
1280
- let fileContent;
1281
- try {
1282
- fileContent = await fsPromises.readFile(jsonlPath, 'utf8');
1283
- } catch (error) {
1284
- if (error.code === 'ENOENT') {
1285
- return res.status(404).json({ error: 'Session file not found', path: jsonlPath });
1286
- }
1287
- throw error; // Re-throw other errors to be caught by outer try-catch
1288
- }
1289
- const lines = fileContent.trim().split('\n');
1290
-
1291
- const parsedContextWindow = parseInt(process.env.CONTEXT_WINDOW, 10);
1292
- const contextWindow = Number.isFinite(parsedContextWindow) ? parsedContextWindow : 160000;
1293
- let inputTokens = 0;
1294
- let outputTokens = 0;
1295
- let cacheReadTokens = 0;
1296
- let cacheCreationTokens = 0;
1297
-
1298
- // Find the latest assistant message with usage data (scan from end)
1299
- for (let i = lines.length - 1; i >= 0; i--) {
1300
- try {
1301
- const entry = JSON.parse(lines[i]);
1302
-
1303
- // Only count assistant messages which have usage data
1304
- if (entry.type === 'assistant' && entry.message?.usage) {
1305
- const usage = entry.message.usage;
1306
-
1307
- // Use token counts from latest assistant message only
1308
- const directInputTokens = readUsageNumber(usage.input_tokens ?? usage.inputTokens);
1309
- cacheReadTokens = readUsageNumber(usage.cache_read_input_tokens ?? usage.cacheReadInputTokens ?? usage.cacheReadTokens);
1310
- cacheCreationTokens = readUsageNumber(usage.cache_creation_input_tokens ?? usage.cacheCreationInputTokens ?? usage.cacheCreationTokens);
1311
- inputTokens = directInputTokens + cacheReadTokens + cacheCreationTokens;
1312
- outputTokens = readUsageNumber(usage.output_tokens ?? usage.outputTokens);
1313
-
1314
- break; // Stop after finding the latest assistant message
1315
- }
1316
- } catch (parseError) {
1317
- // Skip lines that can't be parsed
1318
- continue;
1319
- }
1320
- }
1321
-
1322
- const totalUsed = inputTokens + outputTokens;
1323
- const cacheTokens = cacheReadTokens + cacheCreationTokens;
1324
-
1325
- res.json({
1326
- used: totalUsed,
1327
- total: contextWindow,
1328
- inputTokens,
1329
- outputTokens,
1330
- cacheReadTokens,
1331
- cacheCreationTokens,
1332
- cacheTokens,
1333
- breakdown: {
1334
- input: inputTokens,
1335
- output: outputTokens
1336
- }
1337
- });
1338
- } catch (error) {
1339
- console.error('Error reading session token usage:', error);
1340
- res.status(500).json({ error: 'Failed to read session token usage' });
1341
- }
1342
- });
1343
-
1344
- // Serve React app for all other routes (excluding static files)
1345
- app.get('*', (req, res) => {
1346
- // Skip requests for static assets (files with extensions)
1347
- if (path.extname(req.path)) {
1348
- return res.status(404).send('Not found');
1349
- }
1350
-
1351
- // Only serve index.html for HTML routes, not for static assets
1352
- // Static assets should already be handled by express.static middleware above
1353
- const indexPath = path.join(APP_ROOT, 'dist', 'index.html');
1354
-
1355
- // Check if dist/index.html exists (production build available)
1356
- if (fs.existsSync(indexPath)) {
1357
- // Set no-cache headers for HTML to prevent service worker issues
1358
- res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
1359
- res.setHeader('Pragma', 'no-cache');
1360
- res.setHeader('Expires', '0');
1361
- res.sendFile(indexPath);
1362
- } else {
1363
- // In development, redirect to Vite dev server only if dist doesn't exist
1364
- const redirectHost = getConnectableHost(req.hostname);
1365
- res.redirect(`${req.protocol}://${redirectHost}:${VITE_PORT}`);
1366
- }
1367
- });
1368
-
1369
- // global error middleware must be last
1370
- app.use((err, req, res, next) => {
1371
- if (err instanceof AppError) {
1372
- return res.status(err.statusCode).json({
1373
- success: false,
1374
- error: {
1375
- code: err.code,
1376
- message: err.message,
1377
- details: err.details,
1378
- },
1379
- });
1380
- }
1381
-
1382
- console.error(err);
1383
-
1384
- return res.status(500).json({
1385
- success: false,
1386
- error: {
1387
- code: 'INTERNAL_ERROR',
1388
- message: 'Internal server error',
1389
- },
1390
- });
1391
- });
1392
-
1393
- // Helper function to convert permissions to rwx format
1394
- function permToRwx(perm) {
1395
- const r = perm & 4 ? 'r' : '-';
1396
- const w = perm & 2 ? 'w' : '-';
1397
- const x = perm & 1 ? 'x' : '-';
1398
- return r + w + x;
1399
- }
1400
-
1401
- // Directories that are almost never interesting for a project tree but can
1402
- // contain tens of thousands of files. Skipping them before recursion keeps
1403
- // traversal time bounded on large monorepos and high-latency filesystems
1404
- // (NFS / SMB).
1405
- const IGNORED_DIRS = new Set([
1406
- // JS / TS toolchains
1407
- 'node_modules', 'dist', 'build', '.next', '.nuxt', '.cache', '.parcel-cache',
1408
- // VCS
1409
- '.git', '.svn', '.hg',
1410
- // Python
1411
- '__pycache__', '.pytest_cache', '.mypy_cache', '.tox', 'venv', '.venv',
1412
- // Rust / Go / Java / Ruby
1413
- 'target', 'vendor',
1414
- // Build output / IDE
1415
- '.gradle', '.idea', 'coverage', '.nyc_output'
1416
- ]);
1417
-
1418
- const DEFAULT_FS_CONCURRENCY = 64;
1419
- const parsedFsConcurrency = Number.parseInt(process.env.FS_CONCURRENCY || '', 10);
1420
- const FS_CONCURRENCY = Number.isFinite(parsedFsConcurrency) && parsedFsConcurrency > 0
1421
- ? parsedFsConcurrency
1422
- : DEFAULT_FS_CONCURRENCY;
1423
- let activeFsOperations = 0;
1424
- const pendingFsOperations = [];
1425
-
1426
- async function acquire() {
1427
- if (activeFsOperations < FS_CONCURRENCY) {
1428
- activeFsOperations += 1;
1429
- return;
1430
- }
1431
-
1432
- await new Promise((resolve) => {
1433
- pendingFsOperations.push(resolve);
1434
- });
1435
- }
1436
-
1437
- function release() {
1438
- const next = pendingFsOperations.shift();
1439
- if (next) {
1440
- next();
1441
- return;
1442
- }
1443
-
1444
- activeFsOperations = Math.max(0, activeFsOperations - 1);
1445
- }
1446
-
1447
- async function getFileTree(dirPath, maxDepth = 3, currentDepth = 0, showHidden = true) {
1448
- // Using fsPromises from import
1449
- let entries;
1450
- try {
1451
- await acquire();
1452
- try {
1453
- entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
1454
- } finally {
1455
- release();
1456
- }
1457
- } catch (error) {
1458
- // Only log non-permission errors to avoid spam
1459
- if (error.code !== 'EACCES' && error.code !== 'EPERM') {
1460
- console.error('Error reading directory:', error);
1461
- }
1462
- return [];
1463
- }
1464
-
1465
- const filteredEntries = entries.filter((entry) => !(entry.isDirectory() && IGNORED_DIRS.has(entry.name)));
1466
-
1467
- // Process every entry in parallel. On high-latency filesystems (NFS/SMB)
1468
- // serial stat() was the real bottleneck — issuing them concurrently lets
1469
- // the kernel pipeline the round-trips and the recursive calls overlap too.
1470
- const items = await Promise.all(filteredEntries.map(async (entry) => {
1471
- const itemPath = path.join(dirPath, entry.name);
1472
- const item = {
1473
- name: entry.name,
1474
- path: itemPath,
1475
- type: entry.isDirectory() ? 'directory' : 'file'
1476
- };
1477
-
1478
- // Get file stats for additional metadata
1479
- try {
1480
- await acquire();
1481
- try {
1482
- const stats = await fsPromises.lstat(itemPath);
1483
- item.size = stats.size;
1484
- item.modified = stats.mtime.toISOString();
1485
-
1486
- // Mark symlinks so UI can distinguish them
1487
- if (stats.isSymbolicLink()) {
1488
- item.isSymlink = true;
1489
- }
1490
-
1491
- // Convert permissions to rwx format
1492
- const mode = stats.mode;
1493
- const ownerPerm = (mode >> 6) & 7;
1494
- const groupPerm = (mode >> 3) & 7;
1495
- const otherPerm = mode & 7;
1496
- item.permissions =
1497
- ((mode >> 6) & 7).toString() +
1498
- ((mode >> 3) & 7).toString() +
1499
- (mode & 7).toString();
1500
- item.permissionsRwx =
1501
- permToRwx(ownerPerm) +
1502
- permToRwx(groupPerm) +
1503
- permToRwx(otherPerm);
1504
- } finally {
1505
- release();
1506
- }
1507
- } catch (statError) {
1508
- // If stat fails, provide default values
1509
- item.size = 0;
1510
- item.modified = null;
1511
- item.permissions = '000';
1512
- item.permissionsRwx = '---------';
1513
- }
1514
-
1515
- if (entry.isDirectory() && currentDepth < maxDepth) {
1516
- // Recurse. Let readdir's own EACCES bubble up through the catch in
1517
- // the recursive call rather than doing a separate access() probe
1518
- // (which doubled the round-trip count on SMB without adding info).
1519
- // The recursive call starts with a bounded readdir; holding a permit
1520
- // for the whole subtree can deadlock when sibling directories are
1521
- // waiting on their own children.
1522
- item.children = await getFileTree(itemPath, maxDepth, currentDepth + 1, showHidden);
1523
- }
1524
-
1525
- return item;
1526
- }));
1527
-
1528
- return items.sort((a, b) => {
1529
- if (a.type !== b.type) {
1530
- return a.type === 'directory' ? -1 : 1;
1531
- }
1532
- return a.name.localeCompare(b.name);
1533
- });
1534
- }
1535
-
1536
- const SERVER_PORT = process.env.SERVER_PORT || 3001;
1537
- const HOST = process.env.HOST || '0.0.0.0';
1538
- const DISPLAY_HOST = getConnectableHost(HOST);
1539
- const VITE_PORT = process.env.VITE_PORT || 5173;
1540
- const LOCAL_SERVER_MARKER_PATH = path.join(os.homedir(), '.cloudcli', 'local-server.json');
1541
-
1542
- async function writeLocalServerMarker() {
1543
- const marker = {
1544
- pid: process.pid,
1545
- host: HOST,
1546
- port: Number.parseInt(String(SERVER_PORT), 10),
1547
- url: `http://${DISPLAY_HOST}:${SERVER_PORT}`,
1548
- installMode,
1549
- appRoot: APP_ROOT,
1550
- updatedAt: new Date().toISOString(),
1551
- };
1552
-
1553
- await fsPromises.mkdir(path.dirname(LOCAL_SERVER_MARKER_PATH), { recursive: true });
1554
- await fsPromises.writeFile(LOCAL_SERVER_MARKER_PATH, JSON.stringify(marker, null, 2), 'utf8');
1555
- }
1556
-
1557
- async function removeLocalServerMarker() {
1558
- try {
1559
- const raw = await fsPromises.readFile(LOCAL_SERVER_MARKER_PATH, 'utf8');
1560
- const marker = JSON.parse(raw);
1561
- if (marker.pid && marker.pid !== process.pid) return;
1562
- } catch (error) {
1563
- if (error.code === 'ENOENT') return;
1564
- }
1565
-
1566
- try {
1567
- await fsPromises.unlink(LOCAL_SERVER_MARKER_PATH);
1568
- } catch (error) {
1569
- if (error.code !== 'ENOENT') {
1570
- console.warn('[WARN] Could not remove local server marker:', error.message);
1571
- }
1572
- }
1573
- }
1574
-
1575
- // Initialize database and start server
1576
- async function startServer() {
1577
- try {
1578
- // Initialize authentication database
1579
- await initializeDatabase();
1580
-
1581
- // Configure Web Push (VAPID keys)
1582
- configureWebPush();
1583
-
1584
- // Check if running in production mode (dist folder exists)
1585
- const distIndexPath = path.join(APP_ROOT, 'dist', 'index.html');
1586
- const isProduction = fs.existsSync(distIndexPath);
1587
-
1588
- // Log Claude implementation mode
1589
- console.log(`${c.info('[INFO]')} Using Claude Agents SDK for Claude integration`);
1590
- console.log('');
1591
-
1592
- if (isProduction) {
1593
- console.log(`${c.info('[INFO]')} To run in production mode, go to http://${DISPLAY_HOST}:${SERVER_PORT}`);
1594
- }
1595
-
1596
- console.log(`${c.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`);
1597
-
1598
- server.listen(SERVER_PORT, HOST, async () => {
1599
- const appInstallPath = APP_ROOT;
1600
- await writeLocalServerMarker().catch((error) => {
1601
- console.warn('[WARN] Could not write local server marker:', error.message);
1602
- });
1603
-
1604
- console.log('');
1605
- console.log(c.dim('═'.repeat(63)));
1606
- console.log(` ${c.bright('CloudCLI Server - Ready')}`);
1607
- console.log(c.dim('═'.repeat(63)));
1608
- console.log('');
1609
- console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://' + DISPLAY_HOST + ':' + SERVER_PORT)}`);
1610
- console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`);
1611
- console.log(`${c.tip('[TIP]')} Run "cloudcli status" for full configuration details`);
1612
- console.log('');
1613
-
1614
- // Start watching the projects folder for changes
1615
- await initializeSessionsWatcher();
1616
-
1617
- // Start server-side plugin processes for enabled plugins
1618
- startEnabledPluginServers().catch(err => {
1619
- console.error('[Plugins] Error during startup:', err.message);
1620
- });
1621
- });
1622
-
1623
- await closeSessionsWatcher();
1624
- // Clean up plugin processes on shutdown
1625
- const shutdownRuntimeServices = async () => {
1626
- try {
1627
- await browserUseService.stopAllSessions();
1628
- } catch (err) {
1629
- console.error('[Browser] Error stopping sessions during shutdown:', err?.message || err);
1630
- }
1631
- try {
1632
- await stopAllPlugins();
1633
- } catch (err) {
1634
- console.error('[Plugins] Error stopping plugins during shutdown:', err?.message || err);
1635
- }
1636
- try {
1637
- await removeLocalServerMarker();
1638
- } catch (err) {
1639
- console.error('[Local Server] Error removing server marker during shutdown:', err?.message || err);
1640
- }
1641
- process.exit(0);
1642
- };
1643
- process.on('SIGTERM', () => void shutdownRuntimeServices());
1644
- process.on('SIGINT', () => void shutdownRuntimeServices());
1645
- } catch (error) {
1646
- console.error('[ERROR] Failed to start server:', error);
1647
- process.exit(1);
1648
- }
1649
- }
1650
-
1651
- startServer();