@glwhappen/web-code 1.32.9 → 1.32.11

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