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