@cloudcli-ai/cloudcli 1.29.5 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (354) hide show
  1. package/README.de.md +1 -1
  2. package/README.ja.md +1 -1
  3. package/README.ko.md +1 -1
  4. package/README.md +1 -1
  5. package/README.ru.md +1 -1
  6. package/README.tr.md +252 -0
  7. package/README.zh-CN.md +1 -1
  8. package/dist/assets/index-DF8TTVHl.css +32 -0
  9. package/dist/assets/index-DbZAcc9z.js +1262 -0
  10. package/dist/index.html +2 -2
  11. package/dist-server/server/claude-sdk.js +13 -8
  12. package/dist-server/server/claude-sdk.js.map +1 -1
  13. package/dist-server/server/cursor-cli.js +7 -7
  14. package/dist-server/server/cursor-cli.js.map +1 -1
  15. package/dist-server/server/gemini-cli.js +5 -5
  16. package/dist-server/server/gemini-cli.js.map +1 -1
  17. package/dist-server/server/gemini-response-handler.js +2 -2
  18. package/dist-server/server/gemini-response-handler.js.map +1 -1
  19. package/dist-server/server/index.js +139 -977
  20. package/dist-server/server/index.js.map +1 -1
  21. package/dist-server/server/middleware/auth.js +1 -1
  22. package/dist-server/server/middleware/auth.js.map +1 -1
  23. package/dist-server/server/modules/database/connection.js +125 -0
  24. package/dist-server/server/modules/database/connection.js.map +1 -0
  25. package/dist-server/server/modules/database/index.js +13 -0
  26. package/dist-server/server/modules/database/index.js.map +1 -0
  27. package/dist-server/server/modules/database/init-db.js +18 -0
  28. package/dist-server/server/modules/database/init-db.js.map +1 -0
  29. package/dist-server/server/modules/database/migrations.js +370 -0
  30. package/dist-server/server/modules/database/migrations.js.map +1 -0
  31. package/dist-server/server/modules/database/repositories/api-keys.js +72 -0
  32. package/dist-server/server/modules/database/repositories/api-keys.js.map +1 -0
  33. package/dist-server/server/modules/database/repositories/app-config.js +47 -0
  34. package/dist-server/server/modules/database/repositories/app-config.js.map +1 -0
  35. package/dist-server/server/modules/database/repositories/credentials.js +68 -0
  36. package/dist-server/server/modules/database/repositories/credentials.js.map +1 -0
  37. package/dist-server/server/modules/database/repositories/github-tokens.js +54 -0
  38. package/dist-server/server/modules/database/repositories/github-tokens.js.map +1 -0
  39. package/dist-server/server/modules/database/repositories/notification-preferences.js +72 -0
  40. package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -0
  41. package/dist-server/server/modules/database/repositories/projects.db.integration.test.js +64 -0
  42. package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +1 -0
  43. package/dist-server/server/modules/database/repositories/projects.db.js +160 -0
  44. package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -0
  45. package/dist-server/server/modules/database/repositories/push-subscriptions.js +49 -0
  46. package/dist-server/server/modules/database/repositories/push-subscriptions.js.map +1 -0
  47. package/dist-server/server/modules/database/repositories/scan-state.db.js +31 -0
  48. package/dist-server/server/modules/database/repositories/scan-state.db.js.map +1 -0
  49. package/dist-server/server/modules/database/repositories/sessions.db.js +109 -0
  50. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -0
  51. package/dist-server/server/modules/database/repositories/users.js +88 -0
  52. package/dist-server/server/modules/database/repositories/users.js.map +1 -0
  53. package/dist-server/server/modules/database/repositories/vapid-keys.js +38 -0
  54. package/dist-server/server/modules/database/repositories/vapid-keys.js.map +1 -0
  55. package/dist-server/server/modules/database/schema.js +143 -0
  56. package/dist-server/server/modules/database/schema.js.map +1 -0
  57. package/dist-server/server/modules/projects/index.js +4 -0
  58. package/dist-server/server/modules/projects/index.js.map +1 -0
  59. package/dist-server/server/modules/projects/projects.routes.js +188 -0
  60. package/dist-server/server/modules/projects/projects.routes.js.map +1 -0
  61. package/dist-server/server/modules/projects/services/project-clone.service.js +219 -0
  62. package/dist-server/server/modules/projects/services/project-clone.service.js.map +1 -0
  63. package/dist-server/server/modules/projects/services/project-delete.service.js +65 -0
  64. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -0
  65. package/dist-server/server/modules/projects/services/project-management.service.js +93 -0
  66. package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -0
  67. package/dist-server/server/modules/projects/services/project-star.service.js +60 -0
  68. package/dist-server/server/modules/projects/services/project-star.service.js.map +1 -0
  69. package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js +167 -0
  70. package/dist-server/server/modules/projects/services/projects-has-taskmaster.service.js.map +1 -0
  71. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +169 -0
  72. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -0
  73. package/dist-server/server/modules/projects/tests/project-clone.service.test.js +126 -0
  74. package/dist-server/server/modules/projects/tests/project-clone.service.test.js.map +1 -0
  75. package/dist-server/server/modules/projects/tests/project-management.service.test.js +85 -0
  76. package/dist-server/server/modules/projects/tests/project-management.service.test.js.map +1 -0
  77. package/dist-server/server/modules/projects/tests/project-star.service.test.js +95 -0
  78. package/dist-server/server/modules/projects/tests/project-star.service.test.js.map +1 -0
  79. package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js +87 -0
  80. package/dist-server/server/modules/projects/tests/projects-has-taskmaster.service.test.js.map +1 -0
  81. package/dist-server/server/modules/providers/index.js +4 -0
  82. package/dist-server/server/modules/providers/index.js.map +1 -0
  83. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +103 -0
  84. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -0
  85. package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js +103 -0
  86. package/dist-server/server/modules/providers/list/claude/claude-mcp.provider.js.map +1 -0
  87. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +63 -0
  88. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -0
  89. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +403 -0
  90. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -0
  91. package/dist-server/server/modules/providers/list/claude/claude.provider.js +15 -0
  92. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -0
  93. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js +84 -0
  94. package/dist-server/server/modules/providers/list/codex/codex-auth.provider.js.map +1 -0
  95. package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js +107 -0
  96. package/dist-server/server/modules/providers/list/codex/codex-mcp.provider.js.map +1 -0
  97. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +71 -0
  98. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -0
  99. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +494 -0
  100. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -0
  101. package/dist-server/server/modules/providers/list/codex/codex.provider.js +15 -0
  102. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -0
  103. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js +118 -0
  104. package/dist-server/server/modules/providers/list/cursor/cursor-auth.provider.js.map +1 -0
  105. package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js +80 -0
  106. package/dist-server/server/modules/providers/list/cursor/cursor-mcp.provider.js.map +1 -0
  107. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +127 -0
  108. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -0
  109. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +369 -0
  110. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -0
  111. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +15 -0
  112. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -0
  113. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +131 -0
  114. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -0
  115. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js +82 -0
  116. package/dist-server/server/modules/providers/list/gemini/gemini-mcp.provider.js.map +1 -0
  117. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +296 -0
  118. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +1 -0
  119. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +474 -0
  120. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -0
  121. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +15 -0
  122. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -0
  123. package/dist-server/server/modules/providers/provider.registry.js +31 -0
  124. package/dist-server/server/modules/providers/provider.registry.js.map +1 -0
  125. package/dist-server/server/modules/providers/provider.routes.js +331 -0
  126. package/dist-server/server/modules/providers/provider.routes.js.map +1 -0
  127. package/dist-server/server/modules/providers/services/mcp.service.js +69 -0
  128. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -0
  129. package/dist-server/server/modules/providers/services/provider-auth.service.js +25 -0
  130. package/dist-server/server/modules/providers/services/provider-auth.service.js.map +1 -0
  131. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +868 -0
  132. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -0
  133. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +56 -0
  134. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -0
  135. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +234 -0
  136. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -0
  137. package/dist-server/server/modules/providers/services/sessions.service.js +104 -0
  138. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -0
  139. package/dist-server/server/modules/providers/shared/base/abstract.provider.js +14 -0
  140. package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -0
  141. package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js +102 -0
  142. package/dist-server/server/modules/providers/shared/mcp/mcp.provider.js.map +1 -0
  143. package/dist-server/server/modules/providers/tests/mcp.test.js +250 -0
  144. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -0
  145. package/dist-server/server/modules/websocket/index.js +3 -0
  146. package/dist-server/server/modules/websocket/index.js.map +1 -0
  147. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +188 -0
  148. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -0
  149. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +52 -0
  150. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -0
  151. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +334 -0
  152. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -0
  153. package/dist-server/server/modules/websocket/services/websocket-auth.service.js +32 -0
  154. package/dist-server/server/modules/websocket/services/websocket-auth.service.js.map +1 -0
  155. package/dist-server/server/modules/websocket/services/websocket-server.service.js +36 -0
  156. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -0
  157. package/dist-server/server/modules/websocket/services/websocket-state.service.js +14 -0
  158. package/dist-server/server/modules/websocket/services/websocket-state.service.js.map +1 -0
  159. package/dist-server/server/modules/websocket/services/websocket-writer.service.js +32 -0
  160. package/dist-server/server/modules/websocket/services/websocket-writer.service.js.map +1 -0
  161. package/dist-server/server/openai-codex.js +5 -5
  162. package/dist-server/server/openai-codex.js.map +1 -1
  163. package/dist-server/server/routes/agent.js +10 -17
  164. package/dist-server/server/routes/agent.js.map +1 -1
  165. package/dist-server/server/routes/auth.js +3 -1
  166. package/dist-server/server/routes/auth.js.map +1 -1
  167. package/dist-server/server/routes/commands.js +1 -45
  168. package/dist-server/server/routes/commands.js.map +1 -1
  169. package/dist-server/server/routes/cursor.js +9 -489
  170. package/dist-server/server/routes/cursor.js.map +1 -1
  171. package/dist-server/server/routes/gemini.js +2 -2
  172. package/dist-server/server/routes/gemini.js.map +1 -1
  173. package/dist-server/server/routes/git.js +33 -29
  174. package/dist-server/server/routes/git.js.map +1 -1
  175. package/dist-server/server/routes/mcp-utils.js +1 -18
  176. package/dist-server/server/routes/mcp-utils.js.map +1 -1
  177. package/dist-server/server/routes/settings.js +11 -1
  178. package/dist-server/server/routes/settings.js.map +1 -1
  179. package/dist-server/server/routes/taskmaster.js +97 -569
  180. package/dist-server/server/routes/taskmaster.js.map +1 -1
  181. package/dist-server/server/routes/user.js +1 -1
  182. package/dist-server/server/routes/user.js.map +1 -1
  183. package/dist-server/server/services/notification-orchestrator.js +2 -2
  184. package/dist-server/server/services/notification-orchestrator.js.map +1 -1
  185. package/dist-server/server/services/vapid-keys.js +2 -1
  186. package/dist-server/server/services/vapid-keys.js.map +1 -1
  187. package/dist-server/server/shared/interfaces.js +2 -0
  188. package/dist-server/server/shared/interfaces.js.map +1 -0
  189. package/dist-server/server/shared/types.js +2 -0
  190. package/dist-server/server/shared/types.js.map +1 -0
  191. package/dist-server/server/shared/utils.js +551 -0
  192. package/dist-server/server/shared/utils.js.map +1 -0
  193. package/dist-server/server/utils/mcp-detector.js +0 -49
  194. package/dist-server/server/utils/mcp-detector.js.map +1 -1
  195. package/dist-server/server/utils/taskmaster-websocket.js +18 -12
  196. package/dist-server/server/utils/taskmaster-websocket.js.map +1 -1
  197. package/dist-server/shared/modelConstants.js +3 -1
  198. package/dist-server/shared/modelConstants.js.map +1 -1
  199. package/package.json +7 -2
  200. package/server/claude-sdk.js +14 -8
  201. package/server/cursor-cli.js +7 -7
  202. package/server/gemini-cli.js +5 -5
  203. package/server/gemini-response-handler.js +2 -2
  204. package/server/index.js +173 -1035
  205. package/server/middleware/auth.js +1 -1
  206. package/server/modules/database/connection.ts +143 -0
  207. package/server/modules/database/index.ts +12 -0
  208. package/server/modules/database/init-db.ts +17 -0
  209. package/server/modules/database/migrations.ts +442 -0
  210. package/server/modules/database/repositories/api-keys.ts +119 -0
  211. package/server/modules/database/repositories/app-config.ts +53 -0
  212. package/server/modules/database/repositories/credentials.ts +106 -0
  213. package/server/modules/database/repositories/github-tokens.ts +100 -0
  214. package/server/modules/database/repositories/notification-preferences.ts +103 -0
  215. package/server/modules/database/repositories/projects.db.integration.test.ts +72 -0
  216. package/server/modules/database/repositories/projects.db.ts +183 -0
  217. package/server/modules/database/repositories/push-subscriptions.ts +80 -0
  218. package/server/modules/database/repositories/scan-state.db.ts +42 -0
  219. package/server/modules/database/repositories/sessions.db.ts +174 -0
  220. package/server/modules/database/repositories/users.ts +140 -0
  221. package/server/modules/database/repositories/vapid-keys.ts +57 -0
  222. package/server/modules/database/schema.ts +152 -0
  223. package/server/modules/projects/index.ts +6 -0
  224. package/server/modules/projects/projects.routes.ts +247 -0
  225. package/server/modules/projects/services/project-clone.service.ts +321 -0
  226. package/server/modules/projects/services/project-delete.service.ts +75 -0
  227. package/server/modules/projects/services/project-management.service.ts +150 -0
  228. package/server/modules/projects/services/project-star.service.ts +78 -0
  229. package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -0
  230. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +285 -0
  231. package/server/modules/projects/tests/project-clone.service.test.ts +183 -0
  232. package/server/modules/projects/tests/project-management.service.test.ts +117 -0
  233. package/server/modules/projects/tests/project-star.service.test.ts +123 -0
  234. package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -0
  235. package/server/modules/providers/index.ts +4 -0
  236. package/server/modules/providers/list/claude/claude-auth.provider.ts +123 -0
  237. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -0
  238. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +110 -0
  239. package/server/modules/providers/list/claude/claude-sessions.provider.ts +475 -0
  240. package/server/modules/providers/list/claude/claude.provider.ts +17 -0
  241. package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -0
  242. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -0
  243. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +119 -0
  244. package/server/modules/providers/list/codex/codex-sessions.provider.ts +564 -0
  245. package/server/modules/providers/list/codex/codex.provider.ts +17 -0
  246. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -0
  247. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -0
  248. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +176 -0
  249. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +421 -0
  250. package/server/modules/providers/list/cursor/cursor.provider.ts +17 -0
  251. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +151 -0
  252. package/server/modules/providers/list/gemini/gemini-mcp.provider.ts +110 -0
  253. package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +401 -0
  254. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +541 -0
  255. package/server/modules/providers/list/gemini/gemini.provider.ts +17 -0
  256. package/server/modules/providers/provider.registry.ts +36 -0
  257. package/server/modules/providers/provider.routes.ts +425 -0
  258. package/server/modules/providers/services/mcp.service.ts +94 -0
  259. package/server/modules/providers/services/provider-auth.service.ts +26 -0
  260. package/server/modules/providers/services/session-conversations-search.service.ts +1167 -0
  261. package/server/modules/providers/services/session-synchronizer.service.ts +74 -0
  262. package/server/modules/providers/services/sessions-watcher.service.ts +283 -0
  263. package/server/modules/providers/services/sessions.service.ts +130 -0
  264. package/server/modules/providers/shared/base/abstract.provider.ts +27 -0
  265. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -0
  266. package/server/modules/providers/tests/mcp.test.ts +293 -0
  267. package/server/modules/websocket/README.md +267 -0
  268. package/server/modules/websocket/index.ts +2 -0
  269. package/server/modules/websocket/services/chat-websocket.service.ts +271 -0
  270. package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -0
  271. package/server/modules/websocket/services/shell-websocket.service.ts +453 -0
  272. package/server/modules/websocket/services/websocket-auth.service.ts +54 -0
  273. package/server/modules/websocket/services/websocket-server.service.ts +58 -0
  274. package/server/modules/websocket/services/websocket-state.service.ts +16 -0
  275. package/server/modules/websocket/services/websocket-writer.service.ts +38 -0
  276. package/server/openai-codex.js +5 -5
  277. package/server/routes/agent.js +11 -16
  278. package/server/routes/auth.js +4 -2
  279. package/server/routes/commands.js +1 -50
  280. package/server/routes/cursor.js +15 -527
  281. package/server/routes/gemini.js +3 -2
  282. package/server/routes/git.js +33 -28
  283. package/server/routes/mcp-utils.js +2 -19
  284. package/server/routes/settings.js +11 -1
  285. package/server/routes/taskmaster.js +107 -602
  286. package/server/routes/user.js +1 -1
  287. package/server/services/notification-orchestrator.js +3 -2
  288. package/server/services/vapid-keys.js +2 -1
  289. package/server/shared/interfaces.ts +92 -0
  290. package/server/shared/types.ts +327 -0
  291. package/server/shared/utils.ts +669 -0
  292. package/server/tsconfig.json +6 -3
  293. package/server/utils/mcp-detector.js +0 -51
  294. package/server/utils/taskmaster-websocket.js +19 -13
  295. package/shared/modelConstants.js +3 -1
  296. package/dist/assets/index-BBAE1OJ_.css +0 -32
  297. package/dist/assets/index-Ci1GOyV7.js +0 -1262
  298. package/dist-server/server/database/db.js +0 -535
  299. package/dist-server/server/database/db.js.map +0 -1
  300. package/dist-server/server/database/schema.js +0 -97
  301. package/dist-server/server/database/schema.js.map +0 -1
  302. package/dist-server/server/projects.js +0 -2325
  303. package/dist-server/server/projects.js.map +0 -1
  304. package/dist-server/server/providers/claude/adapter.js +0 -266
  305. package/dist-server/server/providers/claude/adapter.js.map +0 -1
  306. package/dist-server/server/providers/claude/status.js +0 -121
  307. package/dist-server/server/providers/claude/status.js.map +0 -1
  308. package/dist-server/server/providers/codex/adapter.js +0 -232
  309. package/dist-server/server/providers/codex/adapter.js.map +0 -1
  310. package/dist-server/server/providers/codex/status.js +0 -70
  311. package/dist-server/server/providers/codex/status.js.map +0 -1
  312. package/dist-server/server/providers/cursor/adapter.js +0 -342
  313. package/dist-server/server/providers/cursor/adapter.js.map +0 -1
  314. package/dist-server/server/providers/cursor/status.js +0 -118
  315. package/dist-server/server/providers/cursor/status.js.map +0 -1
  316. package/dist-server/server/providers/gemini/adapter.js +0 -174
  317. package/dist-server/server/providers/gemini/adapter.js.map +0 -1
  318. package/dist-server/server/providers/gemini/status.js +0 -104
  319. package/dist-server/server/providers/gemini/status.js.map +0 -1
  320. package/dist-server/server/providers/registry.js +0 -58
  321. package/dist-server/server/providers/registry.js.map +0 -1
  322. package/dist-server/server/providers/types.js +0 -117
  323. package/dist-server/server/providers/types.js.map +0 -1
  324. package/dist-server/server/providers/utils.js +0 -28
  325. package/dist-server/server/providers/utils.js.map +0 -1
  326. package/dist-server/server/routes/cli-auth.js +0 -25
  327. package/dist-server/server/routes/cli-auth.js.map +0 -1
  328. package/dist-server/server/routes/codex.js +0 -294
  329. package/dist-server/server/routes/codex.js.map +0 -1
  330. package/dist-server/server/routes/mcp.js +0 -480
  331. package/dist-server/server/routes/mcp.js.map +0 -1
  332. package/dist-server/server/routes/messages.js +0 -56
  333. package/dist-server/server/routes/messages.js.map +0 -1
  334. package/dist-server/server/routes/projects.js +0 -505
  335. package/dist-server/server/routes/projects.js.map +0 -1
  336. package/server/database/db.js +0 -593
  337. package/server/database/schema.js +0 -102
  338. package/server/projects.js +0 -2555
  339. package/server/providers/claude/adapter.js +0 -278
  340. package/server/providers/claude/status.js +0 -136
  341. package/server/providers/codex/adapter.js +0 -248
  342. package/server/providers/codex/status.js +0 -78
  343. package/server/providers/cursor/adapter.js +0 -348
  344. package/server/providers/cursor/status.js +0 -128
  345. package/server/providers/gemini/adapter.js +0 -186
  346. package/server/providers/gemini/status.js +0 -111
  347. package/server/providers/registry.js +0 -67
  348. package/server/providers/types.js +0 -132
  349. package/server/providers/utils.js +0 -29
  350. package/server/routes/cli-auth.js +0 -27
  351. package/server/routes/codex.js +0 -329
  352. package/server/routes/mcp.js +0 -552
  353. package/server/routes/messages.js +0 -61
  354. package/server/routes/projects.js +0 -548
