@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
@@ -0,0 +1,257 @@
1
+ import { readFile, readdir, stat } 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 { parseFrontMatter } from '@/shared/frontmatter.js';
7
+ import type {
8
+ ProviderSkill,
9
+ ProviderSkillListOptions,
10
+ ProviderSkillSource,
11
+ } from '@/shared/types.js';
12
+ import {
13
+ findProviderSkillMarkdownFiles,
14
+ readJsonConfig,
15
+ readObjectRecord,
16
+ readOptionalString,
17
+ readProviderSkillMarkdownDefinition,
18
+ } from '@/shared/utils.js';
19
+
20
+ const getClaudeHomePath = (): string => path.join(os.homedir(), '.claude');
21
+
22
+ const getClaudePluginName = (pluginId: string): string | null => {
23
+ const normalizedPluginId = pluginId.trim();
24
+ if (!normalizedPluginId || normalizedPluginId === '@') {
25
+ return null;
26
+ }
27
+
28
+ const [pluginName] = normalizedPluginId.split('@');
29
+ return readOptionalString(pluginName) ?? null;
30
+ };
31
+
32
+ const stripMarkdownExtension = (filename: string): string =>
33
+ filename.replace(/\.md$/i, '');
34
+
35
+ const pathExistsAsDirectory = async (directoryPath: string): Promise<boolean> => {
36
+ try {
37
+ const directoryStats = await stat(directoryPath);
38
+ return directoryStats.isDirectory();
39
+ } catch {
40
+ return false;
41
+ }
42
+ };
43
+
44
+ const listChildDirectories = async (directoryPath: string): Promise<string[]> => {
45
+ try {
46
+ const entries = await readdir(directoryPath, { withFileTypes: true });
47
+ return entries
48
+ .filter((entry) => entry.isDirectory())
49
+ .map((entry) => path.join(directoryPath, entry.name))
50
+ .sort((left, right) => left.localeCompare(right));
51
+ } catch {
52
+ return [];
53
+ }
54
+ };
55
+
56
+ const readClaudePluginName = async (
57
+ installPath: string,
58
+ pluginId: string,
59
+ ): Promise<string | null> => {
60
+ try {
61
+ const pluginConfig = await readJsonConfig(
62
+ path.join(installPath, '.claude-plugin', 'plugin.json'),
63
+ );
64
+
65
+ // Older or partial plugin installs may not have plugin.json yet. Falling
66
+ // back keeps discovery useful without inventing a separate namespace.
67
+ return readOptionalString(pluginConfig.name) ?? getClaudePluginName(pluginId);
68
+ } catch {
69
+ return getClaudePluginName(pluginId);
70
+ }
71
+ };
72
+
73
+ export class ClaudeSkillsProvider extends SkillsProvider {
74
+ constructor() {
75
+ super('claude');
76
+ }
77
+
78
+ async listSkills(options?: ProviderSkillListOptions): Promise<ProviderSkill[]> {
79
+ return [
80
+ ...(await super.listSkills(options)),
81
+ ...(await this.listPluginSkills(getClaudeHomePath())),
82
+ ];
83
+ }
84
+
85
+ protected async getSkillSources(workspacePath: string): Promise<ProviderSkillSource[]> {
86
+ const claudeHomePath = getClaudeHomePath();
87
+
88
+ return [
89
+ {
90
+ scope: 'user',
91
+ rootDir: path.join(claudeHomePath, 'skills'),
92
+ commandPrefix: '/',
93
+ },
94
+ {
95
+ scope: 'project',
96
+ rootDir: path.join(workspacePath, '.claude', 'skills'),
97
+ commandPrefix: '/',
98
+ },
99
+ ];
100
+ }
101
+
102
+ private async listPluginSkills(claudeHomePath: string): Promise<ProviderSkill[]> {
103
+ const settings = await readJsonConfig(path.join(claudeHomePath, 'settings.json'));
104
+ const enabledPlugins = readObjectRecord(settings.enabledPlugins);
105
+ if (!enabledPlugins) {
106
+ return [];
107
+ }
108
+
109
+ const installedConfig = await readJsonConfig(
110
+ path.join(claudeHomePath, 'plugins', 'installed_plugins.json'),
111
+ );
112
+ const installedPlugins = readObjectRecord(installedConfig.plugins);
113
+ if (!installedPlugins) {
114
+ return [];
115
+ }
116
+
117
+ const skills: ProviderSkill[] = [];
118
+ const visitedPluginFolders = new Set<string>();
119
+ const pluginEntries = Object.entries(enabledPlugins)
120
+ .sort(([left], [right]) => left.localeCompare(right));
121
+ for (const [pluginId, enabled] of pluginEntries) {
122
+ if (enabled !== true) {
123
+ continue;
124
+ }
125
+
126
+ const installs = installedPlugins[pluginId];
127
+ if (!Array.isArray(installs)) {
128
+ continue;
129
+ }
130
+
131
+ for (const install of installs) {
132
+ const installRecord = readObjectRecord(install);
133
+ const installPath = readOptionalString(installRecord?.installPath);
134
+ if (!installPath) {
135
+ continue;
136
+ }
137
+
138
+ // Claude's installed path points at one version folder; the usable
139
+ // plugin payloads live in the direct child folders beside it.
140
+ const pluginFolders = await listChildDirectories(path.dirname(installPath));
141
+ for (const pluginFolder of pluginFolders) {
142
+ const pluginFolderKey = `${pluginId}:${path.resolve(pluginFolder)}`;
143
+ if (visitedPluginFolders.has(pluginFolderKey)) {
144
+ continue;
145
+ }
146
+ visitedPluginFolders.add(pluginFolderKey);
147
+
148
+ const pluginName = await readClaudePluginName(pluginFolder, pluginId);
149
+ if (!pluginName) {
150
+ continue;
151
+ }
152
+
153
+ const commandsPath = path.join(pluginFolder, 'commands');
154
+ if (await pathExistsAsDirectory(commandsPath)) {
155
+ skills.push(
156
+ ...(await this.listPluginCommandSkills(commandsPath, pluginId, pluginName)),
157
+ );
158
+ continue;
159
+ }
160
+
161
+ const skillsPath = path.join(pluginFolder, 'skills');
162
+ if (!(await pathExistsAsDirectory(skillsPath))) {
163
+ continue;
164
+ }
165
+
166
+ skills.push(
167
+ ...(await this.listPluginSkillMarkdowns(pluginFolder, pluginId, pluginName)),
168
+ );
169
+ }
170
+ }
171
+ }
172
+
173
+ return skills;
174
+ }
175
+
176
+ private async listPluginCommandSkills(
177
+ commandsPath: string,
178
+ pluginId: string,
179
+ pluginName: string,
180
+ ): Promise<ProviderSkill[]> {
181
+ const skills: ProviderSkill[] = [];
182
+
183
+ try {
184
+ const entries = await readdir(commandsPath, { withFileTypes: true });
185
+ const commandFiles = entries
186
+ .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.md'))
187
+ .sort((left, right) => left.name.localeCompare(right.name));
188
+
189
+ for (const commandFile of commandFiles) {
190
+ const sourcePath = path.join(commandsPath, commandFile.name);
191
+ try {
192
+ const definition = await this.readPluginCommandDefinition(sourcePath);
193
+ skills.push({
194
+ provider: this.provider,
195
+ name: definition.name,
196
+ description: definition.description,
197
+ command: `/${pluginName}:${definition.name}`,
198
+ scope: 'plugin',
199
+ sourcePath,
200
+ pluginName,
201
+ pluginId,
202
+ });
203
+ } catch {
204
+ // Malformed command markdown should not block sibling plugin commands.
205
+ }
206
+ }
207
+ } catch {
208
+ // Missing or unreadable command folders are treated as empty plugin command sets.
209
+ }
210
+
211
+ return skills;
212
+ }
213
+
214
+ private async readPluginCommandDefinition(
215
+ commandPath: string,
216
+ ): Promise<{ name: string; description: string }> {
217
+ const content = await readFile(commandPath, 'utf8');
218
+ const parsed = parseFrontMatter(content);
219
+ const data = readObjectRecord(parsed.data) ?? {};
220
+
221
+ return {
222
+ name: stripMarkdownExtension(path.basename(commandPath)),
223
+ description: readOptionalString(data.description) ?? '',
224
+ };
225
+ }
226
+
227
+ private async listPluginSkillMarkdowns(
228
+ installPath: string,
229
+ pluginId: string,
230
+ pluginName: string,
231
+ ): Promise<ProviderSkill[]> {
232
+ const skillFiles = await findProviderSkillMarkdownFiles(path.join(installPath, 'skills'), {
233
+ recursive: true,
234
+ });
235
+ const skills: ProviderSkill[] = [];
236
+
237
+ for (const skillPath of skillFiles) {
238
+ try {
239
+ const definition = await readProviderSkillMarkdownDefinition(skillPath);
240
+ skills.push({
241
+ provider: this.provider,
242
+ name: definition.name,
243
+ description: definition.description,
244
+ command: `/${pluginName}:${definition.name}`,
245
+ scope: 'plugin',
246
+ sourcePath: skillPath,
247
+ pluginName,
248
+ pluginId,
249
+ });
250
+ } catch {
251
+ // A bad plugin skill file should not block other installed plugin skills.
252
+ }
253
+ }
254
+
255
+ return skills;
256
+ }
257
+ }
@@ -1,13 +1,23 @@
1
1
  import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
