@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.
- 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-CBo8yakG.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,15 +1,69 @@
|
|
|
1
|
-
import { promises as fs } from
|
|
2
|
-
import os from
|
|
3
|
-
import path from
|
|
4
|
-
import express from
|
|
5
|
-
import {
|
|
6
|
-
import { parseFrontMatter } from
|
|
7
|
-
import { findAppRoot, getModuleDir } from
|
|
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(
|
|
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,
|
|
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 =
|
|
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(
|
|
46
|
-
description = firstLine.replace(/^#+\s*/,
|
|
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 !==
|
|
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:
|
|
77
|
-
description:
|
|
78
|
-
namespace:
|
|
79
|
-
metadata: { type:
|
|
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:
|
|
89
|
-
description:
|
|
90
|
-
namespace:
|
|
91
|
-
metadata: { type:
|
|
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:
|
|
95
|
-
description:
|
|
96
|
-
namespace:
|
|
97
|
-
metadata: { type:
|
|
142
|
+
name: "/cost",
|
|
143
|
+
description: "Display token usage information",
|
|
144
|
+
namespace: "builtin",
|
|
145
|
+
metadata: { type: "builtin" },
|
|
98
146
|
},
|
|
99
147
|
{
|
|
100
|
-
name:
|
|
101
|
-
description:
|
|
102
|
-
namespace:
|
|
103
|
-
metadata: { type:
|
|
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:
|
|
107
|
-
description:
|
|
108
|
-
namespace:
|
|
109
|
-
metadata: { type:
|
|
154
|
+
name: "/config",
|
|
155
|
+
description: "Open settings and configuration",
|
|
156
|
+
namespace: "builtin",
|
|
157
|
+
metadata: { type: "builtin" },
|
|
110
158
|
},
|
|
111
159
|
{
|
|
112
|
-
name:
|
|
113
|
-
description:
|
|
114
|
-
namespace:
|
|
115
|
-
metadata: { type:
|
|
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
|
-
|
|
171
|
+
"/help": async (args, context) => {
|
|
130
172
|
const helpText = `# Claude Code Commands
|
|
131
173
|
|
|
132
174
|
## Built-in Commands
|
|
133
175
|
|
|
134
|
-
${builtInCommands
|
|
176
|
+
${builtInCommands
|
|
177
|
+
.map((cmd) => `### ${cmd.name}
|
|
135
178
|
${cmd.description}
|
|
136
|
-
`)
|
|
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:
|
|
158
|
-
action:
|
|
201
|
+
type: "builtin",
|
|
202
|
+
action: "help",
|
|
159
203
|
data: {
|
|
160
204
|
content: helpText,
|
|
161
|
-
format:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
214
|
+
"/models": executeModelsCommand,
|
|
215
|
+
"/cost": async (args, context) => {
|
|
199
216
|
const tokenUsage = context?.tokenUsage || {};
|
|
200
|
-
const provider = context?.provider
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
|
223
|
-
|
|
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:
|
|
241
|
-
action:
|
|
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
|
-
|
|
261
|
+
"/status": async (args, context) => {
|
|
258
262
|
// Read version from package.json
|
|
259
|
-
const packageJsonPath = path.join(APP_ROOT,
|
|
260
|
-
let version =
|
|
261
|
-
let packageName =
|
|
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,
|
|
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(
|
|
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:
|
|
278
|
-
action:
|
|
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
|
|
285
|
-
provider:
|
|
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
|
-
|
|
305
|
+
"/memory": async (args, context) => {
|
|
292
306
|
const projectPath = context?.projectPath;
|
|
293
307
|
if (!projectPath) {
|
|
294
308
|
return {
|
|
295
|
-
type:
|
|
296
|
-
action:
|
|
309
|
+
type: "builtin",
|
|
310
|
+
action: "memory",
|
|
297
311
|
data: {
|
|
298
|
-
error:
|
|
299
|
-
message:
|
|
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,
|
|
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:
|
|
315
|
-
action:
|
|
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
|
-
|
|
339
|
+
"/config": async (args, context) => {
|
|
326
340
|
return {
|
|
327
|
-
type:
|
|
328
|
-
action:
|
|
341
|
+
type: "builtin",
|
|
342
|
+
action: "config",
|
|
329
343
|
data: {
|
|
330
|
-
message:
|
|
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(
|
|
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,
|
|
367
|
-
const projectCommands = await scanCommandsDirectory(projectCommandsDir, projectCommandsDir,
|
|
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,
|
|
373
|
-
const userCommands = await scanCommandsDirectory(userCommandsDir, userCommandsDir,
|
|
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 !==
|
|
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(
|
|
379
|
+
console.error("Error listing commands:", error);
|
|
387
380
|
res.status(500).json({
|
|
388
|
-
error:
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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(),
|
|
429
|
+
const userBase = path.resolve(path.join(os.homedir(), ".claude", "commands"));
|
|
437
430
|
const projectBase = context?.projectPath
|
|
438
|
-
? path.resolve(path.join(context.projectPath,
|
|
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 !==
|
|
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:
|
|
447
|
-
message:
|
|
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,
|
|
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`,
|
|
454
|
+
processedContent = processedContent.replace(new RegExp(`\\${placeholder}\\b`, "g"), arg);
|
|
462
455
|
});
|
|
463
456
|
res.json({
|
|
464
|
-
type:
|
|
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 ===
|
|
466
|
+
if (error.code === "ENOENT") {
|
|
474
467
|
return res.status(404).json({
|
|
475
|
-
error:
|
|
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(
|
|
472
|
+
console.error("Error executing command:", error);
|
|
480
473
|
res.status(500).json({
|
|
481
|
-
error:
|
|
482
|
-
message: error.message
|
|
474
|
+
error: "Failed to execute command",
|
|
475
|
+
message: error.message,
|
|
483
476
|
});
|
|
484
477
|
}
|
|
485
478
|
});
|