@cloudcli-ai/cloudcli 1.31.5 → 1.33.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 (252) hide show
  1. package/README.de.md +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/README.ru.md +1 -1
  5. package/README.tr.md +1 -1
  6. package/README.zh-CN.md +1 -1
  7. package/dist/api-docs.html +6 -7
  8. package/dist/assets/index-MOIvpy53.css +32 -0
  9. package/dist/assets/{index-DqMVUeZS.js → index-skXSjBje.js} +290 -270
  10. package/dist/index.html +2 -2
  11. package/dist/modelConstants.js +841 -0
  12. package/dist-server/server/claude-sdk.js +67 -39
  13. package/dist-server/server/claude-sdk.js.map +1 -1
  14. package/dist-server/server/cursor-cli.js +6 -9
  15. package/dist-server/server/cursor-cli.js.map +1 -1
  16. package/dist-server/server/gemini-cli.js +163 -35
  17. package/dist-server/server/gemini-cli.js.map +1 -1
  18. package/dist-server/server/gemini-response-handler.js +34 -0
  19. package/dist-server/server/gemini-response-handler.js.map +1 -1
  20. package/dist-server/server/index.js +131 -19
  21. package/dist-server/server/index.js.map +1 -1
  22. package/dist-server/server/modules/database/index.js +1 -0
  23. package/dist-server/server/modules/database/index.js.map +1 -1
  24. package/dist-server/server/modules/database/migrations.js +11 -0
  25. package/dist-server/server/modules/database/migrations.js.map +1 -1
  26. package/dist-server/server/modules/database/repositories/projects.db.js +12 -0
  27. package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
  28. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js +61 -0
  29. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +1 -0
  30. package/dist-server/server/modules/database/repositories/sessions.db.js +49 -8
  31. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  32. package/dist-server/server/modules/database/schema.js +1 -0
  33. package/dist-server/server/modules/database/schema.js.map +1 -1
  34. package/dist-server/server/modules/projects/projects.routes.js +12 -3
  35. package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
  36. package/dist-server/server/modules/projects/services/project-delete.service.js +14 -1
  37. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  38. package/dist-server/server/modules/projects/services/project-management.service.js +1 -0
  39. package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
  40. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +47 -0
  41. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
  42. package/dist-server/server/modules/providers/index.js +1 -0
  43. package/dist-server/server/modules/providers/index.js.map +1 -1
  44. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +27 -7
  45. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  46. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +141 -0
  47. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
  48. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +55 -2
  49. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  50. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +153 -14
  51. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  52. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +198 -0
  53. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
  54. package/dist-server/server/modules/providers/list/claude/claude.provider.js +4 -0
  55. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  56. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +84 -0
  57. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
  58. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +54 -2
  59. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  60. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +21 -6
  61. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  62. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +50 -0
  63. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
  64. package/dist-server/server/modules/providers/list/codex/codex.provider.js +4 -0
  65. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  66. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +754 -0
  67. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
  68. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +8 -30
  69. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  70. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +187 -24
  71. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  72. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +28 -0
  73. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
  74. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +4 -0
  75. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
  76. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +125 -2
  77. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -1
  78. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +27 -0
  79. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -0
  80. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +24 -8
  81. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +1 -1
  82. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +10 -10
  83. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
  84. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +33 -0
  85. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -0
  86. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +4 -0
  87. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
  88. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +92 -0
  89. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
  90. package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
  91. package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
  92. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +267 -0
  93. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
  94. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +115 -0
  95. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
  96. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +410 -0
  97. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
  98. package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
  99. package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
  100. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +19 -0
  101. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
  102. package/dist-server/server/modules/providers/provider.registry.js +2 -0
  103. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  104. package/dist-server/server/modules/providers/provider.routes.js +65 -3
  105. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  106. package/dist-server/server/modules/providers/services/mcp.service.js +1 -9
  107. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
  108. package/dist-server/server/modules/providers/services/provider-models.service.js +199 -0
  109. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
  110. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +129 -13
  111. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  112. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +1 -0
  113. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  114. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +13 -4
  115. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  116. package/dist-server/server/modules/providers/services/sessions.service.js +81 -7
  117. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  118. package/dist-server/server/modules/providers/services/skills.service.js +11 -0
  119. package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
  120. package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
  121. package/dist-server/server/modules/providers/shared/skills/skills.provider.js +45 -0
  122. package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
  123. package/dist-server/server/modules/providers/tests/mcp.test.js +73 -6
  124. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
  125. package/dist-server/server/modules/providers/tests/opencode-models.test.js +66 -0
  126. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
  127. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +262 -0
  128. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
  129. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +270 -0
  130. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
  131. package/dist-server/server/modules/providers/tests/skills.test.js +259 -0
  132. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
  133. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +12 -1
  134. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  135. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +4 -4
  136. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -1
  137. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -1
  138. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  139. package/dist-server/server/openai-codex.js +87 -29
  140. package/dist-server/server/openai-codex.js.map +1 -1
  141. package/dist-server/server/opencode-cli.js +287 -0
  142. package/dist-server/server/opencode-cli.js.map +1 -0
  143. package/dist-server/server/opencode-cli.test.js +84 -0
  144. package/dist-server/server/opencode-cli.test.js.map +1 -0
  145. package/dist-server/server/routes/agent.js +21 -8
  146. package/dist-server/server/routes/agent.js.map +1 -1
  147. package/dist-server/server/routes/commands.js +204 -211
  148. package/dist-server/server/routes/commands.js.map +1 -1
  149. package/dist-server/server/routes/cursor.js +2 -2
  150. package/dist-server/server/routes/cursor.js.map +1 -1
  151. package/dist-server/server/routes/settings.js +0 -10
  152. package/dist-server/server/routes/settings.js.map +1 -1
  153. package/dist-server/server/routes/tests/commands.test.js +76 -0
  154. package/dist-server/server/routes/tests/commands.test.js.map +1 -0
  155. package/dist-server/server/shared/claude-cli-path.js +103 -0
  156. package/dist-server/server/shared/claude-cli-path.js.map +1 -0
  157. package/dist-server/server/shared/claude-cli-path.test.js +45 -0
  158. package/dist-server/server/shared/claude-cli-path.test.js.map +1 -0
  159. package/dist-server/server/{utils → shared}/frontmatter.js +3 -3
  160. package/dist-server/server/shared/frontmatter.js.map +1 -0
  161. package/dist-server/server/shared/utils.js +368 -0
  162. package/dist-server/server/shared/utils.js.map +1 -1
  163. package/dist-server/server/utils/commandParser.js +3 -3
  164. package/dist-server/server/utils/commandParser.js.map +1 -1
  165. package/package.json +3 -1
  166. package/public/api-docs.html +878 -0
  167. package/public/modelConstants.js +841 -0
  168. package/server/claude-sdk.js +74 -40
  169. package/server/cursor-cli.js +6 -11
  170. package/server/gemini-cli.js +197 -43
  171. package/server/gemini-response-handler.js +38 -0
  172. package/server/index.js +144 -19
  173. package/server/modules/database/index.ts +1 -0
  174. package/server/modules/database/migrations.ts +12 -0
  175. package/server/modules/database/repositories/projects.db.ts +13 -0
  176. package/server/modules/database/repositories/sessions.db.integration.test.ts +72 -0
  177. package/server/modules/database/repositories/sessions.db.ts +60 -9
  178. package/server/modules/database/schema.ts +1 -0
  179. package/server/modules/projects/projects.routes.ts +20 -3
  180. package/server/modules/projects/services/project-delete.service.ts +16 -1
  181. package/server/modules/projects/services/project-management.service.ts +2 -0
  182. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +71 -1
  183. package/server/modules/providers/README.md +354 -0
  184. package/server/modules/providers/index.ts +2 -1
  185. package/server/modules/providers/list/claude/claude-auth.provider.ts +38 -13
  186. package/server/modules/providers/list/claude/claude-models.provider.ts +191 -0
  187. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +68 -2
  188. package/server/modules/providers/list/claude/claude-sessions.provider.ts +175 -14
  189. package/server/modules/providers/list/claude/claude-skills.provider.ts +257 -0
  190. package/server/modules/providers/list/claude/claude.provider.ts +11 -1
  191. package/server/modules/providers/list/codex/codex-models.provider.ts +125 -0
  192. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +62 -2
  193. package/server/modules/providers/list/codex/codex-sessions.provider.ts +25 -6
  194. package/server/modules/providers/list/codex/codex-skills.provider.ts +60 -0
  195. package/server/modules/providers/list/codex/codex.provider.ts +11 -1
  196. package/server/modules/providers/list/cursor/cursor-models.provider.ts +820 -0
  197. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +17 -40
  198. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +216 -26
  199. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +31 -0
  200. package/server/modules/providers/list/cursor/cursor.provider.ts +11 -1
  201. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +158 -2
  202. package/server/modules/providers/list/gemini/gemini-models.provider.ts +42 -0
  203. package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +24 -20
  204. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +10 -11
  205. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +36 -0
  206. package/server/modules/providers/list/gemini/gemini.provider.ts +11 -1
  207. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +111 -0
  208. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
  209. package/server/modules/providers/list/opencode/opencode-models.provider.ts +339 -0
  210. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +157 -0
  211. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +506 -0
  212. package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
  213. package/server/modules/providers/list/opencode/opencode.provider.ts +27 -0
  214. package/server/modules/providers/provider.registry.ts +2 -0
  215. package/server/modules/providers/provider.routes.ts +97 -4
  216. package/server/modules/providers/services/mcp.service.ts +1 -12
  217. package/server/modules/providers/services/provider-models.service.ts +325 -0
  218. package/server/modules/providers/services/session-conversations-search.service.ts +162 -13
  219. package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
  220. package/server/modules/providers/services/sessions-watcher.service.ts +14 -4
  221. package/server/modules/providers/services/sessions.service.ts +112 -9
  222. package/server/modules/providers/services/skills.service.ts +15 -0
  223. package/server/modules/providers/shared/base/abstract.provider.ts +4 -0
  224. package/server/modules/providers/shared/skills/skills.provider.ts +64 -0
  225. package/server/modules/providers/tests/mcp.test.ts +93 -6
  226. package/server/modules/providers/tests/opencode-models.test.ts +73 -0
  227. package/server/modules/providers/tests/opencode-sessions.test.ts +334 -0
  228. package/server/modules/providers/tests/provider-models.service.test.ts +318 -0
  229. package/server/modules/providers/tests/skills.test.ts +512 -0
  230. package/server/modules/websocket/services/chat-websocket.service.ts +15 -1
  231. package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +4 -4
  232. package/server/modules/websocket/services/shell-websocket.service.ts +9 -0
  233. package/server/openai-codex.js +99 -30
  234. package/server/opencode-cli.js +336 -0
  235. package/server/opencode-cli.test.js +95 -0
  236. package/server/routes/agent.js +22 -8
  237. package/server/routes/commands.js +256 -233
  238. package/server/routes/cursor.js +2 -2
  239. package/server/routes/settings.js +1 -10
  240. package/server/routes/tests/commands.test.js +82 -0
  241. package/server/shared/claude-cli-path.test.ts +61 -0
  242. package/server/shared/claude-cli-path.ts +139 -0
  243. package/server/{utils/frontmatter.js → shared/frontmatter.ts} +3 -3
  244. package/server/shared/interfaces.ts +64 -0
  245. package/server/shared/types.ts +166 -1
  246. package/server/shared/utils.ts +478 -0
  247. package/server/utils/commandParser.js +5 -3
  248. package/dist/assets/index-6rYG_mRo.css +0 -32
  249. package/dist-server/server/utils/frontmatter.js.map +0 -1
  250. package/dist-server/shared/modelConstants.js +0 -98
  251. package/dist-server/shared/modelConstants.js.map +0 -1
  252. package/shared/modelConstants.js +0 -106