2
2
  import { ClaudeProviderAuth } from '@/modules/providers/list/claude/claude-auth.provider.js';
3
+ import { ClaudeProviderModels } from '@/modules/providers/list/claude/claude-models.provider.js';
3
4
  import { ClaudeMcpProvider } from '@/modules/providers/list/claude/claude-mcp.provider.js';
4
5
  import { ClaudeSessionSynchronizer } from '@/modules/providers/list/claude/claude-session-synchronizer.provider.js';
5
6
  import { ClaudeSessionsProvider } from '@/modules/providers/list/claude/claude-sessions.provider.js';
6
- import type { IProviderAuth, IProviderSessionSynchronizer, IProviderSessions } from '@/shared/interfaces.js';
7
+ import { ClaudeSkillsProvider } from '@/modules/providers/list/claude/claude-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 ClaudeProvider extends AbstractProvider {
17
+ readonly models: IProviderModels = new ClaudeProviderModels();
9
18
  readonly mcp = new ClaudeMcpProvider();
10
19
  readonly auth: IProviderAuth = new ClaudeProviderAuth();
20
+ readonly skills: IProviderSkills = new ClaudeSkillsProvider();
11
21
  readonly sessions: IProviderSessions = new ClaudeSessionsProvider();
12
22
  readonly sessionSynchronizer: IProviderSessionSynchronizer = new ClaudeSessionSynchronizer();