@@ -0,0 +1,248 @@
1
+ import { access, readFile, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { projectsDb } from '@/modules/database/index.js';
5
+ import { AppError } from '@/shared/utils.js';
6
+
7
+ type TaskMasterTask = {
8
+ status?: string;
9
+ subtasks?: Array<{
10
+ status?: string;
11
+ }>;
12
+ };
13
+
14
+ type TaskMasterMetadata =
15
+ | {
16
+ taskCount: number;
17
+ subtaskCount: number;
18
+ completed: number;
19
+ pending: number;
20
+ inProgress: number;
21
+ review: number;
22
+ completionPercentage: number;
23
+ lastModified: string;
24
+ }
25
+ | {
26
+ error: string;
27
+ }
28
+ | null;
29
+
30
+ type TaskMasterDetectionResult = {
31
+ hasTaskmaster: boolean;
32
+ hasEssentialFiles?: boolean;
33
+ files?: Record<string, boolean>;
34
+ metadata?: TaskMasterMetadata;
35
+ path?: string;
36
+ reason?: string;
37
+ };
38
+
39
+ type NormalizedTaskMasterInfo = {
40
+ hasTaskmaster: boolean;
41
+ hasEssentialFiles: boolean;
42
+ metadata: TaskMasterMetadata;
43
+ status: 'configured' | 'not-configured';
44
+ };
45
+
46
+ type GetProjectTaskMasterByIdResult = {
47
+ projectId: string;
48
+ projectPath: string;
49
+ taskmaster: NormalizedTaskMasterInfo;
50
+ };
51
+
52
+ type GetProjectTaskMasterDependencies = {
53
+ resolveProjectPathById: (projectId: string) => string | null;
54
+ detectTaskMasterFolder: (projectPath: string) => Promise<TaskMasterDetectionResult>;
55
+ };
56
+
57
+ type GetProjectTaskMasterResolver = (projectId: string) => Promise<GetProjectTaskMasterByIdResult | null>;
58
+
59
+ function extractTasksFromJson(tasksData: unknown): TaskMasterTask[] {
60
+ if (!tasksData || typeof tasksData !== 'object') {
61
+ return [];
62
+ }
63
+
64
+ const legacyTasks = (tasksData as { tasks?: unknown }).tasks;
65
+ if (Array.isArray(legacyTasks)) {
66
+ return legacyTasks as TaskMasterTask[];
67
+ }
68
+
69
+ const taggedTaskCollections: TaskMasterTask[] = [];
70
+ for (const tagValue of Object.values(tasksData)) {
71
+ if (!tagValue || typeof tagValue !== 'object') {
72
+ continue;
73
+ }
74
+
75
+ const tagTasks = (tagValue as { tasks?: unknown }).tasks;
76
+ if (Array.isArray(tagTasks)) {
77
+ taggedTaskCollections.push(...(tagTasks as TaskMasterTask[]));
78
+ }
79
+ }
80
+
81
+ return taggedTaskCollections;
82
+ }
83
+
84
+ async function detectTaskMasterFolder(projectPath: string): Promise<TaskMasterDetectionResult> {
85
+ try {
86
+ const taskMasterPath = path.join(projectPath, '.taskmaster');
87
+
88
+ try {
89
+ const taskMasterStats = await stat(taskMasterPath);
90
+ if (!taskMasterStats.isDirectory()) {
91
+ return {
92
+ hasTaskmaster: false,
93
+ reason: '.taskmaster exists but is not a directory',
94
+ };
95
+ }
96
+ } catch (error) {
97
+ const fileError = error as NodeJS.ErrnoException;
98
+ if (fileError.code === 'ENOENT') {
99
+ return {
100
+ hasTaskmaster: false,
101
+ reason: '.taskmaster directory not found',
102
+ };
103
+ }
104
+
105
+ throw fileError;
106
+ }
107
+
108
+ const keyFiles = ['tasks/tasks.json', 'config.json'];
109
+ const fileStatus: Record<string, boolean> = {};
110
+ let hasEssentialFiles = true;
111
+
112
+ for (const fileName of keyFiles) {
113
+ const absoluteFilePath = path.join(taskMasterPath, fileName);
114
+ try {
115
+ await access(absoluteFilePath);
116
+ fileStatus[fileName] = true;
117
+ } catch {
118
+ fileStatus[fileName] = false;
119
+ if (fileName === 'tasks/tasks.json') {
120
+ hasEssentialFiles = false;
121
+ }
122
+ }
123
+ }
124
+
125
+ let taskMetadata: TaskMasterMetadata = null;
126
+ if (fileStatus['tasks/tasks.json']) {
127
+ const tasksPath = path.join(taskMasterPath, 'tasks/tasks.json');
128
+ try {
129
+ const tasksContent = await readFile(tasksPath, 'utf8');
130
+ const parsedTasksJson = JSON.parse(tasksContent) as unknown;
131
+ const tasks = extractTasksFromJson(parsedTasksJson);
132
+
133
+ const stats = tasks.reduce(
134
+ (accumulator, currentTask) => {
135
+ accumulator.total += 1;
136
+ const normalizedTaskStatus = currentTask.status || 'pending';
137
+ accumulator.byStatus[normalizedTaskStatus] = (accumulator.byStatus[normalizedTaskStatus] || 0) + 1;
138
+
139
+ if (Array.isArray(currentTask.subtasks)) {
140
+ for (const subtask of currentTask.subtasks) {
141
+ accumulator.subtotalTasks += 1;
142
+ const normalizedSubtaskStatus = subtask.status || 'pending';
143
+ accumulator.subtaskByStatus[normalizedSubtaskStatus] =
144
+ (accumulator.subtaskByStatus[normalizedSubtaskStatus] || 0) + 1;
145
+ }
146
+ }
147
+
148
+ return accumulator;
149
+ },
150
+ {
151
+ total: 0,
152
+ subtotalTasks: 0,
153
+ byStatus: {} as Record<string, number>,
154
+ subtaskByStatus: {} as Record<string, number>,
155
+ },
156
+ );
157
+
158
+ const tasksStat = await stat(tasksPath);
159
+ taskMetadata = {
160
+ taskCount: stats.total,
161
+ subtaskCount: stats.subtotalTasks,
162
+ completed: stats.byStatus.done || 0,
163
+ pending: stats.byStatus.pending || 0,
164
+ inProgress: stats.byStatus['in-progress'] || 0,
165
+ review: stats.byStatus.review || 0,
166
+ completionPercentage: stats.total > 0 ? Math.round(((stats.byStatus.done || 0) / stats.total) * 100) : 0,
167
+ lastModified: tasksStat.mtime.toISOString(),
168
+ };
169
+ } catch (parseError) {
170
+ console.warn('Failed to parse tasks.json:', (parseError as Error).message);
171
+ taskMetadata = {
172
+ error: 'Failed to parse tasks.json',
173
+ };
174
+ }
175
+ }
176
+
177
+ return {
178
+ hasTaskmaster: true,
179
+ hasEssentialFiles,
180
+ files: fileStatus,
181
+ metadata: taskMetadata,
182
+ path: taskMasterPath,
183
+ };
184
+ } catch (error) {
185
+ console.error('Error detecting TaskMaster folder:', error);
186
+ return {
187
+ hasTaskmaster: false,
188
+ reason: `Error checking directory: ${(error as Error).message}`,
189
+ };
190
+ }
191
+ }
192
+
193
+ function normalizeTaskMasterInfo(taskMasterResult: TaskMasterDetectionResult | null = null): NormalizedTaskMasterInfo {
194
+ const hasTaskmaster = Boolean(taskMasterResult?.hasTaskmaster);
195
+ const hasEssentialFiles = Boolean(taskMasterResult?.hasEssentialFiles);
196
+
197
+ return {
198
+ hasTaskmaster,
199
+ hasEssentialFiles,
200
+ metadata: taskMasterResult?.metadata ?? null,
201
+ status: hasTaskmaster && hasEssentialFiles ? 'configured' : 'not-configured',
202
+ };
203
+ }
204
+
205
+ const defaultDependencies: GetProjectTaskMasterDependencies = {
206
+ resolveProjectPathById: (projectId: string): string | null => projectsDb.getProjectPathById(projectId),
207
+ detectTaskMasterFolder,
208
+ };
209
+
210
+ export async function getProjectTaskMasterById(
211
+ projectId: string,
212
+ dependencies: GetProjectTaskMasterDependencies = defaultDependencies,
213
+ ): Promise<GetProjectTaskMasterByIdResult | null> {
214
+ const projectPath = dependencies.resolveProjectPathById(projectId);
215
+ if (!projectPath) {
216
+ return null;
217
+ }
218
+
219
+ const taskMasterResult = await dependencies.detectTaskMasterFolder(projectPath);
220
+ return {
221
+ projectId,
222
+ projectPath,
223
+ taskmaster: normalizeTaskMasterInfo(taskMasterResult),
224
+ };
225
+ }
226
+
227
+ export async function getProjectTaskMaster(
228
+ projectId: string,
229
+ resolveById: GetProjectTaskMasterResolver = getProjectTaskMasterById,
230
+ ): Promise<GetProjectTaskMasterByIdResult> {
231
+ const normalizedProjectId = projectId.trim();
232
+ if (!normalizedProjectId) {
233
+ throw new AppError('projectId is required', {
234
+ code: 'PROJECT_ID_REQUIRED',
235
+ statusCode: 400,
236
+ });
237
+ }
238
+
239
+ const taskMasterDetails = await resolveById(normalizedProjectId);
240
+ if (!taskMasterDetails) {
241
+ throw new AppError('Project not found', {
242
+ code: 'PROJECT_NOT_FOUND',
243
+ statusCode: 404,
244
+ });
245
+ }
246
+
247
+ return taskMasterDetails;
248
+ }
@@ -0,0 +1,285 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { projectsDb, sessionsDb } from '@/modules/database/index.js';
5
+ import { sessionSynchronizerService } from '@/modules/providers/index.js';
6
+ import { WS_OPEN_STATE, connectedClients } from '@/modules/websocket/index.js';
7
+ import type { RealtimeClientConnection } from '@/shared/types.js';
8
+ import { AppError } from '@/shared/utils.js';
9
+
10
+ type SessionSummary = {
11
+ id: string;
12
+ summary: string;
13
+ messageCount: number;
14
+ lastActivity: string;
15
+ };
16
+
17
+ type SessionsByProvider = Record<'claude' | 'cursor' | 'codex' | 'gemini', SessionSummary[]>;
18
+
19
+ type SessionRepositoryRow = {
20
+ provider: string;
21
+ session_id: string;
22
+ custom_name?: string | null;
23
+ updated_at?: string | null;
24
+ created_at?: string | null;
25
+ };
26
+
27
+ export type ProjectListItem = {
28
+ projectId: string;
29
+ path: string;
30
+ displayName: string;
31
+ fullPath: string;
32
+ isStarred: boolean;
33
+ sessions: SessionSummary[];
34
+ cursorSessions: SessionSummary[];
35
+ codexSessions: SessionSummary[];
36
+ geminiSessions: SessionSummary[];
37
+ sessionMeta: {
38
+ hasMore: boolean;
39
+ total: number;
40
+ };
41
+ };
42
+
43
+ type ProgressUpdate = {
44
+ phase: 'loading' | 'complete';
45
+ current: number;
46
+ total: number;
47
+ currentProject?: string;
48
+ };
49
+
50
+ type GetProjectsWithSessionsOptions = {
51
+ skipSynchronization?: boolean;
52
+ sessionsLimit?: number;
53
+ sessionsOffset?: number;
54
+ };
55
+
56
+ type SessionPaginationOptions = {
57
+ limit?: number;
58
+ offset?: number;
59
+ };
60
+
61
+ type ProjectSessionsPageResult = {
62
+ sessionsByProvider: SessionsByProvider;
63
+ total: number;
64
+ hasMore: boolean;
65
+ };
66
+
67
+ export type ProjectSessionsPageApiView = {
68
+ projectId: string;
69
+ sessions: SessionSummary[];
70
+ cursorSessions: SessionSummary[];
71
+ codexSessions: SessionSummary[];
72
+ geminiSessions: SessionSummary[];
73
+ sessionMeta: {
74
+ hasMore: boolean;
75
+ total: number;
76
+ };
77
+ };
78
+
79
+ const DEFAULT_PROJECT_SESSIONS_PAGE_SIZE = 20;
80
+ const MAX_PROJECT_SESSIONS_PAGE_SIZE = 200;
81
+
82
+ /**
83
+ * Generate better display name from path.
84
+ */
85
+ export async function generateDisplayName(projectName: string, actualProjectDir: string | null = null): Promise<string> {
86
+ // Use actual project directory if provided, otherwise decode from project name.
87
+ const projectPath = actualProjectDir || projectName.replace(/-/g, '/');
88
+
89
+ // Try to read package.json from the project path.
90
+ try {
91
+ const packageJsonPath = path.join(projectPath, 'package.json');
92
+ const packageData = await fs.readFile(packageJsonPath, 'utf8');
93
+ const packageJson = JSON.parse(packageData) as { name?: string };
94
+
95
+ // Return the name from package.json if it exists.
96
+ if (packageJson.name) {
97
+ return packageJson.name;
98
+ }
99
+ } catch {
100
+ // Fall back to path-based naming if package.json doesn't exist or can't be read.
101
+ }
102
+
103
+ // If it starts with /, it's an absolute path.
104
+ if (projectPath.startsWith('/')) {
105
+ const parts = projectPath.split('/').filter(Boolean);
106
+ // Return only the last folder name.
107
+ return parts[parts.length - 1] || projectPath;
108
+ }
109
+
110
+ return projectPath;
111
+ }
112
+
113
+ function normalizeSessionPagination(options: SessionPaginationOptions = {}): { limit: number; offset: number } {
114
+ const rawLimit = Number.isFinite(options.limit) ? Math.floor(Number(options.limit)) : DEFAULT_PROJECT_SESSIONS_PAGE_SIZE;
115
+ const rawOffset = Number.isFinite(options.offset) ? Math.floor(Number(options.offset)) : 0;
116
+
117
+ return {
118
+ limit: Math.min(Math.max(1, rawLimit), MAX_PROJECT_SESSIONS_PAGE_SIZE),
119
+ offset: Math.max(0, rawOffset),
120
+ };
121
+ }
122
+
123
+ function mapSessionRowToSummary(row: SessionRepositoryRow): SessionSummary {
124
+ return {
125
+ id: row.session_id,
126
+ summary: row.custom_name || '',
127
+ messageCount: 0,
128
+ lastActivity: row.updated_at ?? row.created_at ?? new Date().toISOString(),
129
+ };
130
+ }
131
+
132
+ function bucketSessionRowsByProvider(rows: SessionRepositoryRow[]): SessionsByProvider {
133
+ const byProvider: SessionsByProvider = {
134
+ claude: [],
135
+ cursor: [],
136
+ codex: [],
137
+ gemini: [],
138
+ };
139
+
140
+ for (const row of rows) {
141
+ const provider = row.provider as keyof SessionsByProvider;
142
+ const bucket = byProvider[provider];
143
+ if (!bucket) {
144
+ continue;
145
+ }
146
+
147
+ bucket.push(mapSessionRowToSummary(row));
148
+ }
149
+
150
+ return byProvider;
151
+ }
152
+
153
+ /**
154
+ * Reads one paginated project session slice from the DB and groups rows by provider.
155
+ */
156
+ function readProjectSessionsPageByPath(
157
+ projectPath: string,
158
+ options: SessionPaginationOptions = {},
159
+ ): ProjectSessionsPageResult {
160
+ const pagination = normalizeSessionPagination(options);
161
+ const rows = sessionsDb.getSessionsByProjectPathPage(
162
+ projectPath,
163
+ pagination.limit,
164
+ pagination.offset,
165
+ ) as SessionRepositoryRow[];
166
+ const total = sessionsDb.countSessionsByProjectPath(projectPath);
167
+
168
+ return {
169
+ sessionsByProvider: bucketSessionRowsByProvider(rows),
170
+ total,
171
+ hasMore: pagination.offset + rows.length < total,
172
+ };
173
+ }
174
+
175
+ // Broadcast progress to all connected WebSocket clients
176
+ function broadcastProgress(progress: ProgressUpdate) {
177
+ const message = JSON.stringify({
178
+ type: 'loading_progress',
179
+ ...progress,
180
+ });
181
+
182
+ connectedClients.forEach((client: RealtimeClientConnection) => {
183
+ if (client.readyState === WS_OPEN_STATE) {
184
+ client.send(message);
185
+ }
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Reads all projects from DB and returns provider-bucketed session summaries.
191
+ */
192
+ export async function getProjectsWithSessions(
193
+ options: GetProjectsWithSessionsOptions = {}
194
+ ): Promise<ProjectListItem[]> {
195
+ if (!options.skipSynchronization) {
196
+ await sessionSynchronizerService.synchronizeSessions();
197
+ }
198
+
199
+ const projectRows = projectsDb.getProjectPaths() as Array<{
200
+ project_id: string;
201
+ project_path: string;
202
+ custom_project_name?: string | null;
203
+ isStarred?: number;
204
+ }>;
205
+ const totalProjects = projectRows.length;
206
+ const projects: ProjectListItem[] = [];
207
+ let processedProjects = 0;
208
+
209
+ for (const row of projectRows) {
210
+ processedProjects += 1;
211
+
212
+ const projectId = row.project_id;
213
+ const projectPath = row.project_path;
214
+
215
+ broadcastProgress({
216
+ phase: 'loading',
217
+ current: processedProjects,
218
+ total: totalProjects,
219
+ currentProject: projectPath,
220
+ });
221
+
222
+ const displayName =
223
+ row.custom_project_name && row.custom_project_name.trim().length > 0
224
+ ? row.custom_project_name
225
+ : await generateDisplayName(path.basename(projectPath) || projectPath, projectPath);
226
+
227
+ const sessionsPage = readProjectSessionsPageByPath(projectPath, {
228
+ limit: options.sessionsLimit,
229
+ offset: options.sessionsOffset,
230
+ });
231
+
232
+ projects.push({
233
+ projectId,
234
+ path: projectPath,
235
+ displayName,
236
+ fullPath: projectPath,
237
+ isStarred: Boolean(row.isStarred),
238
+ sessions: sessionsPage.sessionsByProvider.claude,
239
+ cursorSessions: sessionsPage.sessionsByProvider.cursor,
240
+ codexSessions: sessionsPage.sessionsByProvider.codex,
241
+ geminiSessions: sessionsPage.sessionsByProvider.gemini,
242
+ sessionMeta: {
243
+ hasMore: sessionsPage.hasMore,
244
+ total: sessionsPage.total,
245
+ },
246
+ });
247
+ }
248
+
249
+ broadcastProgress({
250
+ phase: 'complete',
251
+ current: totalProjects,
252
+ total: totalProjects,
253
+ });
254
+
255
+ return projects;
256
+ }
257
+
258
+ /**
259
+ * Loads one paginated session slice for a specific project id.
260
+ */
261
+ export async function getProjectSessionsPage(
262
+ projectId: string,
263
+ options: SessionPaginationOptions = {},
264
+ ): Promise<ProjectSessionsPageApiView> {
265
+ const projectRow = projectsDb.getProjectById(projectId);
266
+ if (!projectRow) {
267
+ throw new AppError(`Project "${projectId}" was not found.`, {
268
+ code: 'PROJECT_NOT_FOUND',
269
+ statusCode: 404,
270
+ });
271
+ }
272
+
273
+ const sessionsPage = readProjectSessionsPageByPath(projectRow.project_path, options);
274
+ return {
275
+ projectId: projectRow.project_id,
276
+ sessions: sessionsPage.sessionsByProvider.claude,
277
+ cursorSessions: sessionsPage.sessionsByProvider.cursor,
278
+ codexSessions: sessionsPage.sessionsByProvider.codex,
279
+ geminiSessions: sessionsPage.sessionsByProvider.gemini,
280
+ sessionMeta: {
281
+ hasMore: sessionsPage.hasMore,
282
+ total: sessionsPage.total,
283
+ },
284
+ };
285
+ }
@@ -0,0 +1,183 @@
1
+ import assert from 'node:assert/strict';
2
+ import { EventEmitter } from 'node:events';
3
+ import path from 'node:path';
4
+ import { PassThrough } from 'node:stream';
5
+ import test from 'node:test';
6
+
7
+ import { startCloneProject } from '@/modules/projects/services/project-clone.service.js';
8
+ import { AppError } from '@/shared/utils.js';
9
+
10
+ type TestDependencies = Parameters<typeof startCloneProject>[2];
11
+
12
+ function buildDependencies(overrides: Partial<NonNullable<TestDependencies>> = {}): NonNullable<TestDependencies> {
13
+ return {
14
+ validatePath: async () => ({ valid: true, resolvedPath: '/workspace/root' }),
15
+ ensureDirectory: async () => undefined,
16
+ pathExists: async () => false,
17
+ removePath: async () => undefined,
18
+ getGithubTokenById: async () => ({ github_token: 'token-value' }),
19
+ spawnGitClone: () => {
20
+ throw new Error('spawnGitClone should be overridden in this test');
21
+ },
22
+ registerProject: async () => ({ project: { projectId: 'project-1' } }),
23
+ logError: () => undefined,
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ function createMockGitProcess() {
29
+ const emitter = new EventEmitter() as EventEmitter & {
30
+ stdout: PassThrough;
31
+ stderr: PassThrough;
32
+ kill: () => void;
33
+ };
34
+
35
+ emitter.stdout = new PassThrough();
36
+ emitter.stderr = new PassThrough();
37
+ emitter.kill = () => {
38
+ emitter.emit('close', null);
39
+ };
40
+
41
+ return emitter;
42
+ }
43
+
44
+ test('startCloneProject rejects when workspace path is missing', async () => {
45
+ await assert.rejects(
46
+ async () =>
47
+ startCloneProject(
48
+ {
49
+ workspacePath: '',
50
+ githubUrl: 'https://github.com/example/repo',
51
+ userId: 1,
52
+ },
53
+ {
54
+ onProgress: () => undefined,
55
+ onComplete: () => undefined,
56
+ },
57
+ buildDependencies(),
58
+ ),
59
+ (error: unknown) => {
60
+ assert.ok(error instanceof AppError);
61
+ assert.equal(error.code, 'WORKSPACE_PATH_REQUIRED');
62
+ return true;
63
+ },
64
+ );
65
+ });
66
+
67
+ test('startCloneProject rejects when github URL is missing', async () => {
68
+ await assert.rejects(
69
+ async () =>
70
+ startCloneProject(
71
+ {
72
+ workspacePath: '/workspace/root',
73
+ githubUrl: '',
74
+ userId: 1,
75
+ },
76
+ {
77
+ onProgress: () => undefined,
78
+ onComplete: () => undefined,
79
+ },
80
+ buildDependencies(),
81
+ ),
82
+ (error: unknown) => {
83
+ assert.ok(error instanceof AppError);
84
+ assert.equal(error.code, 'GITHUB_URL_REQUIRED');
85
+ return true;
86
+ },
87
+ );
88
+ });
89
+
90
+ test('startCloneProject rejects github URL values that begin with option prefixes', async () => {
91
+ await assert.rejects(
92
+ async () =>
93
+ startCloneProject(
94
+ {
95
+ workspacePath: '/workspace/root',
96
+ githubUrl: '--upload-pack=malicious',
97
+ userId: 1,
98
+ },
99
+ {
100
+ onProgress: () => undefined,
101
+ onComplete: () => undefined,
102
+ },
103
+ buildDependencies(),
104
+ ),
105
+ (error: unknown) => {
106
+ assert.ok(error instanceof AppError);
107
+ assert.equal(error.code, 'INVALID_GITHUB_URL');
108
+ return true;
109
+ },
110
+ );
111
+ });
112
+
113
+ test('startCloneProject rejects when selected github token does not exist', async () => {
114
+ await assert.rejects(
115
+ async () =>
116
+ startCloneProject(
117
+ {
118
+ workspacePath: '/workspace/root',
119
+ githubUrl: 'https://github.com/example/repo',
120
+ githubTokenId: 12,
121
+ userId: 1,
122
+ },
123
+ {
124
+ onProgress: () => undefined,
125
+ onComplete: () => undefined,
126
+ },
127
+ buildDependencies({
128
+ getGithubTokenById: async () => null,
129
+ }),
130
+ ),
131
+ (error: unknown) => {
132
+ assert.ok(error instanceof AppError);
133
+ assert.equal(error.code, 'GITHUB_TOKEN_NOT_FOUND');
134
+ return true;
135
+ },
136
+ );
137
+ });
138
+
139
+ test('startCloneProject completes and emits complete payload when git exits successfully', async () => {
140
+ const gitProcess = createMockGitProcess();
141
+ const progressMessages: string[] = [];
142
+ let completePayload: { project: Record<string, unknown>; message: string } | null = null;
143
+ let capturedProjectPath = '';
144
+ let capturedCustomName = '';
145
+
146
+ const operation = await startCloneProject(
147
+ {
148
+ workspacePath: '/workspace/root',
149
+ githubUrl: 'https://github.com/example/repo.git',
150
+ userId: 1,
151
+ },
152
+ {
153
+ onProgress: (message) => {
154
+ progressMessages.push(message);
155
+ },
156
+ onComplete: (payload: { project: Record<string, unknown>; message: string }) => {
157
+ completePayload = payload;
158
+ },
159
+ },
160
+ buildDependencies({
161
+ spawnGitClone: () => gitProcess as any,
162
+ registerProject: async (projectPath, customName) => {
163
+ capturedProjectPath = projectPath;
164
+ capturedCustomName = customName;
165
+ return { project: { projectId: 'project-1', path: projectPath } };
166
+ },
167
+ }),
168
+ );
169
+
170
+ gitProcess.emit('close', 0);
171
+ await operation.waitForCompletion;
172
+
173
+ assert.ok(progressMessages.some((message) => message.includes("Cloning into 'repo'")));
174
+ assert.equal(capturedCustomName, 'repo');
175
+ assert.equal(path.basename(capturedProjectPath), 'repo');
176
+ assert.notEqual(completePayload, null);
177
+ const resolvedCompletePayload = completePayload as unknown as {
178
+ project: Record<string, unknown>;
179
+ message: string;
180
+ };
181
+ assert.equal(resolvedCompletePayload.message, 'Repository cloned successfully');
182
+ assert.equal((resolvedCompletePayload.project.projectId as string) || '', 'project-1');
183
+ });