claude-code-workflow 6.3.48 → 6.3.49
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/.claude/CLAUDE.md +6 -8
- package/.claude/agents/action-planning-agent.md +28 -45
- package/.claude/agents/cli-lite-planning-agent.md +93 -1
- package/.claude/agents/code-developer.md +144 -27
- package/.claude/commands/ccw-coordinator.md +175 -21
- package/.claude/commands/ccw-debug.md +832 -0
- package/.claude/commands/ccw.md +90 -9
- package/.claude/commands/cli/cli-init.md +1 -0
- package/.claude/commands/issue/convert-to-plan.md +718 -0
- package/.claude/commands/issue/from-brainstorm.md +382 -0
- package/.claude/commands/memory/tips.md +332 -0
- package/.claude/commands/workflow/analyze-with-file.md +804 -0
- package/.claude/commands/workflow/brainstorm/auto-parallel.md +18 -43
- package/.claude/commands/workflow/brainstorm/role-analysis.md +705 -0
- package/.claude/commands/workflow/brainstorm-with-file.md +1153 -0
- package/.claude/commands/workflow/debug-with-file.md +7 -5
- package/.claude/commands/workflow/execute.md +6 -4
- package/.claude/commands/workflow/lite-plan.md +2 -2
- package/.claude/commands/workflow/plan-verify.md +162 -327
- package/.claude/commands/workflow/plan.md +162 -26
- package/.claude/commands/workflow/replan.md +78 -2
- package/.claude/commands/workflow/{review-fix.md → review-cycle-fix.md} +6 -6
- package/.claude/commands/workflow/review-module-cycle.md +2 -2
- package/.claude/commands/workflow/review-session-cycle.md +2 -2
- package/.claude/commands/workflow/tools/conflict-resolution.md +16 -26
- package/.claude/commands/workflow/tools/context-gather.md +81 -118
- package/.claude/commands/workflow/tools/task-generate-agent.md +94 -10
- package/.claude/skills/ccw-help/command.json +4 -4
- package/.claude/skills/lite-skill-generator/SKILL.md +650 -0
- package/.claude/skills/lite-skill-generator/templates/simple-skill.md +68 -0
- package/.claude/skills/lite-skill-generator/templates/style-guide.md +64 -0
- package/.claude/skills/skill-generator/SKILL.md +277 -85
- package/.claude/skills/skill-generator/phases/01-requirements-discovery.md +4 -15
- package/.claude/skills/skill-generator/phases/02-structure-generation.md +72 -17
- package/.claude/skills/skill-generator/phases/03-phase-generation.md +218 -51
- package/.claude/skills/skill-generator/phases/04-specs-templates.md +111 -41
- package/.claude/skills/skill-generator/phases/05-validation.md +139 -56
- package/.claude/skills/skill-generator/templates/autonomous-action.md +78 -268
- package/.claude/skills/skill-generator/templates/autonomous-orchestrator.md +14 -0
- package/.claude/skills/skill-generator/templates/code-analysis-action.md +12 -0
- package/.claude/skills/skill-generator/templates/llm-action.md +12 -0
- package/.claude/skills/skill-generator/templates/script-template.md +368 -0
- package/.claude/skills/skill-generator/templates/sequential-phase.md +14 -0
- package/.claude/skills/skill-generator/templates/skill-md.md +14 -0
- package/.claude/skills/skill-tuning/SKILL.md +130 -266
- package/.claude/skills/skill-tuning/phases/orchestrator.md +95 -283
- package/.claude/skills/skill-tuning/specs/problem-taxonomy.md +90 -198
- package/.claude/skills/skill-tuning/specs/tuning-strategies.md +193 -1345
- package/.claude/workflows/cli-templates/schemas/plan-verify-agent-schema.json +47 -0
- package/.claude/workflows/cli-templates/schemas/verify-json-schema.json +158 -0
- package/.claude/workflows/cli-tools-usage.md +1 -1
- package/.codex/AGENTS.md +1 -3
- package/.codex/prompts/analyze-with-file.md +607 -0
- package/.codex/prompts/brainstorm-to-cycle.md +455 -0
- package/.codex/prompts/brainstorm-with-file.md +933 -0
- package/.codex/prompts/debug-with-file.md +15 -20
- package/.codex/skills/ccw-cli-tools/SKILL.md +559 -0
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +29 -5
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts +2 -0
- package/ccw/dist/commands/issue.d.ts.map +1 -1
- package/ccw/dist/commands/issue.js +62 -20
- package/ccw/dist/commands/issue.js.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -1
- package/ccw/dist/config/litellm-api-config-manager.js +5 -3
- package/ccw/dist/config/litellm-api-config-manager.js.map +1 -1
- package/ccw/dist/config/litellm-provider-models.d.ts +73 -0
- package/ccw/dist/config/litellm-provider-models.d.ts.map +1 -0
- package/ccw/dist/config/litellm-provider-models.js +172 -0
- package/ccw/dist/config/litellm-provider-models.js.map +1 -0
- package/ccw/dist/config/provider-models.d.ts +25 -51
- package/ccw/dist/config/provider-models.d.ts.map +1 -1
- package/ccw/dist/config/provider-models.js +84 -149
- package/ccw/dist/config/provider-models.js.map +1 -1
- package/ccw/dist/config/storage-paths.d.ts.map +1 -1
- package/ccw/dist/config/storage-paths.js +23 -5
- package/ccw/dist/config/storage-paths.js.map +1 -1
- package/ccw/dist/core/auth/csrf-middleware.js +3 -3
- package/ccw/dist/core/auth/csrf-middleware.js.map +1 -1
- package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
- package/ccw/dist/core/dashboard-generator.js +3 -1
- package/ccw/dist/core/dashboard-generator.js.map +1 -1
- package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/claude-routes.js +206 -14
- package/ccw/dist/core/routes/claude-routes.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/commands-routes.d.ts +7 -0
- package/ccw/dist/core/routes/commands-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/commands-routes.js +480 -0
- package/ccw/dist/core/routes/commands-routes.js.map +1 -0
- package/ccw/dist/core/routes/model-routes.d.ts +11 -0
- package/ccw/dist/core/routes/model-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/model-routes.js +112 -0
- package/ccw/dist/core/routes/model-routes.js.map +1 -0
- package/ccw/dist/core/routes/nav-status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/nav-status-routes.js +84 -1
- package/ccw/dist/core/routes/nav-status-routes.js.map +1 -1
- package/ccw/dist/core/routes/provider-routes.d.ts +11 -0
- package/ccw/dist/core/routes/provider-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/provider-routes.js +67 -0
- package/ccw/dist/core/routes/provider-routes.js.map +1 -0
- package/ccw/dist/core/routes/skills-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/skills-routes.js +219 -7
- package/ccw/dist/core/routes/skills-routes.js.map +1 -1
- package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/system-routes.js +58 -6
- package/ccw/dist/core/routes/system-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +13 -0
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/mcp-server/index.js +2 -2
- package/ccw/dist/mcp-server/index.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +48 -11
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.js +146 -50
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
- package/ccw/dist/tools/cli-config-manager.d.ts +1 -13
- package/ccw/dist/tools/cli-config-manager.d.ts.map +1 -1
- package/ccw/dist/tools/cli-config-manager.js +3 -27
- package/ccw/dist/tools/cli-config-manager.js.map +1 -1
- package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor-core.js +7 -2
- package/ccw/dist/tools/cli-executor-core.js.map +1 -1
- package/ccw/dist/tools/cli-executor-state.d.ts.map +1 -1
- package/ccw/dist/tools/cli-history-store.d.ts +11 -0
- package/ccw/dist/tools/cli-history-store.d.ts.map +1 -1
- package/ccw/dist/tools/cli-history-store.js +82 -2
- package/ccw/dist/tools/cli-history-store.js.map +1 -1
- package/ccw/dist/tools/command-registry.d.ts +7 -0
- package/ccw/dist/tools/command-registry.d.ts.map +1 -1
- package/ccw/dist/tools/command-registry.js +14 -1
- package/ccw/dist/tools/command-registry.js.map +1 -1
- package/ccw/dist/tools/generate-module-docs.d.ts.map +1 -1
- package/ccw/dist/tools/generate-module-docs.js +11 -7
- package/ccw/dist/tools/generate-module-docs.js.map +1 -1
- package/ccw/dist/tools/litellm-executor.d.ts +1 -0
- package/ccw/dist/tools/litellm-executor.d.ts.map +1 -1
- package/ccw/dist/tools/litellm-executor.js +11 -9
- package/ccw/dist/tools/litellm-executor.js.map +1 -1
- package/ccw/dist/types/skill-types.d.ts +97 -0
- package/ccw/dist/types/skill-types.d.ts.map +1 -0
- package/ccw/dist/types/skill-types.js +6 -0
- package/ccw/dist/types/skill-types.js.map +1 -0
- package/ccw/src/commands/cli.ts +36 -5
- package/ccw/src/commands/issue.ts +81 -26
- package/ccw/src/config/litellm-api-config-manager.ts +5 -3
- package/ccw/src/config/litellm-provider-models.ts +222 -0
- package/ccw/src/config/provider-models.ts +91 -190
- package/ccw/src/config/storage-paths.ts +20 -5
- package/ccw/src/core/auth/csrf-middleware.ts +3 -3
- package/ccw/src/core/dashboard-generator.ts +3 -1
- package/ccw/src/core/routes/claude-routes.ts +233 -15
- package/ccw/src/core/routes/cli-routes.ts +2 -3
- package/ccw/src/core/routes/commands-routes.ts +620 -0
- package/ccw/src/core/routes/nav-status-routes.ts +95 -1
- package/ccw/src/core/routes/provider-routes.ts +78 -0
- package/ccw/src/core/routes/skills-routes.ts +266 -45
- package/ccw/src/core/routes/system-routes.ts +102 -50
- package/ccw/src/core/server.ts +13 -0
- package/ccw/src/mcp-server/index.ts +2 -2
- package/ccw/src/templates/dashboard-css/18-cli-settings.css +35 -0
- package/ccw/src/templates/dashboard-css/37-commands.css +193 -0
- package/ccw/src/templates/dashboard-js/components/navigation.js +4 -0
- package/ccw/src/templates/dashboard-js/i18n.js +116 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +249 -4
- package/ccw/src/templates/dashboard-js/views/commands-manager.js +503 -0
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +7 -7
- package/ccw/src/templates/dashboard-js/views/mcp-manager.js +2 -7
- package/ccw/src/templates/dashboard-js/views/skills-manager.js +164 -23
- package/ccw/src/templates/dashboard.html +7 -0
- package/ccw/src/tools/claude-cli-tools.ts +170 -56
- package/ccw/src/tools/cli-config-manager.ts +2 -33
- package/ccw/src/tools/cli-executor-core.ts +8 -2
- package/ccw/src/tools/cli-history-store.ts +92 -2
- package/ccw/src/tools/command-registry.ts +16 -1
- package/ccw/src/tools/generate-module-docs.ts +11 -7
- package/ccw/src/tools/litellm-executor.ts +13 -9
- package/ccw/src/types/skill-types.ts +99 -0
- package/package.json +1 -1
- package/.claude/commands/enhance-prompt.md +0 -93
- package/.claude/commands/memory/code-map-memory.md +0 -687
- package/.claude/commands/memory/docs.md +0 -615
- package/.claude/commands/memory/load-skill-memory.md +0 -182
- package/.claude/commands/memory/skill-memory.md +0 -525
- package/.claude/commands/memory/swagger-docs.md +0 -773
- package/.claude/commands/memory/tech-research-rules.md +0 -310
- package/.claude/commands/memory/workflow-skill-memory.md +0 -517
- package/.claude/commands/task/breakdown.md +0 -208
- package/.claude/commands/task/create.md +0 -152
- package/.claude/commands/task/execute.md +0 -270
- package/.claude/commands/task/replan.md +0 -441
- package/.claude/commands/version.md +0 -254
- package/.claude/commands/workflow/action-plan-verify.md +0 -485
- package/.claude/commands/workflow/brainstorm/api-designer.md +0 -587
- package/.claude/commands/workflow/brainstorm/data-architect.md +0 -220
- package/.claude/commands/workflow/brainstorm/product-manager.md +0 -200
- package/.claude/commands/workflow/brainstorm/product-owner.md +0 -200
- package/.claude/commands/workflow/brainstorm/scrum-master.md +0 -200
- package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +0 -200
- package/.claude/commands/workflow/brainstorm/system-architect.md +0 -389
- package/.claude/commands/workflow/brainstorm/ui-designer.md +0 -221
- package/.claude/commands/workflow/brainstorm/ux-expert.md +0 -221
- package/.claude/commands/workflow/debug.md +0 -331
- package/.claude/commands/workflow/develop-with-file.md +0 -1044
- package/.claude/skills/ccw-loop/README.md +0 -303
- package/.claude/skills/skill-generator/templates/script-bash.md +0 -277
- package/.claude/skills/skill-generator/templates/script-python.md +0 -198
- package/.codex/prompts/debug.md +0 -318
- package/ccw/src/core/routes/mcp-routes.ts.backup +0 -549
|
@@ -51,6 +51,92 @@ function countDiscoveries(projectPath: string): number {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Recursively count command files in a directory
|
|
56
|
+
*/
|
|
57
|
+
function countCommandsInDir(dirPath: string): { enabled: number; disabled: number } {
|
|
58
|
+
let enabled = 0;
|
|
59
|
+
let disabled = 0;
|
|
60
|
+
|
|
61
|
+
if (!existsSync(dirPath)) {
|
|
62
|
+
return { enabled, disabled };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const fullPath = join(dirPath, entry.name);
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
if (entry.name === '_disabled') {
|
|
71
|
+
// Count disabled commands recursively
|
|
72
|
+
disabled += countAllMdFiles(fullPath);
|
|
73
|
+
} else {
|
|
74
|
+
// Recursively count enabled commands
|
|
75
|
+
const subCounts = countCommandsInDir(fullPath);
|
|
76
|
+
enabled += subCounts.enabled;
|
|
77
|
+
disabled += subCounts.disabled;
|
|
78
|
+
}
|
|
79
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
80
|
+
enabled++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch { /* ignore */ }
|
|
84
|
+
|
|
85
|
+
return { enabled, disabled };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Count all .md files recursively (for disabled directory)
|
|
90
|
+
*/
|
|
91
|
+
function countAllMdFiles(dirPath: string): number {
|
|
92
|
+
let count = 0;
|
|
93
|
+
if (!existsSync(dirPath)) return count;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
const fullPath = join(dirPath, entry.name);
|
|
99
|
+
if (entry.isDirectory()) {
|
|
100
|
+
count += countAllMdFiles(fullPath);
|
|
101
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
102
|
+
count++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch { /* ignore */ }
|
|
106
|
+
|
|
107
|
+
return count;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Count commands from project and user directories
|
|
112
|
+
*/
|
|
113
|
+
function countCommands(projectPath: string): {
|
|
114
|
+
project: { enabled: number; disabled: number };
|
|
115
|
+
user: { enabled: number; disabled: number };
|
|
116
|
+
total: number;
|
|
117
|
+
enabled: number;
|
|
118
|
+
disabled: number;
|
|
119
|
+
} {
|
|
120
|
+
// Project commands
|
|
121
|
+
const projectDir = join(projectPath, '.claude', 'commands');
|
|
122
|
+
const projectCounts = countCommandsInDir(projectDir);
|
|
123
|
+
|
|
124
|
+
// User commands
|
|
125
|
+
const userDir = join(homedir(), '.claude', 'commands');
|
|
126
|
+
const userCounts = countCommandsInDir(userDir);
|
|
127
|
+
|
|
128
|
+
const totalEnabled = projectCounts.enabled + userCounts.enabled;
|
|
129
|
+
const totalDisabled = projectCounts.disabled + userCounts.disabled;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
project: projectCounts,
|
|
133
|
+
user: userCounts,
|
|
134
|
+
total: totalEnabled + totalDisabled,
|
|
135
|
+
enabled: totalEnabled,
|
|
136
|
+
disabled: totalDisabled
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
54
140
|
/**
|
|
55
141
|
* Count skills from project and user directories
|
|
56
142
|
*/
|
|
@@ -197,10 +283,11 @@ export async function handleNavStatusRoutes(ctx: RouteContext): Promise<boolean>
|
|
|
197
283
|
const projectPath = url.searchParams.get('path') || initialPath;
|
|
198
284
|
|
|
199
285
|
// Execute all counts (synchronous file reads wrapped in Promise.resolve for consistency)
|
|
200
|
-
const [issues, discoveries, skills, rules, claude, hooks] = await Promise.all([
|
|
286
|
+
const [issues, discoveries, skills, commands, rules, claude, hooks] = await Promise.all([
|
|
201
287
|
Promise.resolve(countIssues(projectPath)),
|
|
202
288
|
Promise.resolve(countDiscoveries(projectPath)),
|
|
203
289
|
Promise.resolve(countSkills(projectPath)),
|
|
290
|
+
Promise.resolve(countCommands(projectPath)),
|
|
204
291
|
Promise.resolve(countRules(projectPath)),
|
|
205
292
|
Promise.resolve(countClaudeFiles(projectPath)),
|
|
206
293
|
Promise.resolve(countHooks(projectPath))
|
|
@@ -210,6 +297,13 @@ export async function handleNavStatusRoutes(ctx: RouteContext): Promise<boolean>
|
|
|
210
297
|
issues: { count: issues },
|
|
211
298
|
discoveries: { count: discoveries },
|
|
212
299
|
skills: { count: skills.total, project: skills.project, user: skills.user },
|
|
300
|
+
commands: {
|
|
301
|
+
count: commands.total,
|
|
302
|
+
enabled: commands.enabled,
|
|
303
|
+
disabled: commands.disabled,
|
|
304
|
+
project: commands.project,
|
|
305
|
+
user: commands.user
|
|
306
|
+
},
|
|
213
307
|
rules: { count: rules.total, project: rules.project, user: rules.user },
|
|
214
308
|
claude: { count: claude },
|
|
215
309
|
hooks: { count: hooks.total, global: hooks.global, project: hooks.project },
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Reference Routes Module
|
|
3
|
+
* Handles read-only provider model reference API endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { RouteContext } from './types.js';
|
|
7
|
+
import {
|
|
8
|
+
PROVIDER_MODELS,
|
|
9
|
+
getAllProviders,
|
|
10
|
+
getProviderModels
|
|
11
|
+
} from '../../config/provider-models.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Handle Provider Reference routes
|
|
15
|
+
* @returns true if route was handled, false otherwise
|
|
16
|
+
*/
|
|
17
|
+
export async function handleProviderRoutes(ctx: RouteContext): Promise<boolean> {
|
|
18
|
+
const { pathname, req, res } = ctx;
|
|
19
|
+
|
|
20
|
+
// ========== GET ALL PROVIDERS ==========
|
|
21
|
+
// GET /api/providers
|
|
22
|
+
if (pathname === '/api/providers' && req.method === 'GET') {
|
|
23
|
+
try {
|
|
24
|
+
const providers = getAllProviders().map(id => ({
|
|
25
|
+
id,
|
|
26
|
+
name: PROVIDER_MODELS[id].name,
|
|
27
|
+
modelCount: PROVIDER_MODELS[id].models.length
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
31
|
+
res.end(JSON.stringify({ success: true, providers }));
|
|
32
|
+
} catch (err) {
|
|
33
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
34
|
+
res.end(JSON.stringify({
|
|
35
|
+
success: false,
|
|
36
|
+
error: (err as Error).message
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ========== GET MODELS FOR PROVIDER ==========
|
|
43
|
+
// GET /api/providers/:provider/models
|
|
44
|
+
const providerMatch = pathname.match(/^\/api\/providers\/([^\/]+)\/models$/);
|
|
45
|
+
if (providerMatch && req.method === 'GET') {
|
|
46
|
+
const provider = decodeURIComponent(providerMatch[1]);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const models = getProviderModels(provider);
|
|
50
|
+
|
|
51
|
+
if (models.length === 0) {
|
|
52
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
53
|
+
res.end(JSON.stringify({
|
|
54
|
+
success: false,
|
|
55
|
+
error: `Provider not found: ${provider}`
|
|
56
|
+
}));
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
61
|
+
res.end(JSON.stringify({
|
|
62
|
+
success: true,
|
|
63
|
+
provider,
|
|
64
|
+
providerName: PROVIDER_MODELS[provider].name,
|
|
65
|
+
models
|
|
66
|
+
}));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
69
|
+
res.end(JSON.stringify({
|
|
70
|
+
success: false,
|
|
71
|
+
error: (err as Error).message
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
@@ -2,51 +2,24 @@
|
|
|
2
2
|
* Skills Routes Module
|
|
3
3
|
* Handles all Skills-related API endpoints
|
|
4
4
|
*/
|
|
5
|
-
import { readFileSync, existsSync, readdirSync, statSync, unlinkSync, promises as fsPromises } from 'fs';
|
|
5
|
+
import { readFileSync, existsSync, readdirSync, statSync, unlinkSync, renameSync, promises as fsPromises } from 'fs';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { homedir } from 'os';
|
|
8
8
|
import { executeCliTool } from '../../tools/cli-executor.js';
|
|
9
9
|
import { SmartContentFormatter } from '../../tools/cli-output-converter.js';
|
|
10
10
|
import { validatePath as validateAllowedPath } from '../../utils/path-validator.js';
|
|
11
11
|
import type { RouteContext } from './types.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
interface SkillSummary {
|
|
24
|
-
name: string;
|
|
25
|
-
folderName: string;
|
|
26
|
-
description: string;
|
|
27
|
-
version: string | null;
|
|
28
|
-
allowedTools: string[];
|
|
29
|
-
location: SkillLocation;
|
|
30
|
-
path: string;
|
|
31
|
-
supportingFiles: string[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface SkillsConfig {
|
|
35
|
-
projectSkills: SkillSummary[];
|
|
36
|
-
userSkills: SkillSummary[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface SkillInfo {
|
|
40
|
-
name: string;
|
|
41
|
-
description: string;
|
|
42
|
-
version: string | null;
|
|
43
|
-
allowedTools: string[];
|
|
44
|
-
supportingFiles: string[];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
type SkillFolderValidation =
|
|
48
|
-
| { valid: true; errors: string[]; skillInfo: SkillInfo }
|
|
49
|
-
| { valid: false; errors: string[]; skillInfo: null };
|
|
12
|
+
import type {
|
|
13
|
+
SkillLocation,
|
|
14
|
+
ParsedSkillFrontmatter,
|
|
15
|
+
SkillSummary,
|
|
16
|
+
SkillsConfig,
|
|
17
|
+
SkillInfo,
|
|
18
|
+
SkillFolderValidation,
|
|
19
|
+
DisabledSkillSummary,
|
|
20
|
+
ExtendedSkillsConfig,
|
|
21
|
+
SkillOperationResult
|
|
22
|
+
} from '../../types/skill-types.js';
|
|
50
23
|
|
|
51
24
|
type GenerationType = 'description' | 'template';
|
|
52
25
|
|
|
@@ -65,6 +38,178 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
65
38
|
|
|
66
39
|
// ========== Skills Helper Functions ==========
|
|
67
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Disable a skill by renaming SKILL.md to SKILL.md.disabled
|
|
43
|
+
*/
|
|
44
|
+
async function disableSkill(
|
|
45
|
+
skillName: string,
|
|
46
|
+
location: SkillLocation,
|
|
47
|
+
projectPath: string,
|
|
48
|
+
initialPath: string,
|
|
49
|
+
reason?: string // Kept for API compatibility but no longer used
|
|
50
|
+
): Promise<SkillOperationResult> {
|
|
51
|
+
try {
|
|
52
|
+
// Validate skill name
|
|
53
|
+
if (skillName.includes('/') || skillName.includes('\\') || skillName.includes('..')) {
|
|
54
|
+
return { success: false, message: 'Invalid skill name', status: 400 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Get skill directory
|
|
58
|
+
let skillsDir: string;
|
|
59
|
+
if (location === 'project') {
|
|
60
|
+
try {
|
|
61
|
+
const validatedProjectPath = await validateAllowedPath(projectPath, { mustExist: true, allowedDirectories: [initialPath] });
|
|
62
|
+
skillsDir = join(validatedProjectPath, '.claude', 'skills');
|
|
63
|
+
} catch (err) {
|
|
64
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
65
|
+
return { success: false, message: message.includes('Access denied') ? 'Access denied' : 'Invalid path', status: 403 };
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
skillsDir = join(homedir(), '.claude', 'skills');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const skillDir = join(skillsDir, skillName);
|
|
72
|
+
if (!existsSync(skillDir)) {
|
|
73
|
+
return { success: false, message: 'Skill not found', status: 404 };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
77
|
+
if (!existsSync(skillMdPath)) {
|
|
78
|
+
return { success: false, message: 'SKILL.md not found', status: 404 };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const disabledPath = join(skillDir, 'SKILL.md.disabled');
|
|
82
|
+
if (existsSync(disabledPath)) {
|
|
83
|
+
return { success: false, message: 'Skill already disabled', status: 409 };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Rename: SKILL.md → SKILL.md.disabled
|
|
87
|
+
renameSync(skillMdPath, disabledPath);
|
|
88
|
+
|
|
89
|
+
return { success: true, message: 'Skill disabled', skillName, location };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return { success: false, message: (error as Error).message, status: 500 };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Enable a skill by renaming SKILL.md.disabled back to SKILL.md
|
|
97
|
+
*/
|
|
98
|
+
async function enableSkill(
|
|
99
|
+
skillName: string,
|
|
100
|
+
location: SkillLocation,
|
|
101
|
+
projectPath: string,
|
|
102
|
+
initialPath: string
|
|
103
|
+
): Promise<SkillOperationResult> {
|
|
104
|
+
try {
|
|
105
|
+
// Validate skill name
|
|
106
|
+
if (skillName.includes('/') || skillName.includes('\\') || skillName.includes('..')) {
|
|
107
|
+
return { success: false, message: 'Invalid skill name', status: 400 };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Get skill directory
|
|
111
|
+
let skillsDir: string;
|
|
112
|
+
if (location === 'project') {
|
|
113
|
+
try {
|
|
114
|
+
const validatedProjectPath = await validateAllowedPath(projectPath, { mustExist: true, allowedDirectories: [initialPath] });
|
|
115
|
+
skillsDir = join(validatedProjectPath, '.claude', 'skills');
|
|
116
|
+
} catch (err) {
|
|
117
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
118
|
+
return { success: false, message: message.includes('Access denied') ? 'Access denied' : 'Invalid path', status: 403 };
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
skillsDir = join(homedir(), '.claude', 'skills');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const skillDir = join(skillsDir, skillName);
|
|
125
|
+
if (!existsSync(skillDir)) {
|
|
126
|
+
return { success: false, message: 'Skill not found', status: 404 };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const disabledPath = join(skillDir, 'SKILL.md.disabled');
|
|
130
|
+
if (!existsSync(disabledPath)) {
|
|
131
|
+
return { success: false, message: 'Disabled skill not found', status: 404 };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const skillMdPath = join(skillDir, 'SKILL.md');
|
|
135
|
+
if (existsSync(skillMdPath)) {
|
|
136
|
+
return { success: false, message: 'Skill already enabled', status: 409 };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Rename: SKILL.md.disabled → SKILL.md
|
|
140
|
+
renameSync(disabledPath, skillMdPath);
|
|
141
|
+
|
|
142
|
+
return { success: true, message: 'Skill enabled', skillName, location };
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return { success: false, message: (error as Error).message, status: 500 };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get list of disabled skills by checking for SKILL.md.disabled files
|
|
150
|
+
*/
|
|
151
|
+
function getDisabledSkillsList(location: SkillLocation, projectPath: string): DisabledSkillSummary[] {
|
|
152
|
+
const result: DisabledSkillSummary[] = [];
|
|
153
|
+
|
|
154
|
+
// Get skills directory (not a separate disabled directory)
|
|
155
|
+
let skillsDir: string;
|
|
156
|
+
if (location === 'project') {
|
|
157
|
+
skillsDir = join(projectPath, '.claude', 'skills');
|
|
158
|
+
} else {
|
|
159
|
+
skillsDir = join(homedir(), '.claude', 'skills');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!existsSync(skillsDir)) {
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const skills = readdirSync(skillsDir, { withFileTypes: true });
|
|
168
|
+
for (const skill of skills) {
|
|
169
|
+
if (skill.isDirectory()) {
|
|
170
|
+
const disabledPath = join(skillsDir, skill.name, 'SKILL.md.disabled');
|
|
171
|
+
if (existsSync(disabledPath)) {
|
|
172
|
+
const content = readFileSync(disabledPath, 'utf8');
|
|
173
|
+
const parsed = parseSkillFrontmatter(content);
|
|
174
|
+
const skillDir = join(skillsDir, skill.name);
|
|
175
|
+
const supportingFiles = getSupportingFiles(skillDir);
|
|
176
|
+
|
|
177
|
+
result.push({
|
|
178
|
+
name: parsed.name || skill.name,
|
|
179
|
+
folderName: skill.name,
|
|
180
|
+
description: parsed.description,
|
|
181
|
+
version: parsed.version,
|
|
182
|
+
allowedTools: parsed.allowedTools,
|
|
183
|
+
location,
|
|
184
|
+
path: skillDir,
|
|
185
|
+
supportingFiles,
|
|
186
|
+
disabledAt: new Date().toISOString(), // Cannot get exact time without config file
|
|
187
|
+
reason: undefined // No longer stored
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(`[Skills] Failed to read disabled skills: ${error}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get extended skills config including disabled skills
|
|
201
|
+
*/
|
|
202
|
+
function getExtendedSkillsConfig(projectPath: string): ExtendedSkillsConfig {
|
|
203
|
+
const baseConfig = getSkillsConfig(projectPath);
|
|
204
|
+
return {
|
|
205
|
+
...baseConfig,
|
|
206
|
+
disabledProjectSkills: getDisabledSkillsList('project', projectPath),
|
|
207
|
+
disabledUserSkills: getDisabledSkillsList('user', projectPath)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ========== Active Skills Helper Functions ==========
|
|
212
|
+
|
|
68
213
|
/**
|
|
69
214
|
* Parse skill frontmatter (YAML header)
|
|
70
215
|
* @param {string} content - Skill file content
|
|
@@ -128,7 +273,8 @@ function getSupportingFiles(skillDir: string): string[] {
|
|
|
128
273
|
try {
|
|
129
274
|
const entries = readdirSync(skillDir, { withFileTypes: true });
|
|
130
275
|
for (const entry of entries) {
|
|
131
|
-
|
|
276
|
+
// Exclude SKILL.md and SKILL.md.disabled from supporting files
|
|
277
|
+
if (entry.name !== 'SKILL.md' && entry.name !== 'SKILL.md.disabled') {
|
|
132
278
|
if (entry.isFile()) {
|
|
133
279
|
files.push(entry.name);
|
|
134
280
|
} else if (entry.isDirectory()) {
|
|
@@ -660,15 +806,23 @@ Create a new Claude Code skill with the following specifications:
|
|
|
660
806
|
export async function handleSkillsRoutes(ctx: RouteContext): Promise<boolean> {
|
|
661
807
|
const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients } = ctx;
|
|
662
808
|
|
|
663
|
-
// API: Get all skills (project and user)
|
|
664
|
-
if (pathname === '/api/skills') {
|
|
809
|
+
// API: Get all skills (project and user) - with optional extended format
|
|
810
|
+
if (pathname === '/api/skills' && req.method === 'GET') {
|
|
665
811
|
const projectPathParam = url.searchParams.get('path') || initialPath;
|
|
812
|
+
const includeDisabled = url.searchParams.get('includeDisabled') === 'true';
|
|
666
813
|
|
|
667
814
|
try {
|
|
668
815
|
const validatedProjectPath = await validateAllowedPath(projectPathParam, { mustExist: true, allowedDirectories: [initialPath] });
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
816
|
+
|
|
817
|
+
if (includeDisabled) {
|
|
818
|
+
const extendedData = getExtendedSkillsConfig(validatedProjectPath);
|
|
819
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
820
|
+
res.end(JSON.stringify(extendedData));
|
|
821
|
+
} else {
|
|
822
|
+
const skillsData = getSkillsConfig(validatedProjectPath);
|
|
823
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
824
|
+
res.end(JSON.stringify(skillsData));
|
|
825
|
+
}
|
|
672
826
|
} catch (err) {
|
|
673
827
|
const message = err instanceof Error ? err.message : String(err);
|
|
674
828
|
const status = message.includes('Access denied') ? 403 : 400;
|
|
@@ -679,6 +833,73 @@ export async function handleSkillsRoutes(ctx: RouteContext): Promise<boolean> {
|
|
|
679
833
|
return true;
|
|
680
834
|
}
|
|
681
835
|
|
|
836
|
+
// API: Get disabled skills list
|
|
837
|
+
if (pathname === '/api/skills/disabled' && req.method === 'GET') {
|
|
838
|
+
const projectPathParam = url.searchParams.get('path') || initialPath;
|
|
839
|
+
|
|
840
|
+
try {
|
|
841
|
+
const validatedProjectPath = await validateAllowedPath(projectPathParam, { mustExist: true, allowedDirectories: [initialPath] });
|
|
842
|
+
const disabledProjectSkills = getDisabledSkillsList('project', validatedProjectPath);
|
|
843
|
+
const disabledUserSkills = getDisabledSkillsList('user', validatedProjectPath);
|
|
844
|
+
|
|
845
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
846
|
+
res.end(JSON.stringify({ disabledProjectSkills, disabledUserSkills }));
|
|
847
|
+
} catch (err) {
|
|
848
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
849
|
+
const status = message.includes('Access denied') ? 403 : 400;
|
|
850
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
851
|
+
res.end(JSON.stringify({ error: status === 403 ? 'Access denied' : 'Invalid path', disabledProjectSkills: [], disabledUserSkills: [] }));
|
|
852
|
+
}
|
|
853
|
+
return true;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// API: Disable a skill
|
|
857
|
+
if (pathname.match(/^\/api\/skills\/[^/]+\/disable$/) && req.method === 'POST') {
|
|
858
|
+
const pathParts = pathname.split('/');
|
|
859
|
+
const skillName = decodeURIComponent(pathParts[3]);
|
|
860
|
+
|
|
861
|
+
handlePostRequest(req, res, async (body) => {
|
|
862
|
+
if (!isRecord(body)) {
|
|
863
|
+
return { error: 'Invalid request body', status: 400 };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const locationValue = body.location;
|
|
867
|
+
const projectPathParam = typeof body.projectPath === 'string' ? body.projectPath : undefined;
|
|
868
|
+
const reason = typeof body.reason === 'string' ? body.reason : undefined;
|
|
869
|
+
|
|
870
|
+
if (locationValue !== 'project' && locationValue !== 'user') {
|
|
871
|
+
return { error: 'Location is required (project or user)' };
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const projectPath = projectPathParam || initialPath;
|
|
875
|
+
return disableSkill(skillName, locationValue, projectPath, initialPath, reason);
|
|
876
|
+
});
|
|
877
|
+
return true;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// API: Enable a skill
|
|
881
|
+
if (pathname.match(/^\/api\/skills\/[^/]+\/enable$/) && req.method === 'POST') {
|
|
882
|
+
const pathParts = pathname.split('/');
|
|
883
|
+
const skillName = decodeURIComponent(pathParts[3]);
|
|
884
|
+
|
|
885
|
+
handlePostRequest(req, res, async (body) => {
|
|
886
|
+
if (!isRecord(body)) {
|
|
887
|
+
return { error: 'Invalid request body', status: 400 };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const locationValue = body.location;
|
|
891
|
+
const projectPathParam = typeof body.projectPath === 'string' ? body.projectPath : undefined;
|
|
892
|
+
|
|
893
|
+
if (locationValue !== 'project' && locationValue !== 'user') {
|
|
894
|
+
return { error: 'Location is required (project or user)' };
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const projectPath = projectPathParam || initialPath;
|
|
898
|
+
return enableSkill(skillName, locationValue, projectPath, initialPath);
|
|
899
|
+
});
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
902
|
+
|
|
682
903
|
// API: List skill directory contents
|
|
683
904
|
if (pathname.match(/^\/api\/skills\/[^/]+\/dir$/) && req.method === 'GET') {
|
|
684
905
|
const pathParts = pathname.split('/');
|