13
23
 
@@ -0,0 +1,125 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import TOML from '@iarna/toml';
6
+
7
+ import type { IProviderModels } from '@/shared/interfaces.js';
8
+ import type {
9
+ ProviderChangeActiveModelInput,
10
+ ProviderCurrentActiveModel,
11
+ ProviderModelOption,
12
+ ProviderModelsDefinition,
13
+ ProviderSessionActiveModelChange,
14
+ } from '@/shared/types.js';
15
+ import {
16
+ buildDefaultProviderCurrentActiveModel,
17
+ readObjectRecord,
18
+ readOptionalString,
19
+ writeProviderSessionActiveModelChange,
20
+ } from '@/shared/utils.js';
21
+
22
+ export const CODEX_FALLBACK_MODELS: ProviderModelsDefinition = {
23
+ OPTIONS: [
24
+ { value: 'gpt-5.5', label: 'gpt-5.5' },
25
+ { value: 'gpt-5.4', label: 'gpt-5.4' },
26
+ { value: 'gpt-5.4-mini', label: 'gpt-5.4-mini' },
27
+ { value: 'gpt-5.3-codex', label: 'gpt-5.3-codex' },
28
+ { value: 'gpt-5.2', label: 'gpt-5.2' },
29
+ ],
30
+ DEFAULT: 'gpt-5.4',
31
+ };
32
+
33
+ type CodexCachedModel = {
34
+ slug?: string;
35
+ display_name?: string;
36
+ description?: string;
37
+ priority?: number;
38
+ visibility?: string;
39
+ supported_in_api?: boolean;
40
+ };
41
+
42
+ const CODEX_MODELS_CACHE_PATH = path.join(os.homedir(), '.codex', 'models_cache.json');
43
+ const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
44
+
45
+ const isCodexCachedModel = (value: unknown): value is CodexCachedModel => {
46
+ const record = readObjectRecord(value);
47
+ return Boolean(record && readOptionalString(record.slug));
48
+ };
49
+
50
+ const readCodexPriority = (value: unknown): number => (
51
+ typeof value === 'number' && Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER
52
+ );
53
+
54
+ const mapCodexModel = (model: CodexCachedModel): ProviderModelOption => ({
55
+ value: model.slug as string,
56
+ label: readOptionalString(model.display_name) ?? (model.slug as string),
57
+ description: readOptionalString(model.description),
58
+ });
59
+
60
+ const buildCodexModelsDefinition = (models: CodexCachedModel[]): ProviderModelsDefinition => {
61
+ const sortedModels = [...models]
62
+ .filter((model) => model.visibility !== 'hidden' && model.supported_in_api !== false)
63
+ .sort((left, right) => readCodexPriority(left.priority) - readCodexPriority(right.priority));
64
+
65
+ const options: ProviderModelOption[] = [];
66
+ const seenValues = new Set<string>();
67
+
68
+ for (const model of sortedModels) {
69
+ const mappedModel = mapCodexModel(model);
70
+ if (seenValues.has(mappedModel.value)) {
71
+ continue;
72
+ }
73
+
74
+ seenValues.add(mappedModel.value);
75
+ options.push(mappedModel);
76
+ }
77
+
78
+ if (options.length === 0) {
79
+ return CODEX_FALLBACK_MODELS;
80
+ }
81
+
82
+ return {
83
+ OPTIONS: options,
84
+ DEFAULT: options[0]?.value ?? CODEX_FALLBACK_MODELS.DEFAULT,
85
+ };
86
+ };
87
+
88
+ export class CodexProviderModels implements IProviderModels {
89
+ async getSupportedModels(): Promise<ProviderModelsDefinition> {
90
+ try {
91
+ const raw = await readFile(CODEX_MODELS_CACHE_PATH, 'utf8');
92
+ const parsed = readObjectRecord(JSON.parse(raw));
93
+ const models = Array.isArray(parsed?.models)
94
+ ? parsed.models.filter(isCodexCachedModel)
95
+ : [];
96
+
97
+ return buildCodexModelsDefinition(models);
98
+ } catch {
99
+ return CODEX_FALLBACK_MODELS;
100
+ }
101
+ }
102
+
103
+ async getCurrentActiveModel(): Promise<ProviderCurrentActiveModel> {
104
+ try {
105
+ const raw = await readFile(CODEX_CONFIG_PATH, 'utf8');
106
+ const parsed = readObjectRecord(TOML.parse(raw));
107
+ const model = readOptionalString(parsed?.model);
108
+ if (!model) {
109
+ return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
110
+ }
111
+
112
+ return {
113
+ model,
114
+ };
115
+ } catch {
116
+ return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
117
+ }
118
+ }
119
+
120
+ async changeActiveModel(
121
+ input: ProviderChangeActiveModelInput,
122
+ ): Promise<ProviderSessionActiveModelChange> {
123
+ return writeProviderSessionActiveModelChange('codex', input);
124
+ }
125
+ }
@@ -1,5 +1,6 @@
1
1
  import os from 'node:os';
