@cloudcli-ai/cloudcli 1.31.5 → 1.32.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 (147) hide show
  1. package/dist/assets/index-AaSE7-3t.js +1336 -0
  2. package/dist/assets/index-CKaku72N.css +32 -0
  3. package/dist/index.html +2 -2
  4. package/dist-server/server/claude-sdk.js +10 -5
  5. package/dist-server/server/claude-sdk.js.map +1 -1
  6. package/dist-server/server/cursor-cli.js +0 -6
  7. package/dist-server/server/cursor-cli.js.map +1 -1
  8. package/dist-server/server/gemini-cli.js +160 -34
  9. package/dist-server/server/gemini-cli.js.map +1 -1
  10. package/dist-server/server/modules/database/migrations.js +11 -0
  11. package/dist-server/server/modules/database/migrations.js.map +1 -1
  12. package/dist-server/server/modules/database/repositories/projects.db.js +12 -0
  13. package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
  14. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js +61 -0
  15. package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +1 -0
  16. package/dist-server/server/modules/database/repositories/sessions.db.js +49 -8
  17. package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
  18. package/dist-server/server/modules/database/schema.js +1 -0
  19. package/dist-server/server/modules/database/schema.js.map +1 -1
  20. package/dist-server/server/modules/projects/projects.routes.js +12 -3
  21. package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
  22. package/dist-server/server/modules/projects/services/project-delete.service.js +14 -1
  23. package/dist-server/server/modules/projects/services/project-delete.service.js.map +1 -1
  24. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +43 -0
  25. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
  26. package/dist-server/server/modules/providers/index.js +1 -0
  27. package/dist-server/server/modules/providers/index.js.map +1 -1
  28. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +2 -1
  29. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  30. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +55 -2
  31. package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
  32. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +153 -14
  33. package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
  34. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +198 -0
  35. package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
  36. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  37. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  38. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +54 -2
  39. package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
  40. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +21 -6
  41. package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
  42. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +82 -0
  43. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
  44. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  45. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  46. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js +8 -30
  47. package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
  48. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +189 -13
  49. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  50. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +28 -0
  51. package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
  52. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
  53. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
  54. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +125 -2
  55. package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js.map +1 -1
  56. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js +24 -8
  57. package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +1 -1
  58. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +7 -1
  59. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
  60. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +33 -0
  61. package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -0
  62. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +2 -0
  63. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
  64. package/dist-server/server/modules/providers/provider.routes.js +23 -2
  65. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  66. package/dist-server/server/modules/providers/services/session-conversations-search.service.js +129 -13
  67. package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
  68. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +7 -5
  69. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  70. package/dist-server/server/modules/providers/services/sessions.service.js +81 -7
  71. package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
  72. package/dist-server/server/modules/providers/services/skills.service.js +11 -0
  73. package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
  74. package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
  75. package/dist-server/server/modules/providers/shared/skills/skills.provider.js +45 -0
  76. package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
  77. package/dist-server/server/modules/providers/tests/skills.test.js +226 -0
  78. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
  79. package/dist-server/server/openai-codex.js +56 -26
  80. package/dist-server/server/openai-codex.js.map +1 -1
  81. package/dist-server/server/routes/commands.js +5 -5
  82. package/dist-server/server/routes/commands.js.map +1 -1
  83. package/dist-server/server/shared/claude-cli-path.js +103 -0
  84. package/dist-server/server/shared/claude-cli-path.js.map +1 -0
  85. package/dist-server/server/shared/claude-cli-path.test.js +45 -0
  86. package/dist-server/server/shared/claude-cli-path.test.js.map +1 -0
  87. package/dist-server/server/{utils → shared}/frontmatter.js +3 -3
  88. package/dist-server/server/shared/frontmatter.js.map +1 -0
  89. package/dist-server/server/shared/utils.js +82 -0
  90. package/dist-server/server/shared/utils.js.map +1 -1
  91. package/dist-server/server/utils/commandParser.js +3 -3
  92. package/dist-server/server/utils/commandParser.js.map +1 -1
  93. package/dist-server/shared/modelConstants.js +1 -0
  94. package/dist-server/shared/modelConstants.js.map +1 -1
  95. package/package.json +1 -1
  96. package/server/claude-sdk.js +10 -5
  97. package/server/cursor-cli.js +0 -8
  98. package/server/gemini-cli.js +190 -42
  99. package/server/modules/database/migrations.ts +12 -0
  100. package/server/modules/database/repositories/projects.db.ts +13 -0
  101. package/server/modules/database/repositories/sessions.db.integration.test.ts +72 -0
  102. package/server/modules/database/repositories/sessions.db.ts +60 -9
  103. package/server/modules/database/schema.ts +1 -0
  104. package/server/modules/projects/projects.routes.ts +20 -3
  105. package/server/modules/projects/services/project-delete.service.ts +16 -1
  106. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +64 -0
  107. package/server/modules/providers/README.md +346 -0
  108. package/server/modules/providers/index.ts +2 -1
  109. package/server/modules/providers/list/claude/claude-auth.provider.ts +8 -7
  110. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +68 -2
  111. package/server/modules/providers/list/claude/claude-sessions.provider.ts +175 -14
  112. package/server/modules/providers/list/claude/claude-skills.provider.ts +257 -0
  113. package/server/modules/providers/list/claude/claude.provider.ts +8 -1
  114. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +62 -2
  115. package/server/modules/providers/list/codex/codex-sessions.provider.ts +25 -6
  116. package/server/modules/providers/list/codex/codex-skills.provider.ts +100 -0
  117. package/server/modules/providers/list/codex/codex.provider.ts +8 -1
  118. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +17 -40
  119. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +216 -13
  120. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +31 -0
  121. package/server/modules/providers/list/cursor/cursor.provider.ts +8 -1
  122. package/server/modules/providers/list/gemini/gemini-auth.provider.ts +158 -2
  123. package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +24 -20
  124. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +7 -1
  125. package/server/modules/providers/list/gemini/gemini-skills.provider.ts +36 -0
  126. package/server/modules/providers/list/gemini/gemini.provider.ts +8 -1
  127. package/server/modules/providers/provider.routes.ts +35 -2
  128. package/server/modules/providers/services/session-conversations-search.service.ts +162 -13
  129. package/server/modules/providers/services/sessions-watcher.service.ts +7 -5
  130. package/server/modules/providers/services/sessions.service.ts +112 -9
  131. package/server/modules/providers/services/skills.service.ts +15 -0
  132. package/server/modules/providers/shared/base/abstract.provider.ts +2 -0
  133. package/server/modules/providers/shared/skills/skills.provider.ts +64 -0
  134. package/server/modules/providers/tests/skills.test.ts +446 -0
  135. package/server/openai-codex.js +59 -27
  136. package/server/routes/commands.js +7 -5
  137. package/server/shared/claude-cli-path.test.ts +61 -0
  138. package/server/shared/claude-cli-path.ts +139 -0
  139. package/server/{utils/frontmatter.js → shared/frontmatter.ts} +3 -3
  140. package/server/shared/interfaces.ts +19 -0
  141. package/server/shared/types.ts +78 -0
  142. package/server/shared/utils.ts +94 -0
  143. package/server/utils/commandParser.js +5 -3
  144. package/shared/modelConstants.js +1 -0
  145. package/dist/assets/index-6rYG_mRo.css +0 -32
  146. package/dist/assets/index-DqMVUeZS.js +0 -1331
  147. package/dist-server/server/utils/frontmatter.js.map +0 -1
