@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
@@ -1,10 +1,12 @@
1
- import express from 'express';
2
- import { promises as fs } from 'fs';
3
- import path from 'path';
4
- import os from 'os';
5
- import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '../../shared/modelConstants.js';
6
- import { parseFrontmatter } from '../utils/frontmatter.js';
7
- import { findAppRoot, getModuleDir } from '../utils/runtime-paths.js';
1
+ import { promises as fs } from "fs";
2
+ import os from "os";
3
+ import path from "path";
4
+
5
+ import express from "express";
6
+
7
+ import { providerModelsService } from "../modules/providers/services/provider-models.service.js";
8
+ import { parseFrontMatter } from "../shared/frontmatter.js";
9
+ import { findAppRoot, getModuleDir } from "../utils/runtime-paths.js";
8
10
 
9
11
  const __dirname = getModuleDir(import.meta.url);
10
12
  // This route reads the top-level package.json for the status command, so it needs the real
@@ -13,6 +15,77 @@ const APP_ROOT = findAppRoot(__dirname);
13
15
 
14
16
  const router = express.Router();
15
17
 
18
+ const MODEL_PROVIDERS = ["claude", "cursor", "codex", "gemini", "opencode"];
19
+
20
+ const MODEL_PROVIDER_LABELS = {
21
+ claude: "Claude",
22
+ cursor: "Cursor",
23
+ codex: "Codex",
24
+ gemini: "Gemini",
25
+ opencode: "OpenCode",
26
+ };
27
+
28
+ const readModelProvider = (value) => {
29
+ if (typeof value !== "string") {
30
+ return "claude";
31
+ }
32
+
33
+ const normalized = value.trim().toLowerCase();
34
+ return MODEL_PROVIDERS.includes(normalized) ? normalized : "claude";
35
+ };
36
+
37
+ const hasConcreteSessionId = (value) =>
38
+ typeof value === "string" && value.trim().length > 0;
39
+
40
+ const resolveCommandModel = async (provider, catalog, sessionId) => {
41
+ if (!hasConcreteSessionId(sessionId)) {
42
+ return catalog.DEFAULT;
43
+ }
44
+
45
+ const currentActiveModel = await providerModelsService.getCurrentActiveModel(
46
+ provider,
47
+ sessionId,
48
+ );
49
+ return currentActiveModel?.model || catalog.DEFAULT;
50
+ };
51
+
52
+ export const executeModelsCommand = async (args, context) => {
53
+ const currentProvider = readModelProvider(context?.provider);
54
+ const result = await providerModelsService.getProviderModels(currentProvider);
55
+ const catalog = result.models;
56
+ const currentModel = await resolveCommandModel(
57
+ currentProvider,
58
+ catalog,
59
+ context?.sessionId,
60
+ );
61
+ const availableModels = catalog.OPTIONS.map((option) => option.value);
62
+ const availableOptions = catalog.OPTIONS.map((option) => ({
63
+ value: option.value,
64
+ label: option.label,
65
+ description: option.description,
66
+ }));
67
+
68
+ return {
69
+ type: "builtin",
70
+ action: "models",
71
+ data: {
72
+ current: {
73
+ provider: currentProvider,
74
+ providerLabel: MODEL_PROVIDER_LABELS[currentProvider],
75
+ model: currentModel,
76
+ },
77
+ available: {
78
+ [currentProvider]: availableModels,
79
+ },
80
+ availableModels,
81
+ availableOptions,
82
+ defaultModel: catalog.DEFAULT,
83
+ cache: result.cache,
84
+ message: `Current model: ${currentModel}`,
85
+ },
86
+ };
87
+ };
88
+
16
89
  /**
17
90
  * Recursively scan directory for command files (.md)
18
91
  * @param {string} dir - Directory to scan
@@ -34,24 +107,30 @@ async function scanCommandsDirectory(dir, baseDir, namespace) {
34
107
 
35
108
  if (entry.isDirectory()) {
36
109
  // Recursively scan subdirectories
37
- const subCommands = await scanCommandsDirectory(fullPath, baseDir, namespace);
110
+ const subCommands = await scanCommandsDirectory(
111
+ fullPath,
112
+ baseDir,
113
+ namespace,
114
+ );
38
115
  commands.push(...subCommands);
39
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
116
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
40
117
  // Parse markdown file for metadata
41
118
  try {
42
- const content = await fs.readFile(fullPath, 'utf8');
43
- const { data: frontmatter, content: commandContent } = parseFrontmatter(content);
119
+ const content = await fs.readFile(fullPath, "utf8");
120
+ const { data: frontmatter, content: commandContent } =
121
+ parseFrontMatter(content);
44
122
 
45
123
  // Calculate relative path from baseDir for command name
46
124
  const relativePath = path.relative(baseDir, fullPath);
47
125
  // Remove .md extension and convert to command name
48
- const commandName = '/' + relativePath.replace(/\.md$/, '').replace(/\\/g, '/');
126
+ const commandName =
127
+ "/" + relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
49
128
 
50
129
  // Extract description from frontmatter or first line of content
51
- let description = frontmatter.description || '';
130
+ let description = frontmatter.description || "";
52
131
  if (!description) {
53
- const firstLine = commandContent.trim().split('\n')[0];
54
- description = firstLine.replace(/^#+\s*/, '').trim();
132
+ const firstLine = commandContent.trim().split("\n")[0];
133
+ description = firstLine.replace(/^#+\s*/, "").trim();
55
134
  }
