@glwhappen/web-code 1.32.9 → 1.32.10

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