@@ -0,0 +1,100 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import { SkillsProvider } from '@/modules/providers/shared/skills/skills.provider.js';
6
+ import type { ProviderSkillSource } from '@/shared/types.js';
7
+
8
+ const hasGitMarker = async (dirPath: string): Promise<boolean> => {
9
+ try {
10
+ const gitMarkerStats = await fs.stat(path.join(dirPath, '.git'));
11
+ return gitMarkerStats.isDirectory() || gitMarkerStats.isFile();
12
+ } catch {
13
+ return false;
14
+ }
15
+ };
16
+
17
+ const findTopmostGitRoot = async (startPath: string): Promise<string | null> => {
18
+ let currentPath = path.resolve(startPath);
19
+ let topmostGitRoot: string | null = null;
20
+
21
+ while (true) {
22
+ if (await hasGitMarker(currentPath)) {
23
+ topmostGitRoot = currentPath;
24
+ }
25
+
26
+ const parentPath = path.dirname(currentPath);
27
+ if (parentPath === currentPath) {
28
+ break;
29
+ }
30
+
31
+ currentPath = parentPath;
32
+ }
33
+
34
+ return topmostGitRoot;
35
+ };
36
+
37
+ const addUniqueSource = (
38
+ sources: ProviderSkillSource[],
39
+ seenRootDirs: Set<string>,
40
+ source: ProviderSkillSource,
41
+ ): void => {
42
+ const normalizedRootDir = path.resolve(source.rootDir);
43
+ if (seenRootDirs.has(normalizedRootDir)) {
44
+ return;
45
+ }
46
+
47
+ seenRootDirs.add(normalizedRootDir);
48
+ sources.push({ ...source, rootDir: normalizedRootDir });
49
+ };
50
+
51
+ export class CodexSkillsProvider extends SkillsProvider {
52
+ constructor() {
53
+ super('codex');
54
+ }
55
+
56
+ protected async getSkillSources(workspacePath: string): Promise<ProviderSkillSource[]> {
57
+ const sources: ProviderSkillSource[] = [];
58
+ const seenRootDirs = new Set<string>();
59
+ const repoRoot = await findTopmostGitRoot(workspacePath);
60
+
61
+ addUniqueSource(sources, seenRootDirs, {
62
+ scope: 'repo',
63
+ rootDir: path.join(workspacePath, '.agents', 'skills'),
64
+ commandPrefix: '$',
65
+ });
66
+
67
+ if (repoRoot) {
68
+ // Codex checks repository skills at the launch folder, one folder above it,
69
+ // and the topmost git root; these can collapse to the same directory.
70
+ addUniqueSource(sources, seenRootDirs, {
71
+ scope: 'repo',
72
+ rootDir: path.join(path.dirname(workspacePath), '.agents', 'skills'),
73
+ commandPrefix: '$',
74
+ });
75
+ addUniqueSource(sources, seenRootDirs, {
76
+ scope: 'repo',
77
+ rootDir: path.join(repoRoot, '.agents', 'skills'),
78
+ commandPrefix: '$',
79
+ });
80
+ }
81
+
82
+ addUniqueSource(sources, seenRootDirs, {
83
+ scope: 'user',
84
+ rootDir: path.join(os.homedir(), '.agents', 'skills'),
85
+ commandPrefix: '$',
86
+ });
87
+ addUniqueSource(sources, seenRootDirs, {
88
+ scope: 'admin',
89
+ rootDir: path.join('/etc', 'codex', 'skills'),
90
+ commandPrefix: '$',
91
+ });
92
+ addUniqueSource(sources, seenRootDirs, {
93
+ scope: 'system',
94
+ rootDir: path.join(os.homedir(), '.codex', 'skills', '.system'),
95
+ commandPrefix: '$',
96
+ });
97
+
98
+ return sources;
99
+ }
100
+ }
@@ -3,11 +3,18 @@ import { CodexProviderAuth } from '@/modules/providers/list/codex/codex-auth.pro
3
3
  import { CodexMcpProvider } from '@/modules/providers/list/codex/codex-mcp.provider.js';