@@ -45,44 +45,28 @@ export class CursorSessionSynchronizer implements IProviderSessionSynchronizer {
45
45
  */
46
46
  async synchronize(since?: Date): Promise<number> {
47
47
  const projectsDir = path.join(this.cursorHome, 'projects');
48
- const projectEntries = await listDirectoryEntriesSafe(projectsDir);
49
- const seenProjectPaths = new Set<string>();
50
48
 
51
49
  let processed = 0;
52
- for (const entry of projectEntries) {
53
- if (!entry.isDirectory()) {
54
- continue;
55
- }
56
50
 
57
- const workerLogPath = path.join(projectsDir, entry.name, 'worker.log');
58
- const projectPath = await this.extractProjectPathFromWorkerLog(workerLogPath);
59
- if (!projectPath || seenProjectPaths.has(projectPath)) {
51
+ const files = await findFilesRecursivelyCreatedAfter(projectsDir, '.jsonl', since ?? null);
52
+
53
+ for (const filePath of files) {
54
+ const parsed = await this.processSessionFile(filePath);
55
+ if (!parsed) {
60
56
  continue;
61
57
  }
62
58
 
63
- seenProjectPaths.add(projectPath);
64
- const projectHash = this.md5(projectPath);
65
- const chatsDir = path.join(this.cursorHome, 'chats', projectHash);
66
- const files = await findFilesRecursivelyCreatedAfter(chatsDir, '.jsonl', since ?? null);
67
-
68
- for (const filePath of files) {
69
- const parsed = await this.processSessionFile(filePath);
70
- if (!parsed) {
71
- continue;
72
- }
73
-
74
- const timestamps = await readFileTimestamps(filePath);
75
- sessionsDb.createSession(
76
- parsed.sessionId,
77
- this.provider,
78
- parsed.projectPath,
79
- parsed.sessionName,
80
- timestamps.createdAt,
81
- timestamps.updatedAt,
82
- filePath
83
- );
84
- processed += 1;
85
- }
59
+ const timestamps = await readFileTimestamps(filePath);
60
+ sessionsDb.createSession(
61
+ parsed.sessionId,
62
+ this.provider,
63
+ parsed.projectPath,
64
+ parsed.sessionName,
65
+ timestamps.createdAt,
66
+ timestamps.updatedAt,
67
+ filePath
68
+ );
69
+ processed += 1;
86
70
  }
87
71
 
88
72
  return processed;
@@ -113,13 +97,6 @@ export class CursorSessionSynchronizer implements IProviderSessionSynchronizer {
113
97
  );
114
98
  }
115
99
 
116
- /**
117
- * Produces the same project hash Cursor uses in chat directory names.
118
- */
119
- private md5(input: string): string {
120
- return crypto.createHash('md5').update(input).digest('hex');
121
- }
122
-
123
100
  /**
124
101
  * Extracts project path from Cursor worker.log.
125
102
  */
@@ -149,7 +126,7 @@ export class CursorSessionSynchronizer implements IProviderSessionSynchronizer {
149
126
  */
150
127
  private async processSessionFile(filePath: string): Promise<ParsedSession | null> {
151
128
  const sessionId = path.basename(filePath, '.jsonl');
152
- const grandparentDir = path.dirname(path.dirname(filePath));
129
+ const grandparentDir = path.dirname(path.dirname(path.dirname(filePath)));
153
130
  const workerLogPath = path.join(grandparentDir, 'worker.log');
154
131
  const projectPath = await this.extractProjectPathFromWorkerLog(workerLogPath);
155
132
 
@@ -4,7 +4,12 @@ import path from 'node:path';
4
4
 
5
5
  import type { IProviderSessions } from '@/shared/interfaces.js';
6
6
  import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
7
- import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
7
+ import {
8
+ createNormalizedMessage,
9
+ generateMessageId,
10
+ readObjectRecord,
11
+ sanitizeLeafDirectoryName,
12
+ } from '@/shared/utils.js';
8
13
 
9
14
  const PROVIDER = 'cursor';
10
15
 
@@ -25,19 +30,162 @@ type CursorMessageBlob = {
25
30
  content: AnyRecord;
26
31
  };
27
32
 
28
- function sanitizeCursorSessionId(sessionId: string): string {
29
- const normalized = sessionId.trim();
30
- if (!normalized) {
31
- throw new Error('Cursor session id is required.');
33
+ function isInternalCursorText(value: unknown): boolean {
34
+ if (typeof value !== 'string') {
35
+ return false;
32
36
  }
33
37
 
34
- if (
35
- normalized.includes('..')
36
- || normalized.includes(path.posix.sep)
37
- || normalized.includes(path.win32.sep)
38
- || normalized !== path.basename(normalized)
39
- ) {
40
- throw new Error(`Invalid cursor session id "${sessionId}".`);
38
+ const normalized = value.trim();
39
+ return normalized.startsWith('<user_info>') || normalized.startsWith('<system_reminder>');
40
+ }
41
+
42
+ function isInternalCursorPart(part: unknown): boolean {
43
+ if (!part || typeof part !== 'object') {
44
+ return false;
45
+ }
46
+
47
+ const record = part as AnyRecord;
48
+ const type = typeof record.type === 'string' ? record.type : '';
49
+ if (type === 'user_info' || type === 'system_reminder') {
50
+ return true;
51
+ }
52
+
53
+ return isInternalCursorText(record.text);
54
+ }
55
+
56
+ function unwrapUserQueryText(value: string, role: 'user' | 'assistant'): string {
57
+ if (role !== 'user') {
58
+ return value;
59
+ }
60
+
61
+ const normalized = value.trimStart();
62
+ const openTag = '<user_query>';
63
+ const closeTag = '</user_query>';
64
+ if (!normalized.startsWith(openTag)) {
65
+ return value;
66
+ }
67
+
68
+ const afterOpen = normalized.slice(openTag.length);
69
+ const closeIndex = afterOpen.lastIndexOf(closeTag);
70
+ const inner = closeIndex >= 0 ? afterOpen.slice(0, closeIndex) : afterOpen;
71
+ return inner.trim();
72
+ }
73
+
74
+ function normalizeToolId(value: unknown): string | null {
75
+ if (typeof value !== 'string') {
76
+ return null;
77
+ }
78
+
79
+ const normalized = value.trim();
80
+ return normalized ? normalized : null;
81
+ }
82
+
83
+ function extractCursorToolResultContent(item: AnyRecord): string {
84
+ if (typeof item.result === 'string' && item.result.trim()) {
85
+ return item.result;
86
+ }
87
+
88
+ if (typeof item.output === 'string' && item.output.trim()) {
89
+ return item.output;
90
+ }
91
+
92
+ if (Array.isArray(item.experimental_content)) {
93
+ const experimentalText = item.experimental_content
94
+ .map((part: unknown) => {
95
+ if (typeof part === 'string') {
96
+ return part;
97
+ }
98
+ if (part && typeof part === 'object') {
99
+ const record = part as AnyRecord;
100
+ if (typeof record.text === 'string') {
101
+ return record.text;
102
+ }
103
+ }
104
+ return '';
105
+ })
106
+ .filter(Boolean)
107
+ .join('\n');
108
+
109
+ if (experimentalText.trim()) {
110
+ return experimentalText;
111
+ }
112
+ }
113
+
114
+ return typeof item.result === 'string' ? item.result : '';
115
+ }
116
+
117
+ function parseCursorToolInput(rawInput: unknown): unknown {
118
+ if (typeof rawInput !== 'string') {
119
+ return rawInput;
120
+ }
121
+
122
+ const trimmed = rawInput.trim();
123
+ if (!trimmed) {
124
+ return rawInput;
125
+ }
126
+
127
+ try {
128
+ return JSON.parse(trimmed);
129
+ } catch {
130
+ return rawInput;
131
+ }
132
+ }
133
+
134
+ function normalizeCursorToolInput(toolName: string, rawInput: unknown): unknown {
135
+ const parsed = parseCursorToolInput(rawInput);
136
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
137
+ return parsed;
138
+ }
139
+
140
+ const input = parsed as AnyRecord;
141
+ const normalized: AnyRecord = { ...input };
142
+
143
+ const filePath = input.file_path
144
+ ?? input.filePath
145
+ ?? input.path
146
+ ?? input.file
147
+ ?? input.filename;
148
+ if (typeof filePath === 'string' && filePath.trim()) {
149
+ normalized.file_path = filePath;
150
+ }
151
+
152
+ if (toolName === 'Write') {
153
+ const content = input.content
154
+ ?? input.text
155
+ ?? input.value
156
+ ?? input.contents
157
+ ?? input.fileContent
158
+ ?? input.new_string
159
+ ?? input.newString;
160
+ if (typeof content === 'string') {
161
+ normalized.content = content;
162
+ }
163
+ }
164
+
165
+ if (toolName === 'Edit') {
166
+ const oldString = input.old_string
167
+ ?? input.oldString
168
+ ?? input.old
169
+ ?? '';
170
+ const newString = input.new_string
171
+ ?? input.newString
172
+ ?? input.new
173
+ ?? input.content
174
+ ?? '';
175
+
176
+ if (typeof oldString === 'string') {
177
+ normalized.old_string = oldString;
178
+ }
179
+ if (typeof newString === 'string') {
180
+ normalized.new_string = newString;
181
+ }
182
+ }
183
+
184
+ if (toolName === 'ApplyPatch') {
185
+ const patch = input.patch ?? input.diff ?? input.content;
186
+ if (typeof patch === 'string' && !normalized.patch) {
187
+ normalized.patch = patch;
188
+ }
41
189
  }
42
190
 
43
191
  return normalized;
@@ -53,7 +201,7 @@ export class CursorSessionsProvider implements IProviderSessions {
53
201
  const { default: Database } = await import('better-sqlite3');
54
202
 
55
203
  const cwdId = crypto.createHash('md5').update(projectPath || process.cwd()).digest('hex');
56
- const safeSessionId = sanitizeCursorSessionId(sessionId);
204
+ const safeSessionId = sanitizeLeafDirectoryName(sessionId, 'cursor session id');
57
205
  const baseChatsPath = path.join(os.homedir(), '.cursor', 'chats', cwdId);
58
206
  const storeDbPath = path.join(baseChatsPath, safeSessionId, 'store.db');
59
207
  const resolvedBaseChatsPath = path.resolve(baseChatsPath);
@@ -225,13 +373,14 @@ export class CursorSessionsProvider implements IProviderSessions {
225
373
  try {
226
374
  const blobs = await this.loadCursorBlobs(sessionId, projectPath);
227
375
  const allNormalized = this.normalizeCursorBlobs(blobs, sessionId);
228
- const total = allNormalized.length;
376
+ const renderableMessages = allNormalized.filter((msg) => msg.kind !== 'tool_result');
377
+ const total = renderableMessages.length;
229
378
 
230
379
  if (limit !== null) {
231
380
  const start = offset;
232
381
  const page = limit === 0
233
382
  ? []
234
- : allNormalized.slice(start, start + limit);
383
+ : renderableMessages.slice(start, start + limit);
235
384
  const hasMore = limit === 0
236
385
  ? start < total
237
386
  : start + limit < total;
@@ -245,7 +394,7 @@ export class CursorSessionsProvider implements IProviderSessions {
245
394
  }
246
395
 
247
396
  return {
248
- messages: allNormalized,
397
+ messages: renderableMessages,
249
398
  total,
250
399
  hasMore: false,
251
400
  offset: 0,
@@ -283,11 +432,24 @@ export class CursorSessionsProvider implements IProviderSessions {
283
432
  let text = '';
284
433
  if (Array.isArray(content.message.content)) {
285
434
  text = content.message.content
286
- .map((part: string | AnyRecord) => typeof part === 'string' ? part : part?.text || '')
435
+ .map((part: string | AnyRecord) => {
436
+ if (typeof part === 'string') {
437
+ if (isInternalCursorText(part)) {
438
+ return '';
439
+ }
440
+ return unwrapUserQueryText(part, role);
441
+ }
442
+ if (isInternalCursorPart(part)) {
443
+ return '';
444
+ }
445
+ return unwrapUserQueryText(part?.text || '', role);
446
+ })
287
447
  .filter(Boolean)
288
448
  .join('\n');
289
449
  } else if (typeof content.message.content === 'string') {
290
- text = content.message.content;
450
+ if (!isInternalCursorText(content.message.content)) {
451
+ text = unwrapUserQueryText(content.message.content, role);
452
+ }
291
453
  }
292
454
  if (text?.trim()) {
293
455
  messages.push(createNormalizedMessage({
@@ -316,7 +478,14 @@ export class CursorSessionsProvider implements IProviderSessions {
316
478
  if (item?.type !== 'tool-result') {
317
479
  continue;
318
480
  }
319
- const toolCallId = item.toolCallId || content.id;
481
+ const cursorOptions = content.providerOptions?.cursor as AnyRecord | undefined;
482
+ const highLevelToolCallResult = cursorOptions?.highLevelToolCallResult;
483
+ const toolCallId = normalizeToolId(item.toolCallId)
484
+ || normalizeToolId(item.tool_call_id)
485
+ || normalizeToolId(highLevelToolCallResult?.toolCallId)
486
+ || normalizeToolId(highLevelToolCallResult?.tool_call_id)
487
+ || normalizeToolId(content.id)
488
+ || '';
320
489
  messages.push(createNormalizedMessage({
321
490
  id: `${baseId}_tr`,
322
491
  sessionId,
@@ -324,8 +493,9 @@ export class CursorSessionsProvider implements IProviderSessions {
324
493
  provider: PROVIDER,
325
494
  kind: 'tool_result',
326
495
  toolId: toolCallId,
327
- content: item.result || '',
328
- isError: false,
496
+ content: extractCursorToolResultContent(item),
497
+ isError: Boolean(item.isError || item.is_error),
498
+ toolUseResult: highLevelToolCallResult,
329
499
  }));
330
500
  }
331
501
  continue;
@@ -336,8 +506,15 @@ export class CursorSessionsProvider implements IProviderSessions {
336
506
  if (Array.isArray(content.content)) {
337
507
  for (let partIdx = 0; partIdx < content.content.length; partIdx++) {
338
508
  const part = content.content[partIdx];
509
+ if (isInternalCursorPart(part)) {
510
+ continue;
511
+ }
339
512
 
340
513
  if (part?.type === 'text' && part?.text) {
514
+ const normalizedPartText = unwrapUserQueryText(part.text, role);
515
+ if (!normalizedPartText) {
516
+ continue;
517
+ }
341
518
  messages.push(createNormalizedMessage({
342
519
  id: `${baseId}_${partIdx}`,
343
520
  sessionId,
@@ -345,7 +522,7 @@ export class CursorSessionsProvider implements IProviderSessions {
345
522
  provider: PROVIDER,
346
523
  kind: 'text',
347
524
  role,
348
- content: part.text,
525
+ content: normalizedPartText,
349
526
  sequence: blob.sequence,
350
527
  rowid: blob.rowid,
351
528
  }));
@@ -361,7 +538,11 @@ export class CursorSessionsProvider implements IProviderSessions {
361
538
  } else if (part?.type === 'tool-call' || part?.type === 'tool_use') {
362
539
  const rawToolName = part.toolName || part.name || 'Unknown Tool';
363
540
  const toolName = rawToolName === 'ApplyPatch' ? 'Edit' : rawToolName;
364
- const toolId = part.toolCallId || part.id || `tool_${i}_${partIdx}`;
541
+ const toolId = normalizeToolId(part.toolCallId)
542
+ || normalizeToolId(part.tool_call_id)
543
+ || normalizeToolId(part.id)
544
+ || `tool_${i}_${partIdx}`;
545
+ const normalizedToolInput = normalizeCursorToolInput(rawToolName, part.args ?? part.input);
365
546
  const message = createNormalizedMessage({
366
547
  id: `${baseId}_${partIdx}`,
367
548
  sessionId,
@@ -369,14 +550,22 @@ export class CursorSessionsProvider implements IProviderSessions {
369
550
  provider: PROVIDER,
370
551
  kind: 'tool_use',
371
552
  toolName,
372
- toolInput: part.args || part.input,
553
+ toolInput: normalizedToolInput,
373
554
  toolId,
374
555
  });
375
556
  messages.push(message);
376
557
  toolUseMap.set(toolId, message);
377
558
  }
378
559
  }
379
- } else if (typeof content.content === 'string' && content.content.trim()) {
560
+ } else if (
561
+ typeof content.content === 'string'
562
+ && content.content.trim()
563
+ && !isInternalCursorText(content.content)
564
+ ) {
565
+ const normalizedText = unwrapUserQueryText(content.content, role);
566
+ if (!normalizedText) {
567
+ continue;
568
+ }
380
569
  messages.push(createNormalizedMessage({
381
570
  id: baseId,
382
571
  sessionId,
@@ -384,7 +573,7 @@ export class CursorSessionsProvider implements IProviderSessions {
384
573
  provider: PROVIDER,
385
574
  kind: 'text',
386
575
  role,
387
- content: content.content,
576
+ content: normalizedText,
388
577
  sequence: blob.sequence,
389
578
  rowid: blob.rowid,
390
579
  }));
@@ -401,6 +590,7 @@ export class CursorSessionsProvider implements IProviderSessions {
401
590
  toolUse.toolResult = {
402
591
  content: msg.content,
403
592
  isError: msg.isError,
593
+ toolUseResult: msg.toolUseResult,
404
594
  };
405
595
  }
406
596
  }
@@ -0,0 +1,31 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ import { SkillsProvider } from '@/modules/providers/shared/skills/skills.provider.js';
5
+ import type { ProviderSkillSource } from '@/shared/types.js';
6
+
7
+ export class CursorSkillsProvider extends SkillsProvider {
8
+ constructor() {
9
+ super('cursor');
10
+ }
11
+
12
+ protected async getSkillSources(workspacePath: string): Promise<ProviderSkillSource[]> {
13
+ return [
14
+ {
15
+ scope: 'project',
16
+ rootDir: path.join(workspacePath, '.agents', 'skills'),
17
+ commandPrefix: '/',
18
+ },
19
+ {
20
+ scope: 'project',
21
+ rootDir: path.join(workspacePath, '.cursor', 'skills'),
22
+ commandPrefix: '/',
23
+ },
24
+ {
25
+ scope: 'user',
26
+ rootDir: path.join(os.homedir(), '.cursor', 'skills'),
27
+ commandPrefix: '/',
28
+ },
29
+ ];
30
+ }
31
+ }
@@ -1,13 +1,23 @@
1
1
  import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
2
2
  import { CursorProviderAuth } from '@/modules/providers/list/cursor/cursor-auth.provider.js';
3
+ import { CursorProviderModels } from '@/modules/providers/list/cursor/cursor-models.provider.js';
3
4
  import { CursorMcpProvider } from '@/modules/providers/list/cursor/cursor-mcp.provider.js';
4
5
  import { CursorSessionSynchronizer } from '@/modules/providers/list/cursor/cursor-session-synchronizer.provider.js';
5
6
  import { CursorSessionsProvider } from '@/modules/providers/list/cursor/cursor-sessions.provider.js';
6
- import type { IProviderAuth, IProviderSessionSynchronizer, IProviderSessions } from '@/shared/interfaces.js';
7
+ import { CursorSkillsProvider } from '@/modules/providers/list/cursor/cursor-skills.provider.js';
8
+ import type {
9
+ IProviderAuth,
10
+ IProviderModels,
11
+ IProviderSessionSynchronizer,
12
+ IProviderSkills,
13
+ IProviderSessions,
14
+ } from '@/shared/interfaces.js';
7
15
 
8
16
  export class CursorProvider extends AbstractProvider {
17
+ readonly models: IProviderModels = new CursorProviderModels();
9
18
  readonly mcp = new CursorMcpProvider();
10
19
  readonly auth: IProviderAuth = new CursorProviderAuth();
20
+ readonly skills: IProviderSkills = new CursorSkillsProvider();
11
21
  readonly sessions: IProviderSessions = new CursorSessionsProvider();
12
22
  readonly sessionSynchronizer: IProviderSessionSynchronizer = new CursorSessionSynchronizer();
13
23
 
@@ -15,7 +15,24 @@ type GeminiCredentialsStatus = {
15
15
  error?: string;
16
16
  };
17
17
 
18
+ type GeminiAuthType =
19
+ | 'oauth-personal'
20
+ | 'gemini-api-key'
21
+ | 'vertex-ai'
22
+ | 'compute-default-credentials'
23
+ | 'gateway'
24
+ | 'cloud-shell'
25
+ | null;
26
+
18
27
  export class GeminiProviderAuth implements IProviderAuth {
28
+ /**
29
+ * Gemini CLI can override its home root via GEMINI_CLI_HOME.
30
+ * Use the same resolution so status checks match runtime behavior.
31
+ */
32
+ private getGeminiCliHome(): string {
33
+ return process.env.GEMINI_CLI_HOME?.trim() || os.homedir();
34
+ }
35
+
19
36
  /**
20
37
  * Checks whether the Gemini CLI is available on this host.
21
38
  */
@@ -58,6 +75,88 @@ export class GeminiProviderAuth implements IProviderAuth {
58
75
  };
59
76
  }
60
77
 
78
+ /**
79
+ * Parses dotenv-style key/value pairs.
80
+ */
81
+ private parseEnvFile(content: string): Record<string, string> {
82
+ const parsed: Record<string, string> = {};
83
+
84
+ for (const rawLine of content.split(/\r?\n/)) {
85
+ const line = rawLine.trim();
86
+ if (!line || line.startsWith('#')) {
87
+ continue;
88
+ }
89
+
90
+ const normalizedLine = line.startsWith('export ')
91
+ ? line.slice('export '.length).trim()
92
+ : line;
93
+ const separatorIndex = normalizedLine.indexOf('=');
94
+ if (separatorIndex <= 0) {
95
+ continue;
96
+ }
97
+
98
+ const key = normalizedLine.slice(0, separatorIndex).trim();
99
+ if (!key) {
100
+ continue;
101
+ }
102
+
103
+ let value = normalizedLine.slice(separatorIndex + 1).trim();
104
+ const quoted = (value.startsWith('"') && value.endsWith('"')) || (value.startsWith('\'') && value.endsWith('\''));
105
+ if (quoted) {
106
+ value = value.slice(1, -1);
107
+ } else {
108
+ value = value.replace(/\s+#.*$/, '').trim();
109
+ }
110
+
111
+ parsed[key] = value;
112
+ }
113
+
114
+ return parsed;
115
+ }
116
+
117
+ /**
118
+ * Loads user-level auth env in Gemini's "first file found" order.
119
+ */
120
+ private async loadUserLevelAuthEnv(): Promise<Record<string, string>> {
121
+ const geminiCliHome = this.getGeminiCliHome();
122
+ const envCandidates = [
123
+ path.join(geminiCliHome, '.gemini', '.env'),
124
+ path.join(geminiCliHome, '.env'),
125
+ ];
126
+
127
+ for (const envPath of envCandidates) {
128
+ try {
129
+ const content = await readFile(envPath, 'utf8');
130
+ return this.parseEnvFile(content);
131
+ } catch {
132
+ // Continue to the next fallback.
133
+ }
134
+ }
135
+
136
+ return {};
137
+ }
138
+
139
+ /**
140
+ * Reads Gemini's selected auth type from settings.json when available.
141
+ */
142
+ private async readSelectedAuthType(): Promise<GeminiAuthType> {
143
+ try {
144
+ const settingsPath = path.join(this.getGeminiCliHome(), '.gemini', 'settings.json');
145
+ const content = await readFile(settingsPath, 'utf8');
146
+ const settings = readObjectRecord(JSON.parse(content));
147
+ const security = readObjectRecord(settings?.security);
148
+ const auth = readObjectRecord(security?.auth);
149
+ const selectedType = readOptionalString(auth?.selectedType);
150
+ if (!selectedType) {
151
+ return null;
152
+ }
153
+
154
+ return selectedType as GeminiAuthType;
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+
61
160
  /**
62
161
  * Checks Gemini credentials from API key env vars or local OAuth credential files.
63
162
  */
@@ -66,8 +165,46 @@ export class GeminiProviderAuth implements IProviderAuth {
66
165
  return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
67
166
  }
68
167
 
168
+ const userEnv = await this.loadUserLevelAuthEnv();
169
+ if (readOptionalString(userEnv.GEMINI_API_KEY)) {
170
+ return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
171
+ }
172
+
173
+ const selectedType = await this.readSelectedAuthType();
174
+ if (selectedType === 'vertex-ai') {
175
+ const hasGoogleApiKey = Boolean(
176
+ process.env.GOOGLE_API_KEY?.trim()
177
+ || readOptionalString(userEnv.GOOGLE_API_KEY)
178
+ );
179
+ const hasProject = Boolean(
180
+ process.env.GOOGLE_CLOUD_PROJECT?.trim()
181
+ || process.env.GOOGLE_CLOUD_PROJECT_ID?.trim()
182
+ || readOptionalString(userEnv.GOOGLE_CLOUD_PROJECT)
183
+ || readOptionalString(userEnv.GOOGLE_CLOUD_PROJECT_ID)
184
+ );
185
+ const hasLocation = Boolean(
186
+ process.env.GOOGLE_CLOUD_LOCATION?.trim()
187
+ || readOptionalString(userEnv.GOOGLE_CLOUD_LOCATION)
188
+ );
189
+ const hasServiceAccount = Boolean(
190
+ process.env.GOOGLE_APPLICATION_CREDENTIALS?.trim()
191
+ || readOptionalString(userEnv.GOOGLE_APPLICATION_CREDENTIALS)
192
+ );
193
+
194
+ if (hasGoogleApiKey || hasServiceAccount || (hasProject && hasLocation)) {
195
+ return { authenticated: true, email: 'Vertex AI Auth', method: 'vertex_ai' };
196
+ }
197
+
198
+ return {
199
+ authenticated: false,
200
+ email: null,
201
+ method: 'vertex_ai',
202
+ error: 'Gemini is set to Vertex AI, but required env vars are missing',
203
+ };
204
+ }
205
+
69
206
  try {
70
- const credsPath = path.join(os.homedir(), '.gemini', 'oauth_creds.json');
207
+ const credsPath = path.join(this.getGeminiCliHome(), '.gemini', 'oauth_creds.json');
71
208
  const content = await readFile(credsPath, 'utf8');
72
209
  const creds = readObjectRecord(JSON.parse(content)) ?? {};
73
210
  const accessToken = readOptionalString(creds.access_token);
@@ -106,6 +243,25 @@ export class GeminiProviderAuth implements IProviderAuth {
106
243
  method: 'credentials_file',
107
244
  };
108
245
  } catch {
246
+ if (selectedType === 'gemini-api-key') {
247
+ return {
248
+ authenticated: false,
249
+ email: null,
250
+ method: 'api_key',
251
+ error: 'Gemini is set to "Use Gemini API key", but GEMINI_API_KEY is unavailable',
252
+ };
253
+ }
254
+
255
+ if (selectedType === 'oauth-personal') {
256
+ return {
257
+ authenticated: false,
258
+ email: null,
259
+ method: 'credentials_file',
260
+ error: 'Gemini is set to Google sign-in, but no cached OAuth credentials were found',
261
+ };
262
+ }
263
+
264
+ // If no explicit auth type was selected, surface the generic "not configured" error.
109
265
  return {
110
266
  authenticated: false,
111
267
  email: null,
@@ -140,7 +296,7 @@ export class GeminiProviderAuth implements IProviderAuth {
140
296
  */
141
297
  private async getActiveAccountEmail(): Promise<string | null> {
142
298
  try {
143
- const accPath = path.join(os.homedir(), '.gemini', 'google_accounts.json');
299
+ const accPath = path.join(this.getGeminiCliHome(), '.gemini', 'google_accounts.json');
144
300
  const accContent = await readFile(accPath, 'utf8');
145
301
  const accounts = readObjectRecord(JSON.parse(accContent));
146
302
  return readOptionalString(accounts?.active) ?? null;