56
135
 
57
136
  commands.push({
@@ -60,7 +139,7 @@ async function scanCommandsDirectory(dir, baseDir, namespace) {
60
139
  relativePath,
61
140
  description,
62
141
  namespace,
63
- metadata: frontmatter
142
+ metadata: frontmatter,
64
143
  });
65
144
  } catch (err) {
66
145
  console.error(`Error parsing command file ${fullPath}:`, err.message);
@@ -69,7 +148,7 @@ async function scanCommandsDirectory(dir, baseDir, namespace) {
69
148
  }
70
149
  } catch (err) {
71
150
  // Directory doesn't exist or can't be accessed - this is okay
72
- if (err.code !== 'ENOENT' && err.code !== 'EACCES') {
151
+ if (err.code !== "ENOENT" && err.code !== "EACCES") {
73
152
  console.error(`Error scanning directory ${dir}:`, err.message);
74
153
  }
75
154
  }
@@ -82,53 +161,41 @@ async function scanCommandsDirectory(dir, baseDir, namespace) {
82
161
  */
83
162
  const builtInCommands = [
84
163
  {
85
- name: '/help',
86
- description: 'Show help documentation for Claude Code',
87
- namespace: 'builtin',
88
- metadata: { type: 'builtin' }
164
+ name: "/help",
165
+ description: "Show help documentation for Claude Code",
166
+ namespace: "builtin",
167
+ metadata: { type: "builtin" },
89
168
  },
90
169
  {
91
- name: '/clear',
92
- description: 'Clear the conversation history',
93
- namespace: 'builtin',
94
- metadata: { type: 'builtin' }
170
+ name: "/models",
171
+ description: "View available models for the current provider",
172
+ namespace: "builtin",
173
+ metadata: { type: "builtin" },
95
174
  },
96
175
  {
97
- name: '/model',
98
- description: 'Switch or view the current AI model',
99
- namespace: 'builtin',
100
- metadata: { type: 'builtin' }
176
+ name: "/cost",
177
+ description: "Display token usage information",
178
+ namespace: "builtin",
179
+ metadata: { type: "builtin" },
101
180
  },
102
181
  {
103
- name: '/cost',
104
- description: 'Display token usage and cost information',
105
- namespace: 'builtin',
106
- metadata: { type: 'builtin' }
182
+ name: "/memory",
183
+ description: "Open CLAUDE.md memory file for editing",
184
+ namespace: "builtin",
185
+ metadata: { type: "builtin" },
107
186
  },
108
187
  {
109
- name: '/memory',
110
- description: 'Open CLAUDE.md memory file for editing',
111
- namespace: 'builtin',
112
- metadata: { type: 'builtin' }
188
+ name: "/config",
189
+ description: "Open settings and configuration",
190
+ namespace: "builtin",
191
+ metadata: { type: "builtin" },
113
192
  },
114
193
  {
115
- name: '/config',
116
- description: 'Open settings and configuration',
117
- namespace: 'builtin',
118
- metadata: { type: 'builtin' }
194
+ name: "/status",
195
+ description: "Show system status and version information",
196
+ namespace: "builtin",
197
+ metadata: { type: "builtin" },
119
198
  },
120
- {
121
- name: '/status',
122
- description: 'Show system status and version information',
123
- namespace: 'builtin',
124
- metadata: { type: 'builtin' }
125
- },
126
- {
127
- name: '/rewind',
128
- description: 'Rewind the conversation to a previous state',
129
- namespace: 'builtin',
130
- metadata: { type: 'builtin' }
131
- }
132
199
  ];
133
200
 
134
201
  /**
@@ -136,14 +203,18 @@ const builtInCommands = [
136
203
  * Each handler returns { type: 'builtin', action: string, data: any }
137
204
  */
138
205
  const builtInHandlers = {
139
- '/help': async (args, context) => {
206
+ "/help": async (args, context) => {
140
207
  const helpText = `# Claude Code Commands
141
208
 
142
209
  ## Built-in Commands
143
210
 
144
- ${builtInCommands.map(cmd => `### ${cmd.name}
211
+ ${builtInCommands
212
+ .map(
213
+ (cmd) => `### ${cmd.name}
145
214
  ${cmd.description}
146
- `).join('\n')}
215
+ `,
216
+ )
217
+ .join("\n")}
147
218
 
148
219
  ## Custom Commands
149
220
 
@@ -165,77 +236,45 @@ Custom commands can be created in:
165
236
  `;
166
237
 
167
238
  return {
168
- type: 'builtin',
169
- action: 'help',
239
+ type: "builtin",
240
+ action: "help",
170
241
  data: {
171
242
  content: helpText,
172
- format: 'markdown'
173
- }
174
- };
175
- },
176
-
177
- '/clear': async (args, context) => {
178
- return {
179
- type: 'builtin',
180
- action: 'clear',
181
- data: {
182
- message: 'Conversation history cleared'
183
- }
243
+ format: "markdown",
244
+ commands: builtInCommands.map((command) => ({
245
+ name: command.name,
246
+ description: command.description,
247
+ namespace: command.namespace,
248
+ })),
249
+ },
184
250
  };
185
251
  },
186
252
 
187
- '/model': async (args, context) => {
188
- // Read available models from centralized constants
189
- const availableModels = {
190
- claude: CLAUDE_MODELS.OPTIONS.map(o => o.value),
191
- cursor: CURSOR_MODELS.OPTIONS.map(o => o.value),
192
- codex: CODEX_MODELS.OPTIONS.map(o => o.value)
193
- };
194
-
195
- const currentProvider = context?.provider || 'claude';
196
- const currentModel = context?.model || CLAUDE_MODELS.DEFAULT;
197
-
198
- return {
199
- type: 'builtin',
200
- action: 'model',
201
- data: {
202
- current: {
203
- provider: currentProvider,
204
- model: currentModel
205
- },
206
- available: availableModels,
207
- message: args.length > 0
208
- ? `Switching to model: ${args[0]}`
209
- : `Current model: ${currentModel}`
210
- }
211
- };
212
- },
253
+ "/models": executeModelsCommand,
213
254
 
214
- '/cost': async (args, context) => {
255
+ "/cost": async (args, context) => {
215
256
  const tokenUsage = context?.tokenUsage || {};
216
- const provider = context?.provider || 'claude';
217
- const model =
218
- context?.model ||
219
- (provider === 'cursor'
220
- ? CURSOR_MODELS.DEFAULT
221
- : provider === 'codex'
222
- ? CODEX_MODELS.DEFAULT
223
- : CLAUDE_MODELS.DEFAULT);
224
-
225
- const used = Number(tokenUsage.used ?? tokenUsage.totalUsed ?? tokenUsage.total_tokens ?? 0) || 0;
257
+ const provider = readModelProvider(context?.provider);
258
+ const catalog = (await providerModelsService.getProviderModels(provider)).models;
259
+ const model = await resolveCommandModel(provider, catalog, context?.sessionId);
260
+
261
+ const reportedUsed =
262
+ Number(
263
+ tokenUsage.used ?? tokenUsage.totalUsed ?? tokenUsage.total_tokens ?? 0,
264
+ ) || 0;
226
265
  const total =
227
266
  Number(
228
267
  tokenUsage.total ??
229
268
  tokenUsage.contextWindow ??
230
- parseInt(process.env.CONTEXT_WINDOW || '160000', 10),
231
- ) || 160000;
232
- const percentage = total > 0 ? Number(((used / total) * 100).toFixed(1)) : 0;
233
-
269
+ 0,
270
+ ) || 0;
234
271
  const inputTokensRaw =
235
272
  Number(
236
273
  tokenUsage.inputTokens ??
237
274
  tokenUsage.input ??
275
+ tokenUsage.input_tokens ??
238
276
  tokenUsage.cumulativeInputTokens ??
277
+ tokenUsage.breakdown?.input ??
239
278
  tokenUsage.promptTokens ??
240
279
  0,
241
280
  ) || 0;
@@ -243,106 +282,103 @@ Custom commands can be created in:
243
282
  Number(
244
283
  tokenUsage.outputTokens ??
245
284
  tokenUsage.output ??
285
+ tokenUsage.output_tokens ??
246
286
  tokenUsage.cumulativeOutputTokens ??
287
+ tokenUsage.breakdown?.output ??
247
288
  tokenUsage.completionTokens ??
248
289
  0,
249
290
  ) || 0;
250
- const cacheTokens =
251
- Number(
252
- tokenUsage.cacheReadTokens ??
253
- tokenUsage.cacheCreationTokens ??
254
- tokenUsage.cacheTokens ??
255
- tokenUsage.cachedTokens ??
256
- 0,
257
- ) || 0;
258
-
259
- // If we only have total used tokens, treat them as input for display/estimation.
260
- const inputTokens =
261
- inputTokensRaw > 0 || outputTokens > 0 || cacheTokens > 0 ? inputTokensRaw + cacheTokens : used;
262
-
263
- // Rough default rates by provider (USD / 1M tokens).
264
- const pricingByProvider = {
265
- claude: { input: 3, output: 15 },
266
- cursor: { input: 3, output: 15 },
267
- codex: { input: 1.5, output: 6 },
268
- };
269
- const rates = pricingByProvider[provider] || pricingByProvider.claude;
270
-
271
- const inputCost = (inputTokens / 1_000_000) * rates.input;
272
- const outputCost = (outputTokens / 1_000_000) * rates.output;
273
- const totalCost = inputCost + outputCost;
291
+ const hasTokenBreakdown = inputTokensRaw > 0 || outputTokens > 0;
292
+ const used = reportedUsed || inputTokensRaw + outputTokens;
274
293
 
275
294
  return {
276
- type: 'builtin',
277
- action: 'cost',
295
+ type: "builtin",
296
+ action: "cost",
278
297
  data: {
279
298
  tokenUsage: {
280
299
  used,
281
300
  total,
282
- percentage,
283
- },
284
- cost: {
285
- input: inputCost.toFixed(4),
286
- output: outputCost.toFixed(4),
287
- total: totalCost.toFixed(4),
288
301
  },
302
+ ...(hasTokenBreakdown
303
+ ? {
304
+ tokenBreakdown: {
305
+ input: inputTokensRaw,
306
+ output: outputTokens,
307
+ },
308
+ }
309
+ : {}),
310
+ provider,
289
311
  model,
290
312
  },
291
313
  };
292
314
  },
293
315
 
294
- '/status': async (args, context) => {
316
+ "/status": async (args, context) => {
295
317
  // Read version from package.json
296
- const packageJsonPath = path.join(APP_ROOT, 'package.json');
297
- let version = 'unknown';
298
- let packageName = 'claude-code-ui';
318
+ const packageJsonPath = path.join(APP_ROOT, "package.json");
319
+ let version = "unknown";
320
+ let packageName = "claude-code-ui";
299
321
 
300
322
  try {
301
- const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
323
+ const packageJson = JSON.parse(
324
+ await fs.readFile(packageJsonPath, "utf8"),
325
+ );
302
326
  version = packageJson.version;
303
327
  packageName = packageJson.name;
304
328
  } catch (err) {
305
- console.error('Error reading package.json:', err);
329
+ console.error("Error reading package.json:", err);
306
330
  }
307
331
 
308
332
  const uptime = process.uptime();
309
333
  const uptimeMinutes = Math.floor(uptime / 60);
310
334
  const uptimeHours = Math.floor(uptimeMinutes / 60);
311
- const uptimeFormatted = uptimeHours > 0
312
- ? `${uptimeHours}h ${uptimeMinutes % 60}m`
313
- : `${uptimeMinutes}m`;
335
+ const uptimeFormatted =
336
+ uptimeHours > 0
337
+ ? `${uptimeHours}h ${uptimeMinutes % 60}m`
338
+ : `${uptimeMinutes}m`;
339
+
340
+ const statusProvider = readModelProvider(context?.provider);
341
+ const statusCatalog = (await providerModelsService.getProviderModels(statusProvider)).models;
342
+ const model = await resolveCommandModel(statusProvider, statusCatalog, context?.sessionId);
343
+ const memoryUsage = process.memoryUsage();
314
344
 
315
345
  return {
316
- type: 'builtin',
317
- action: 'status',
346
+ type: "builtin",
347
+ action: "status",
318
348
  data: {
319
349
  version,
320
350
  packageName,
321
351
  uptime: uptimeFormatted,
322
352
  uptimeSeconds: Math.floor(uptime),
323
- model: context?.model || CLAUDE_MODELS.DEFAULT,
324
- provider: context?.provider || 'claude',
353
+ model,
354
+ provider: statusProvider,
325
355
  nodeVersion: process.version,
326
- platform: process.platform
327
- }
356
+ platform: process.platform,
357
+ pid: process.pid,
358
+ memoryUsage: {
359
+ rssMb: Math.round(memoryUsage.rss / 1024 / 1024),
360
+ heapUsedMb: Math.round(memoryUsage.heapUsed / 1024 / 1024),
361
+ heapTotalMb: Math.round(memoryUsage.heapTotal / 1024 / 1024),
362
+ },
363
+ },
328
364
  };
329
365
  },
330
366
 
331
- '/memory': async (args, context) => {
367
+ "/memory": async (args, context) => {
332
368
  const projectPath = context?.projectPath;
333
369
 
334
370
  if (!projectPath) {
335
371
  return {
336
- type: 'builtin',
337
- action: 'memory',
372
+ type: "builtin",
373
+ action: "memory",
338
374
  data: {
339
- error: 'No project selected',
340
- message: 'Please select a project to access its CLAUDE.md file'
341
- }
375
+ error: "No project selected",
376
+ message: "Please select a project to access its CLAUDE.md file",
377
+ },
342
378
  };
343
379
  }
344
380
 
345
- const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
381
+ const claudeMdPath = path.join(projectPath, "CLAUDE.md");
346
382
 
347
383
  // Check if CLAUDE.md exists
348
384
  let exists = false;
@@ -354,85 +390,63 @@ Custom commands can be created in:
354
390
  }
355
391
 
356
392
  return {
357
- type: 'builtin',
358
- action: 'memory',
393
+ type: "builtin",
394
+ action: "memory",
359
395
  data: {
360
396
  path: claudeMdPath,
361
397
  exists,
362
398
  message: exists
363
399
  ? `Opening CLAUDE.md at ${claudeMdPath}`
364
- : `CLAUDE.md not found at ${claudeMdPath}. Create it to store project-specific instructions.`
365
- }
400
+ : `CLAUDE.md not found at ${claudeMdPath}. Create it to store project-specific instructions.`,
401
+ },
366
402
  };
367
403
  },
368
404
 
369
- '/config': async (args, context) => {
405
+ "/config": async (args, context) => {
370
406
  return {
371
- type: 'builtin',
372
- action: 'config',
407
+ type: "builtin",
408
+ action: "config",
373
409
  data: {
374
- message: 'Opening settings...'
375
- }
410
+ message: "Opening settings...",
411
+ },
376
412
  };
377
413
  },
378
-
379
- '/rewind': async (args, context) => {
380
- const steps = args[0] ? parseInt(args[0]) : 1;
381
-
382
- if (isNaN(steps) || steps < 1) {
383
- return {
384
- type: 'builtin',
385
- action: 'rewind',
386
- data: {
387
- error: 'Invalid steps parameter',
388
- message: 'Usage: /rewind [number] - Rewind conversation by N steps (default: 1)'
389
- }
390
- };
391
- }
392
-
393
- return {
394
- type: 'builtin',
395
- action: 'rewind',
396
- data: {
397
- steps,
398
- message: `Rewinding conversation by ${steps} step${steps > 1 ? 's' : ''}...`
399
- }
400
- };
401
- }
402
414
  };