4
4
  import { CodexSessionSynchronizer } from '@/modules/providers/list/codex/codex-session-synchronizer.provider.js';
5
5
  import { CodexSessionsProvider } from '@/modules/providers/list/codex/codex-sessions.provider.js';
6
- import type { IProviderAuth, IProviderSessionSynchronizer, IProviderSessions } from '@/shared/interfaces.js';
6
+ import { CodexSkillsProvider } from '@/modules/providers/list/codex/codex-skills.provider.js';
7
+ import type {
8
+ IProviderAuth,
9
+ IProviderSessionSynchronizer,
10
+ IProviderSkills,
11
+ IProviderSessions,
12
+ } from '@/shared/interfaces.js';
7
13
 
8
14
  export class CodexProvider extends AbstractProvider {
9
15
  readonly mcp = new CodexMcpProvider();
10
16
  readonly auth: IProviderAuth = new CodexProviderAuth();
17
+ readonly skills: IProviderSkills = new CodexSkillsProvider();
11
18
  readonly sessions: IProviderSessions = new CodexSessionsProvider();
12
19
  readonly sessionSynchronizer: IProviderSessionSynchronizer = new CodexSessionSynchronizer();
13
20
 
@@ -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
 
@@ -25,6 +25,167 @@ type CursorMessageBlob = {
25
25
  content: AnyRecord;
26
26
  };
27
27
 
28
+ function isInternalCursorText(value: unknown): boolean {
29
+ if (typeof value !== 'string') {
30
+ return false;
31
+ }
32
+
33
+ const normalized = value.trim();
34
+ return normalized.startsWith('<user_info>') || normalized.startsWith('<system_reminder>');
35
+ }
36
+
37
+ function isInternalCursorPart(part: unknown): boolean {
38
+ if (!part || typeof part !== 'object') {
39
+ return false;
40
+ }
41
+
42
+ const record = part as AnyRecord;
43
+ const type = typeof record.type === 'string' ? record.type : '';
44
+ if (type === 'user_info' || type === 'system_reminder') {
45
+ return true;
46
+ }
47
+
48
+ return isInternalCursorText(record.text);
49
+ }
50
+
51
+ function unwrapUserQueryText(value: string, role: 'user' | 'assistant'): string {
52
+ if (role !== 'user') {
53
+ return value;
54
+ }
55
+
56
+ const normalized = value.trimStart();
57
+ const openTag = '<user_query>';
58
+ const closeTag = '</user_query>';
59
+ if (!normalized.startsWith(openTag)) {
60
+ return value;
61
+ }
62
+
63
+ const afterOpen = normalized.slice(openTag.length);
64
+ const closeIndex = afterOpen.lastIndexOf(closeTag);
65
+ const inner = closeIndex >= 0 ? afterOpen.slice(0, closeIndex) : afterOpen;
66
+ return inner.trim();
67
+ }
68
+
69
+ function normalizeToolId(value: unknown): string | null {
70
+ if (typeof value !== 'string') {
71
+ return null;
72
+ }
73
+
74
+ const normalized = value.trim();
75
+ return normalized ? normalized : null;
76
+ }
77
+
78
+ function extractCursorToolResultContent(item: AnyRecord): string {
79
+ if (typeof item.result === 'string' && item.result.trim()) {
80
+ return item.result;
81
+ }
82
+
83
+ if (typeof item.output === 'string' && item.output.trim()) {
84
+ return item.output;
85
+ }
86
+
87
+ if (Array.isArray(item.experimental_content)) {
88
+ const experimentalText = item.experimental_content
89
+ .map((part: unknown) => {
90
+ if (typeof part === 'string') {
91
+ return part;
92
+ }
93
+ if (part && typeof part === 'object') {
94
+ const record = part as AnyRecord;
95
+ if (typeof record.text === 'string') {
96
+ return record.text;
97
+ }
98
+ }
99
+ return '';
100
+ })
101
+ .filter(Boolean)
102
+ .join('\n');
103
+
104
+ if (experimentalText.trim()) {
105
+ return experimentalText;
106
+ }
107
+ }
108
+
109
+ return typeof item.result === 'string' ? item.result : '';
110
+ }
111
+
112
+ function parseCursorToolInput(rawInput: unknown): unknown {
113
+ if (typeof rawInput !== 'string') {
114
+ return rawInput;
115
+ }
116
+
117
+ const trimmed = rawInput.trim();
118
+ if (!trimmed) {
119
+ return rawInput;
120
+ }
121
+
122
+ try {
123
+ return JSON.parse(trimmed);
124
+ } catch {
125
+ return rawInput;
126
+ }
127
+ }
128
+
129
+ function normalizeCursorToolInput(toolName: string, rawInput: unknown): unknown {
130
+ const parsed = parseCursorToolInput(rawInput);
131
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
132
+ return parsed;
133
+ }
134
+
135
+ const input = parsed as AnyRecord;
136
+ const normalized: AnyRecord = { ...input };
137
+
138
+ const filePath = input.file_path
139
+ ?? input.filePath
140
+ ?? input.path
141
+ ?? input.file
142
+ ?? input.filename;
143
+ if (typeof filePath === 'string' && filePath.trim()) {
144
+ normalized.file_path = filePath;
145
+ }
146
+
147
+ if (toolName === 'Write') {
148
+ const content = input.content
149
+ ?? input.text
150
+ ?? input.value
151
+ ?? input.contents
152
+ ?? input.fileContent
153
+ ?? input.new_string
154
+ ?? input.newString;
155
+ if (typeof content === 'string') {
156
+ normalized.content = content;
157
+ }
158
+ }
159
+
160
+ if (toolName === 'Edit') {
161
+ const oldString = input.old_string
162
+ ?? input.oldString
163
+ ?? input.old
164
+ ?? '';
165
+ const newString = input.new_string
166
+ ?? input.newString
167
+ ?? input.new
168
+ ?? input.content
169
+ ?? '';
170
+
171
+ if (typeof oldString === 'string') {
172
+ normalized.old_string = oldString;
173
+ }
174
+ if (typeof newString === 'string') {
175
+ normalized.new_string = newString;
176
+ }
177
+ }
178
+
179
+ if (toolName === 'ApplyPatch') {
180
+ const patch = input.patch ?? input.diff ?? input.content;
181
+ if (typeof patch === 'string' && !normalized.patch) {
182
+ normalized.patch = patch;
183
+ }
184
+ }
185
+
186
+ return normalized;
187
+ }
188
+
28
189
  function sanitizeCursorSessionId(sessionId: string): string {
29
190
  const normalized = sessionId.trim();
30
191
  if (!normalized) {
@@ -225,13 +386,14 @@ export class CursorSessionsProvider implements IProviderSessions {
225
386
  try {
226
387
  const blobs = await this.loadCursorBlobs(sessionId, projectPath);
227
388
  const allNormalized = this.normalizeCursorBlobs(blobs, sessionId);
228
- const total = allNormalized.length;
389
+ const renderableMessages = allNormalized.filter((msg) => msg.kind !== 'tool_result');
390
+ const total = renderableMessages.length;
229
391
 
230
392
  if (limit !== null) {
231
393
  const start = offset;
232
394
  const page = limit === 0
233
395
  ? []
234
- : allNormalized.slice(start, start + limit);
396
+ : renderableMessages.slice(start, start + limit);
235
397
  const hasMore = limit === 0
236
398
  ? start < total
237
399
  : start + limit < total;
@@ -245,7 +407,7 @@ export class CursorSessionsProvider implements IProviderSessions {
245
407
  }
246
408
 
247
409
  return {
248
- messages: allNormalized,
410
+ messages: renderableMessages,
249
411
  total,
250
412
  hasMore: false,
251
413
  offset: 0,
@@ -283,11 +445,24 @@ export class CursorSessionsProvider implements IProviderSessions {
283
445
  let text = '';
284
446
  if (Array.isArray(content.message.content)) {
285
447
  text = content.message.content
286
- .map((part: string | AnyRecord) => typeof part === 'string' ? part : part?.text || '')
448
+ .map((part: string | AnyRecord) => {
449
+ if (typeof part === 'string') {
450
+ if (isInternalCursorText(part)) {
451
+ return '';
452
+ }
453
+ return unwrapUserQueryText(part, role);
454
+ }
455
+ if (isInternalCursorPart(part)) {
456
+ return '';
457
+ }
458
+ return unwrapUserQueryText(part?.text || '', role);
459
+ })
287
460
  .filter(Boolean)
288
461
  .join('\n');
289
462
  } else if (typeof content.message.content === 'string') {
290
- text = content.message.content;
463
+ if (!isInternalCursorText(content.message.content)) {
464
+ text = unwrapUserQueryText(content.message.content, role);
465
+ }
291
466
  }
292
467
  if (text?.trim()) {
293
468
  messages.push(createNormalizedMessage({
@@ -316,7 +491,14 @@ export class CursorSessionsProvider implements IProviderSessions {
316
491
  if (item?.type !== 'tool-result') {
317
492
  continue;
318
493
  }
319
- const toolCallId = item.toolCallId || content.id;
494
+ const cursorOptions = content.providerOptions?.cursor as AnyRecord | undefined;
495
+ const highLevelToolCallResult = cursorOptions?.highLevelToolCallResult;
496
+ const toolCallId = normalizeToolId(item.toolCallId)
497
+ || normalizeToolId(item.tool_call_id)
498
+ || normalizeToolId(highLevelToolCallResult?.toolCallId)
499
+ || normalizeToolId(highLevelToolCallResult?.tool_call_id)
500
+ || normalizeToolId(content.id)
501
+ || '';
320
502
  messages.push(createNormalizedMessage({
321
503
  id: `${baseId}_tr`,
322
504
  sessionId,
@@ -324,8 +506,9 @@ export class CursorSessionsProvider implements IProviderSessions {
324
506
  provider: PROVIDER,
325
507
  kind: 'tool_result',
326
508
  toolId: toolCallId,
327
- content: item.result || '',
328
- isError: false,
509
+ content: extractCursorToolResultContent(item),
510
+ isError: Boolean(item.isError || item.is_error),
511
+ toolUseResult: highLevelToolCallResult,
329
512
  }));
330
513
  }
331
514
  continue;
@@ -336,8 +519,15 @@ export class CursorSessionsProvider implements IProviderSessions {
336
519
  if (Array.isArray(content.content)) {
337
520
  for (let partIdx = 0; partIdx < content.content.length; partIdx++) {
338
521
  const part = content.content[partIdx];
522
+ if (isInternalCursorPart(part)) {
523
+ continue;
524
+ }
339
525
 
340
526
  if (part?.type === 'text' && part?.text) {
527
+ const normalizedPartText = unwrapUserQueryText(part.text, role);
528
+ if (!normalizedPartText) {
529
+ continue;
530
+ }
341
531
  messages.push(createNormalizedMessage({
342
532
  id: `${baseId}_${partIdx}`,
343
533
  sessionId,
@@ -345,7 +535,7 @@ export class CursorSessionsProvider implements IProviderSessions {
345
535
  provider: PROVIDER,
346
536
  kind: 'text',
347
537
  role,
348
- content: part.text,
538
+ content: normalizedPartText,
349
539
  sequence: blob.sequence,
350
540
  rowid: blob.rowid,
351
541
  }));
@@ -361,7 +551,11 @@ export class CursorSessionsProvider implements IProviderSessions {
361
551
  } else if (part?.type === 'tool-call' || part?.type === 'tool_use') {
362
552
  const rawToolName = part.toolName || part.name || 'Unknown Tool';
363
553
  const toolName = rawToolName === 'ApplyPatch' ? 'Edit' : rawToolName;
364
- const toolId = part.toolCallId || part.id || `tool_${i}_${partIdx}`;
554
+ const toolId = normalizeToolId(part.toolCallId)
555
+ || normalizeToolId(part.tool_call_id)
556
+ || normalizeToolId(part.id)
557
+ || `tool_${i}_${partIdx}`;
558
+ const normalizedToolInput = normalizeCursorToolInput(rawToolName, part.args ?? part.input);
365
559
  const message = createNormalizedMessage({
366
560
  id: `${baseId}_${partIdx}`,
367
561
  sessionId,
@@ -369,14 +563,22 @@ export class CursorSessionsProvider implements IProviderSessions {
369
563
  provider: PROVIDER,
370
564
  kind: 'tool_use',
371
565
  toolName,
372
- toolInput: part.args || part.input,
566
+ toolInput: normalizedToolInput,
373
567
  toolId,
374
568
  });
375
569
  messages.push(message);
376
570
  toolUseMap.set(toolId, message);
377
571
  }
378
572
  }
379
- } else if (typeof content.content === 'string' && content.content.trim()) {
573
+ } else if (
574
+ typeof content.content === 'string'
575
+ && content.content.trim()
576
+ && !isInternalCursorText(content.content)
577
+ ) {
578
+ const normalizedText = unwrapUserQueryText(content.content, role);
579
+ if (!normalizedText) {
580
+ continue;
581
+ }
380
582
  messages.push(createNormalizedMessage({
381
583
  id: baseId,
382
584
  sessionId,
@@ -384,7 +586,7 @@ export class CursorSessionsProvider implements IProviderSessions {
384
586
  provider: PROVIDER,
385
587
  kind: 'text',
386
588
  role,
387
- content: content.content,
589
+ content: normalizedText,
388
590
  sequence: blob.sequence,
389
591
  rowid: blob.rowid,
390
592
  }));
@@ -401,6 +603,7 @@ export class CursorSessionsProvider implements IProviderSessions {
401
603
  toolUse.toolResult = {
402
604
  content: msg.content,
403
605
  isError: msg.isError,
606
+ toolUseResult: msg.toolUseResult,
404
607
  };
405
608
  }
406
609
  }
@@ -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
+ }
@@ -3,11 +3,18 @@ import { CursorProviderAuth } from '@/modules/providers/list/cursor/cursor-auth.
3
3
  import { CursorMcpProvider } from '@/modules/providers/list/cursor/cursor-mcp.provider.js';
4
4
  import { CursorSessionSynchronizer } from '@/modules/providers/list/cursor/cursor-session-synchronizer.provider.js';
5
5
  import { CursorSessionsProvider } from '@/modules/providers/list/cursor/cursor-sessions.provider.js';
6
- import type { IProviderAuth, IProviderSessionSynchronizer, IProviderSessions } from '@/shared/interfaces.js';
6
+ import { CursorSkillsProvider } from '@/modules/providers/list/cursor/cursor-skills.provider.js';
7
+ import type {
8
+ IProviderAuth,
9
+ IProviderSessionSynchronizer,
10
+ IProviderSkills,
11
+ IProviderSessions,
12
+ } from '@/shared/interfaces.js';
7
13
 
8
14
  export class CursorProvider extends AbstractProvider {
9
15
  readonly mcp = new CursorMcpProvider();
10
16
  readonly auth: IProviderAuth = new CursorProviderAuth();
17
+ readonly skills: IProviderSkills = new CursorSkillsProvider();
11
18
  readonly sessions: IProviderSessions = new CursorSessionsProvider();
12
19
  readonly sessionSynchronizer: IProviderSessionSynchronizer = new CursorSessionSynchronizer();
13
20