2
2
  import path from 'node:path';
3
+ import { readFile } from 'node:fs/promises';
3
4
 
4
5
  import { sessionsDb } from '@/modules/database/index.js';
5
6
  import {
@@ -99,7 +100,7 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
99
100
  filePath: string,
100
101
  nameMap: Map<string, string>
101
102
  ): Promise<ParsedSession | null> {
102
- return extractFirstValidJsonlData(filePath, (rawData) => {
103
+ const parsed = await extractFirstValidJsonlData(filePath, (rawData) => {
103
104
  const data = rawData as Record<string, unknown>;
104
105
  const payload = data.payload as Record<string, unknown> | undefined;
105
106
  const sessionId = typeof payload?.id === 'string' ? payload.id : undefined;
@@ -112,8 +113,67 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
112
113
  return {
113
114
  sessionId,
114
115
  projectPath,
115
- sessionName: normalizeSessionName(nameMap.get(sessionId), 'Untitled Codex Session'),
116
116
  };
117
117
  });
118
+
119
+ if (!parsed) {
120
+ return null;
121
+ }
122
+
123
+ const existingSession = sessionsDb.getSessionById(parsed.sessionId);
124
+ const existingSessionName = existingSession?.custom_name;
125
+ if (existingSessionName && existingSessionName !== 'Untitled Codex Session') {
126
+ return {
127
+ ...parsed,
128
+ sessionName: normalizeSessionName(existingSessionName, 'Untitled Codex Session'),
129
+ };
130
+ }
131
+
132
+ let sessionName = nameMap.get(parsed.sessionId);
133
+ if (!sessionName) {
134
+ sessionName = await this.extractLastAgentMessageFromEnd(filePath);
135
+ }
136
+
137
+ return {
138
+ ...parsed,
139
+ sessionName: normalizeSessionName(sessionName, 'Untitled Codex Session'),
140
+ };
141
+ }
142
+
143
+ private async extractLastAgentMessageFromEnd(filePath: string): Promise<string | undefined> {
144
+ try {
145
+ const content = await readFile(filePath, 'utf8');
146
+ const lines = content.split(/\r?\n/);
147
+
148
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
149
+ const line = lines[index]?.trim();
150
+ if (!line) {
151
+ continue;
152
+ }
153
+
154
+ let parsed: unknown;
155
+ try {
156
+ parsed = JSON.parse(line);
157
+ } catch {
158
+ continue;
159
+ }
160
+
161
+ const data = parsed as Record<string, unknown>;
162
+ const eventType = typeof data.type === 'string' ? data.type : undefined;
163
+ const payload = data.payload as Record<string, unknown> | undefined;
164
+ const payloadType = typeof payload?.type === 'string' ? payload.type : undefined;
165
+ const lastAgentMessage = typeof payload?.last_agent_message === 'string'
166
+ ? payload.last_agent_message
167
+ : undefined;
168
+
169
+ if (eventType === 'event_msg' && payloadType === 'task_complete' && lastAgentMessage?.trim()) {
170
+ return lastAgentMessage;
171
+ }
172
+ }
173
+ } catch {
174
+ // Ignore missing/unreadable files so sync can continue.
175
+ }
176
+
177
+ return undefined;
118
178
  }
119
179
  }
