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