@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.
- package/README.de.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.tr.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/api-docs.html +6 -7
- package/dist/assets/{index-D_7CSvqO.js → index-BLLsK3sG.js} +276 -261
- package/dist/assets/index-Dl5QP21C.css +32 -0
- package/dist/index.html +2 -2
- package/dist/modelConstants.js +841 -0
- package/dist-server/server/claude-sdk.js +57 -34
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +6 -3
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +3 -1
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/gemini-response-handler.js +34 -0
- package/dist-server/server/gemini-response-handler.js.map +1 -1
- package/dist-server/server/index.js +131 -19
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-management.service.js +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +4 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +143 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +84 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -39
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +754 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +2 -15
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +27 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +3 -9
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +2 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +92 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +267 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +115 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +410 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +19 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +2 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +42 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +1 -9
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +199 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +7 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/mcp.test.js +73 -6
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +66 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +264 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +270 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/skills.test.js +33 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +18 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/openai-codex.js +32 -4
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +287 -0
- package/dist-server/server/opencode-cli.js.map +1 -0
- package/dist-server/server/opencode-cli.test.js +84 -0
- package/dist-server/server/opencode-cli.test.js.map +1 -0
- package/dist-server/server/routes/agent.js +21 -8
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/routes/commands.js +202 -209
- package/dist-server/server/routes/commands.js.map +1 -1
- package/dist-server/server/routes/cursor.js +2 -2
- package/dist-server/server/routes/cursor.js.map +1 -1
- package/dist-server/server/routes/settings.js +0 -10
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/routes/tests/commands.test.js +76 -0
- package/dist-server/server/routes/tests/commands.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +286 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +3 -1
- package/public/api-docs.html +878 -0
- package/public/modelConstants.js +841 -0
- package/server/claude-sdk.js +64 -35
- package/server/cursor-cli.js +6 -3
- package/server/gemini-cli.js +7 -1
- package/server/gemini-response-handler.js +38 -0
- package/server/index.js +150 -19
- package/server/modules/database/index.ts +1 -0
- package/server/modules/projects/services/project-management.service.ts +2 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +7 -1
- package/server/modules/providers/README.md +11 -3
- package/server/modules/providers/list/claude/claude-models.provider.ts +193 -0
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +125 -0
- package/server/modules/providers/list/codex/codex-skills.provider.ts +10 -50
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +820 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +7 -20
- package/server/modules/providers/list/cursor/cursor.provider.ts +3 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +42 -0
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +3 -10
- package/server/modules/providers/list/gemini/gemini.provider.ts +3 -0
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +111 -0
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +339 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +158 -0
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +506 -0
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
- package/server/modules/providers/list/opencode/opencode.provider.ts +27 -0
- package/server/modules/providers/provider.registry.ts +2 -0
- package/server/modules/providers/provider.routes.ts +62 -2
- package/server/modules/providers/services/mcp.service.ts +1 -12
- package/server/modules/providers/services/provider-models.service.ts +325 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +8 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +2 -0
- package/server/modules/providers/tests/mcp.test.ts +93 -6
- package/server/modules/providers/tests/opencode-models.test.ts +73 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +336 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +318 -0
- package/server/modules/providers/tests/skills.test.ts +66 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +21 -1
- package/server/modules/websocket/services/shell-websocket.service.ts +9 -0
- package/server/openai-codex.js +40 -4
- package/server/opencode-cli.js +336 -0
- package/server/opencode-cli.test.js +95 -0
- package/server/routes/agent.js +22 -8
- package/server/routes/commands.js +254 -233
- package/server/routes/cursor.js +2 -2
- package/server/routes/settings.js +1 -10
- package/server/routes/tests/commands.test.js +82 -0
- package/server/shared/interfaces.ts +45 -0
- package/server/shared/types.ts +88 -1
- package/server/shared/utils.ts +384 -0
- package/dist/assets/index-DdxLnCfK.css +0 -32
- package/dist-server/shared/modelConstants.js +0 -99
- package/dist-server/shared/modelConstants.js.map +0 -1
- package/shared/modelConstants.js +0 -107
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { promises as fs } from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
4
|
|
|
5
|
-
import express from
|
|
5
|
+
import express from "express";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { parseFrontMatter } from
|
|
9
|
-
import { findAppRoot, getModuleDir } from
|
|
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(
|
|
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(
|
|
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,
|
|
45
|
-
const { data: frontmatter, content: commandContent } =
|
|
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 =
|
|
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(
|
|
56
|
-
description = firstLine.replace(/^#+\s*/,
|
|
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 !==
|
|
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:
|
|
88
|
-
description:
|
|
89
|
-
namespace:
|
|
90
|
-
metadata: { type:
|
|
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:
|
|
100
|
-
description:
|
|
101
|
-
namespace:
|
|
102
|
-
metadata: { type:
|
|
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:
|
|
106
|
-
description:
|
|
107
|
-
namespace:
|
|
108
|
-
metadata: { type:
|
|
176
|
+
name: "/cost",
|
|
177
|
+
description: "Display token usage information",
|
|
178
|
+
namespace: "builtin",
|
|
179
|
+
metadata: { type: "builtin" },
|
|
109
180
|
},
|
|
110
181
|
{
|
|
111
|
-
name:
|
|
112
|
-
description:
|
|
113
|
-
namespace:
|
|
114
|
-
metadata: { type:
|
|
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:
|
|
118
|
-
description:
|
|
119
|
-
namespace:
|
|
120
|
-
metadata: { type:
|
|
188
|
+
name: "/config",
|
|
189
|
+
description: "Open settings and configuration",
|
|
190
|
+
namespace: "builtin",
|
|
191
|
+
metadata: { type: "builtin" },
|
|
121
192
|
},
|
|
122
193
|
{
|
|
123
|
-
name:
|
|
124
|
-
description:
|
|
125
|
-
namespace:
|
|
126
|
-
metadata: { type:
|
|
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
|
-
|
|
206
|
+
"/help": async (args, context) => {
|
|
142
207
|
const helpText = `# Claude Code Commands
|
|
143
208
|
|
|
144
209
|
## Built-in Commands
|
|
145
210
|
|
|
146
|
-
${builtInCommands
|
|
211
|
+
${builtInCommands
|
|
212
|
+
.map(
|
|
213
|
+
(cmd) => `### ${cmd.name}
|
|
147
214
|
${cmd.description}
|
|
148
|
-
|
|
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:
|
|
171
|
-
action:
|
|
239
|
+
type: "builtin",
|
|
240
|
+
action: "help",
|
|
172
241
|
data: {
|
|
173
242
|
content: helpText,
|
|
174
|
-
format:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
233
|
-
) ||
|
|
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
|
|
253
|
-
|
|
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:
|
|
279
|
-
action:
|
|
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
|
-
|
|
316
|
+
"/status": async (args, context) => {
|
|
297
317
|
// Read version from package.json
|
|
298
|
-
const packageJsonPath = path.join(APP_ROOT,
|
|
299
|
-
let version =
|
|
300
|
-
let packageName =
|
|
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(
|
|
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(
|
|
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 =
|
|
314
|
-
|
|
315
|
-
|
|
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:
|
|
319
|
-
action:
|
|
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
|
|
326
|
-
provider:
|
|
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
|
-
|
|
367
|
+
"/memory": async (args, context) => {
|
|
334
368
|
const projectPath = context?.projectPath;
|
|
335
369
|
|
|
336
370
|
if (!projectPath) {
|
|
337
371
|
return {
|
|
338
|
-
type:
|
|
339
|
-
action:
|
|
372
|
+
type: "builtin",
|
|
373
|
+
action: "memory",
|
|
340
374
|
data: {
|
|
341
|
-
error:
|
|
342
|
-
message:
|
|
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,
|
|
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:
|
|
360
|
-
action:
|
|
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
|
-
|
|
405
|
+
"/config": async (args, context) => {
|
|
372
406
|
return {
|
|
373
|
-
type:
|
|
374
|
-
action:
|
|
407
|
+
type: "builtin",
|
|
408
|
+
action: "config",
|
|
375
409
|
data: {
|
|
376
|
-
message:
|
|
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(
|
|
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,
|
|
427
|
+
const projectCommandsDir = path.join(projectPath, ".claude", "commands");
|
|
418
428
|
const projectCommands = await scanCommandsDirectory(
|
|
419
429
|
projectCommandsDir,
|
|
420
430
|
projectCommandsDir,
|
|
421
|
-
|
|
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,
|
|
438
|
+
const userCommandsDir = path.join(homeDir, ".claude", "commands");
|
|
429
439
|
const userCommands = await scanCommandsDirectory(
|
|
430
440
|
userCommandsDir,
|
|
431
441
|
userCommandsDir,
|
|
432
|
-
|
|
442
|
+
"user",
|
|
433
443
|
);
|
|
434
444
|
allCommands.push(...userCommands);
|
|
435
445
|
|
|
436
446
|
// Separate built-in and custom commands
|
|
437
|
-
const customCommands = allCommands.filter(
|
|
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(
|
|
460
|
+
console.error("Error listing commands:", error);
|
|
449
461
|
res.status(500).json({
|
|
450
|
-
error:
|
|
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(
|
|
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:
|
|
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(
|
|
494
|
+
console.error(
|
|
495
|
+
`Error executing built-in command ${commandName}:`,
|
|
496
|
+
error,
|
|
497
|
+
);
|
|
483
498
|
return res.status(500).json({
|
|
484
|
-
error:
|
|
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:
|
|
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(
|
|
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,
|
|
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 !==
|
|
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:
|
|
513
|
-
message:
|
|
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,
|
|
518
|
-
const { data: metadata, content: commandContent } =
|
|
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(
|
|
547
|
+
processedContent = processedContent.replace(
|
|
548
|
+
new RegExp(`\\${placeholder}\\b`, "g"),
|
|
549
|
+
arg,
|
|
550
|
+
);
|
|
530
551
|
});
|
|
531
552
|
|
|
532
553
|
res.json({
|
|
533
|
-
type:
|
|
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 ===
|
|
562
|
+
if (error.code === "ENOENT") {
|
|
542
563
|
return res.status(404).json({
|
|
543
|
-
error:
|
|
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(
|
|
569
|
+
console.error("Error executing command:", error);
|
|
549
570
|
res.status(500).json({
|
|
550
|
-
error:
|
|
551
|
-
message: error.message
|
|
571
|
+
error: "Failed to execute command",
|
|
572
|
+
message: error.message,
|
|
552
573
|
});
|
|
553
574
|
}
|
|
554
575
|
});
|