@@ -520,7 +520,9 @@ export class CodexSessionsProvider implements IProviderSessions {
520
520
 
521
521
  let result: CodexHistoryResult;
522
522
  try {
523
- result = await getCodexSessionMessages(sessionId, limit, offset);
523
+ // Load full history first so `total` reflects frontend-normalized messages,
524
+ // not raw JSONL records.
525
+ result = await getCodexSessionMessages(sessionId, null, 0);
524
526
  } catch (error) {
525
527
  const message = error instanceof Error ? error.message : String(error);
526
528
  console.warn(`[CodexProvider] Failed to load session ${sessionId}:`, message);
@@ -528,8 +530,6 @@ export class CodexSessionsProvider implements IProviderSessions {
528
530
  }
529
531
 
530
532
  const rawMessages = Array.isArray(result) ? result : (result.messages || []);
531
- const total = Array.isArray(result) ? rawMessages.length : (result.total || 0);
532
- const hasMore = Array.isArray(result) ? false : Boolean(result.hasMore);
533
533
  const tokenUsage = Array.isArray(result) ? undefined : result.tokenUsage;
534
534
 
535
535
  const normalized: NormalizedMessage[] = [];
@@ -552,12 +552,31 @@ export class CodexSessionsProvider implements IProviderSessions {
552
552
  }
553
553
  }
554
554
 
555
+ const totalNormalized = normalized.length;
556
+ let total = 0;
557
+ for (const msg of normalized) {
558
+ if (msg.kind !== 'tool_result') {
559
+ total += 1;
560
+ }
561
+ }
562
+ const normalizedOffset = Math.max(0, offset);
563
+ const normalizedLimit = limit === null ? null : Math.max(0, limit);
564
+ const messages = normalizedLimit === null
565
+ ? normalized
566
+ : normalized.slice(
567
+ Math.max(0, totalNormalized - normalizedOffset - normalizedLimit),
568
+ Math.max(0, totalNormalized - normalizedOffset),
569
+ );
570
+ const hasMore = normalizedLimit === null
571
+ ? false
572
+ : Math.max(0, totalNormalized - normalizedOffset - normalizedLimit) > 0;
573
+
555
574
  return {
556
- messages: normalized,
575
+ messages,
557
576
  total,
558
577
  hasMore,
559
- offset,
560
- limit,
578
+ offset: normalizedOffset,
579
+ limit: normalizedLimit,
561
580
  tokenUsage,
562
581
  };
563
582
  }