403
415
 
404
416
  /**
405
417
  * POST /api/commands/list
406
418
  * List all available commands from project and user directories
407
419
  */
408
- router.post('/list', async (req, res) => {
420
+ router.post("/list", async (req, res) => {
409
421
  try {
410
422
  const { projectPath } = req.body;
411
423
  const allCommands = [...builtInCommands];
412
424
 
413
425
  // Scan project-level commands (.claude/commands/)
414
426
  if (projectPath) {
415
- const projectCommandsDir = path.join(projectPath, '.claude', 'commands');
427
+ const projectCommandsDir = path.join(projectPath, ".claude", "commands");
416
428
  const projectCommands = await scanCommandsDirectory(
417
429
  projectCommandsDir,
418
430
  projectCommandsDir,
419
- 'project'
431
+ "project",
420
432
  );
421
433
  allCommands.push(...projectCommands);
422
434
  }
423
435
 
424
436
  // Scan user-level commands (~/.claude/commands/)
425
437
  const homeDir = os.homedir();
426
- const userCommandsDir = path.join(homeDir, '.claude', 'commands');
438
+ const userCommandsDir = path.join(homeDir, ".claude", "commands");
427
439
  const userCommands = await scanCommandsDirectory(
428
440
  userCommandsDir,
429
441
  userCommandsDir,
430
- 'user'
442
+ "user",
431
443
  );
432
444
  allCommands.push(...userCommands);
433
445
 
434
446
  // Separate built-in and custom commands
435
- const customCommands = allCommands.filter(cmd => cmd.namespace !== 'builtin');
447
+ const customCommands = allCommands.filter(
448
+ (cmd) => cmd.namespace !== "builtin",
449
+ );
436
450
 
437
451
  // Sort commands alphabetically by name
438
452
  customCommands.sort((a, b) => a.name.localeCompare(b.name));
@@ -440,13 +454,13 @@ router.post('/list', async (req, res) => {
440
454
  res.json({
441
455
  builtIn: builtInCommands,
442
456
  custom: customCommands,
443
- count: allCommands.length
457
+ count: allCommands.length,
444
458
  });
445
459
  } catch (error) {
446
- console.error('Error listing commands:', error);
460
+ console.error("Error listing commands:", error);
447
461
  res.status(500).json({
448
- error: 'Failed to list commands',
449
- message: error.message
462
+ error: "Failed to list commands",
463
+ message: error.message,
450
464
  });
451
465
  }
452
466
  });
@@ -457,13 +471,13 @@ router.post('/list', async (req, res) => {
457
471
  * This endpoint prepares the command content but doesn't execute bash commands yet
458
472
  * (that will be handled in the command parser utility)
459
473
  */
460
- router.post('/execute', async (req, res) => {
474
+ router.post("/execute", async (req, res) => {
461
475
  try {
462
476
  const { commandName, commandPath, args = [], context = {} } = req.body;
463
477
 
464
478
  if (!commandName) {
465
479
  return res.status(400).json({
466
- error: 'Command name is required'
480
+ error: "Command name is required",
467
481
  });
468
482
  }
469
483
 
@@ -474,14 +488,17 @@ router.post('/execute', async (req, res) => {
474
488
  const result = await handler(args, context);
475
489
  return res.json({
476
490
  ...result,
477
- command: commandName
491
+ command: commandName,
478
492
  });
479
493
  } catch (error) {
480
- console.error(`Error executing built-in command ${commandName}:`, error);
494
+ console.error(
495
+ `Error executing built-in command ${commandName}:`,
496
+ error,
497
+ );
481
498
  return res.status(500).json({
482
- error: 'Command execution failed',
499
+ error: "Command execution failed",
483
500
  message: error.message,
484
- command: commandName
501
+ command: commandName,
485
502
  });
486
503
  }
487
504
  }
@@ -489,7 +506,7 @@ router.post('/execute', async (req, res) => {
489
506
  // Handle custom commands
490
507
  if (!commandPath) {
491
508
  return res.status(400).json({
492
- error: 'Command path is required for custom commands'
509
+ error: "Command path is required for custom commands",
493
510
  });
494
511
  }
495
512
 
@@ -497,56 +514,62 @@ router.post('/execute', async (req, res) => {
497
514
  // Security: validate commandPath is within allowed directories
498
515
  {
499
516
  const resolvedPath = path.resolve(commandPath);
500
- const userBase = path.resolve(path.join(os.homedir(), '.claude', 'commands'));
517
+ const userBase = path.resolve(
518
+ path.join(os.homedir(), ".claude", "commands"),
519
+ );
501
520
  const projectBase = context?.projectPath
502
- ? path.resolve(path.join(context.projectPath, '.claude', 'commands'))
521
+ ? path.resolve(path.join(context.projectPath, ".claude", "commands"))
503
522
  : null;
504
523
  const isUnder = (base) => {
505
524
  const rel = path.relative(base, resolvedPath);
506
- return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
525
+ return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
507
526
  };
508
527
  if (!(isUnder(userBase) || (projectBase && isUnder(projectBase)))) {
509
528
  return res.status(403).json({
510
- error: 'Access denied',
511
- message: 'Command must be in .claude/commands directory'
529
+ error: "Access denied",
530
+ message: "Command must be in .claude/commands directory",
512
531
  });
513
532
  }
514
533
  }
515
- const content = await fs.readFile(commandPath, 'utf8');
516
- const { data: metadata, content: commandContent } = parseFrontmatter(content);
534
+ const content = await fs.readFile(commandPath, "utf8");
535
+ const { data: metadata, content: commandContent } =
536
+ parseFrontMatter(content);
517
537
  // Basic argument replacement (will be enhanced in command parser utility)
518
538
  let processedContent = commandContent;
519
539
 
520
540
  // Replace $ARGUMENTS with all arguments joined
521
- const argsString = args.join(' ');
541
+ const argsString = args.join(" ");
522
542
  processedContent = processedContent.replace(/\$ARGUMENTS/g, argsString);
523
543
 
524
544
  // Replace $1, $2, etc. with positional arguments
525
545
  args.forEach((arg, index) => {
526
546
  const placeholder = `$${index + 1}`;
527
- processedContent = processedContent.replace(new RegExp(`\\${placeholder}\\b`, 'g'), arg);
547
+ processedContent = processedContent.replace(
548
+ new RegExp(`\\${placeholder}\\b`, "g"),
549
+ arg,
550
+ );
528
551
  });
529
552
 
530
553
  res.json({
531
- type: 'custom',
554
+ type: "custom",
532
555
  command: commandName,
533
556
  content: processedContent,
534
557
  metadata,
535
- hasFileIncludes: processedContent.includes('@'),
536
- hasBashCommands: processedContent.includes('!')
558
+ hasFileIncludes: processedContent.includes("@"),
559
+ hasBashCommands: processedContent.includes("!"),
537
560
  });
538
561
  } catch (error) {
539
- if (error.code === 'ENOENT') {
562
+ if (error.code === "ENOENT") {
540
563
  return res.status(404).json({
541
- error: 'Command not found',
542
- message: `Command file not found: ${req.body.commandPath}`
564
+ error: "Command not found",
565
+ message: `Command file not found: ${req.body.commandPath}`,
543
566
  });
544
567
  }
545
568
 
546
- console.error('Error executing command:', error);
569
+ console.error("Error executing command:", error);
547
570
  res.status(500).json({
548
- error: 'Failed to execute command',
549
- message: error.message
571
+ error: "Failed to execute command",
572
+ message: error.message,
550
573
  });
551
574
  }
552
575
  });