@@ -0,0 +1,60 @@
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
+ import {
7
+ addUniqueProviderSkillSource,
8
+ findTopmostGitRoot,
9
+ } from '@/shared/utils.js';
10
+
11
+ export class CodexSkillsProvider extends SkillsProvider {
12
+ constructor() {
13
+ super('codex');
14
+ }
15
+
16
+ protected async getSkillSources(workspacePath: string): Promise<ProviderSkillSource[]> {
17
+ const sources: ProviderSkillSource[] = [];
18
+ const seenRootDirs = new Set<string>();
19
+ const repoRoot = await findTopmostGitRoot(workspacePath);
20
+
21
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
22
+ scope: 'repo',
23
+ rootDir: path.join(workspacePath, '.agents', 'skills'),
24
+ commandPrefix: '$',
25
+ });
26
+
27
+ if (repoRoot) {
28
+ // Codex checks repository skills at the launch folder, one folder above it,
29
+ // and the topmost git root; these can collapse to the same directory.
30
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
31
+ scope: 'repo',
32
+ rootDir: path.join(path.dirname(workspacePath), '.agents', 'skills'),
33
+ commandPrefix: '$',
34
+ });
35
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
36
+ scope: 'repo',
37
+ rootDir: path.join(repoRoot, '.agents', 'skills'),
38
+ commandPrefix: '$',
39
+ });
40
+ }
41
+
42
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
43
+ scope: 'user',
44
+ rootDir: path.join(os.homedir(), '.agents', 'skills'),
45
+ commandPrefix: '$',
46
+ });
47
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
48
+ scope: 'admin',
49
+ rootDir: path.join('/etc', 'codex', 'skills'),
50
+ commandPrefix: '$',
51
+ });
52
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
53
+ scope: 'system',
54
+ rootDir: path.join(os.homedir(), '.codex', 'skills', '.system'),
55
+ commandPrefix: '$',
56
+ });
57
+
58
+ return sources;
59
+ }
60
+ }
@@ -1,13 +1,23 @@
1
1
  import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
2
2
  import { CodexProviderAuth } from '@/modules/providers/list/codex/codex-auth.provider.js';
3
+ import { CodexProviderModels } from '@/modules/providers/list/codex/codex-models.provider.js';
3
4
  import { CodexMcpProvider } from '@/modules/providers/list/codex/codex-mcp.provider.js';
4
5
  import { CodexSessionSynchronizer } from '@/modules/providers/list/codex/codex-session-synchronizer.provider.js';
5
6
  import { CodexSessionsProvider } from '@/modules/providers/list/codex/codex-sessions.provider.js';
6
- import type { IProviderAuth, IProviderSessionSynchronizer, IProviderSessions } from '@/shared/interfaces.js';
7
+ import { CodexSkillsProvider } from '@/modules/providers/list/codex/codex-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 CodexProvider extends AbstractProvider {
17
+ readonly models: IProviderModels = new CodexProviderModels();
9
18
  readonly mcp = new CodexMcpProvider();
10
19
  readonly auth: IProviderAuth = new CodexProviderAuth();
20
+ readonly skills: IProviderSkills = new CodexSkillsProvider();
11
21
  readonly sessions: IProviderSessions = new CodexSessionsProvider();
12
22
  readonly sessionSynchronizer: IProviderSessionSynchronizer = new CodexSessionSynchronizer();
13
23