claude-code-templates 1.2.1 โ 1.3.1
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/package.json +10 -8
- package/src/command-scanner.js +1 -1
- package/src/file-operations.js +44 -3
- package/src/hook-scanner.js +106 -2
- package/src/index.js +23 -3
- package/src/prompts.js +68 -2
- package/src/templates.js +31 -8
- package/scripts/sync-templates.js +0 -182
- package/templates/common/.claude/commands/git-workflow.md +0 -239
- package/templates/common/.claude/commands/project-setup.md +0 -316
- package/templates/common/CLAUDE.md +0 -109
- package/templates/common/README.md +0 -96
- package/templates/go/README.md +0 -25
- package/templates/javascript-typescript/.claude/commands/api-endpoint.md +0 -51
- package/templates/javascript-typescript/.claude/commands/debug.md +0 -52
- package/templates/javascript-typescript/.claude/commands/lint.md +0 -48
- package/templates/javascript-typescript/.claude/commands/npm-scripts.md +0 -48
- package/templates/javascript-typescript/.claude/commands/refactor.md +0 -55
- package/templates/javascript-typescript/.claude/commands/test.md +0 -61
- package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +0 -51
- package/templates/javascript-typescript/.claude/settings.json +0 -142
- package/templates/javascript-typescript/.mcp.json +0 -13
- package/templates/javascript-typescript/CLAUDE.md +0 -185
- package/templates/javascript-typescript/README.md +0 -259
- package/templates/javascript-typescript/examples/angular-app/.claude/commands/components.md +0 -63
- package/templates/javascript-typescript/examples/angular-app/.claude/commands/services.md +0 -62
- package/templates/javascript-typescript/examples/node-api/.claude/commands/api-endpoint.md +0 -46
- package/templates/javascript-typescript/examples/node-api/.claude/commands/database.md +0 -56
- package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -61
- package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -57
- package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -102
- package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -29
- package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -44
- package/templates/javascript-typescript/examples/react-app/.claude/commands/state-management.md +0 -45
- package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -81
- package/templates/javascript-typescript/examples/vue-app/.claude/commands/components.md +0 -46
- package/templates/javascript-typescript/examples/vue-app/.claude/commands/composables.md +0 -51
- package/templates/python/.claude/commands/django-model.md +0 -124
- package/templates/python/.claude/commands/flask-route.md +0 -217
- package/templates/python/.claude/commands/lint.md +0 -111
- package/templates/python/.claude/commands/test.md +0 -73
- package/templates/python/CLAUDE.md +0 -276
- package/templates/rust/README.md +0 -26
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-templates",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI tool to setup Claude Code configurations with
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "CLI tool to setup Claude Code configurations with framework-specific commands and automation hooks for JavaScript/TypeScript and Python projects",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-claude-config": "bin/create-claude-config.js",
|
|
@@ -20,10 +20,7 @@
|
|
|
20
20
|
"dev:link": "npm link",
|
|
21
21
|
"dev:unlink": "npm unlink -g claude-code-templates",
|
|
22
22
|
"pretest": "npm run dev:link",
|
|
23
|
-
"
|
|
24
|
-
"presync": "echo \"๐ Starting template synchronization...\"",
|
|
25
|
-
"postsync": "echo \"โ
Synchronization completed. Ready to publish!\"",
|
|
26
|
-
"prepublishOnly": "npm run sync && npm run test"
|
|
23
|
+
"prepublishOnly": "npm run test"
|
|
27
24
|
},
|
|
28
25
|
"keywords": [
|
|
29
26
|
"claude",
|
|
@@ -37,7 +34,14 @@
|
|
|
37
34
|
"automation",
|
|
38
35
|
"javascript",
|
|
39
36
|
"typescript",
|
|
37
|
+
"react",
|
|
38
|
+
"vue",
|
|
39
|
+
"angular",
|
|
40
|
+
"nodejs",
|
|
40
41
|
"python",
|
|
42
|
+
"django",
|
|
43
|
+
"flask",
|
|
44
|
+
"fastapi",
|
|
41
45
|
"rust",
|
|
42
46
|
"go"
|
|
43
47
|
],
|
|
@@ -65,8 +69,6 @@
|
|
|
65
69
|
"files": [
|
|
66
70
|
"bin/",
|
|
67
71
|
"src/",
|
|
68
|
-
"scripts/",
|
|
69
|
-
"templates/",
|
|
70
72
|
"README.md"
|
|
71
73
|
]
|
|
72
74
|
}
|
package/src/command-scanner.js
CHANGED
|
@@ -7,7 +7,7 @@ const path = require('path');
|
|
|
7
7
|
* @returns {Array} Array of available commands with metadata
|
|
8
8
|
*/
|
|
9
9
|
function getAvailableCommands(language) {
|
|
10
|
-
const templatesDir = path.join(__dirname, '
|
|
10
|
+
const templatesDir = path.join(__dirname, '../../');
|
|
11
11
|
const languageDir = path.join(templatesDir, language);
|
|
12
12
|
|
|
13
13
|
// Check if language directory exists
|
package/src/file-operations.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const { getHooksForLanguage, filterHooksBySelection } = require('./hook-scanner');
|
|
4
|
+
const { getHooksForLanguage, filterHooksBySelection, getMCPsForLanguage, filterMCPsBySelection } = require('./hook-scanner');
|
|
5
5
|
|
|
6
6
|
async function copyTemplateFiles(templateConfig, targetDir) {
|
|
7
|
-
const templateDir = path.join(__dirname, '
|
|
7
|
+
const templateDir = path.join(__dirname, '../../');
|
|
8
8
|
|
|
9
9
|
// Check if CLAUDE.md already exists
|
|
10
10
|
const claudeFile = path.join(targetDir, 'CLAUDE.md');
|
|
@@ -79,6 +79,10 @@ async function copyTemplateFiles(templateConfig, targetDir) {
|
|
|
79
79
|
// Handle settings.json with hook filtering
|
|
80
80
|
await processSettingsFile(sourcePath, destPath, templateConfig);
|
|
81
81
|
console.log(chalk.green(`โ Copied ${file.source} โ ${file.destination} (with selected hooks)`));
|
|
82
|
+
} else if (file.source.includes('.mcp.json') && templateConfig.selectedMCPs) {
|
|
83
|
+
// Handle .mcp.json with MCP filtering
|
|
84
|
+
await processMCPFile(sourcePath, destPath, templateConfig);
|
|
85
|
+
console.log(chalk.green(`โ Copied ${file.source} โ ${file.destination} (with selected MCPs)`));
|
|
82
86
|
} else {
|
|
83
87
|
// Copy regular files (CLAUDE.md, settings.json, etc.)
|
|
84
88
|
await fs.copy(sourcePath, destPath, {
|
|
@@ -121,6 +125,11 @@ async function copyTemplateFiles(templateConfig, targetDir) {
|
|
|
121
125
|
if (templateConfig.selectedHooks && templateConfig.selectedHooks.length > 0) {
|
|
122
126
|
console.log(chalk.magenta(`๐ง Installed ${templateConfig.selectedHooks.length} automation hooks`));
|
|
123
127
|
}
|
|
128
|
+
|
|
129
|
+
// Report MCP selection
|
|
130
|
+
if (templateConfig.selectedMCPs && templateConfig.selectedMCPs.length > 0) {
|
|
131
|
+
console.log(chalk.blue(`๐ง Installed ${templateConfig.selectedMCPs.length} MCP`));
|
|
132
|
+
}
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
async function processSettingsFile(sourcePath, destPath, templateConfig) {
|
|
@@ -155,6 +164,37 @@ async function processSettingsFile(sourcePath, destPath, templateConfig) {
|
|
|
155
164
|
}
|
|
156
165
|
}
|
|
157
166
|
|
|
167
|
+
async function processMCPFile(sourcePath, destPath, templateConfig) {
|
|
168
|
+
try {
|
|
169
|
+
// Read the original MCP file
|
|
170
|
+
const originalMCPData = JSON.parse(await fs.readFile(sourcePath, 'utf8'));
|
|
171
|
+
|
|
172
|
+
// If MCPs are selected, filter them
|
|
173
|
+
if (templateConfig.selectedMCPs && templateConfig.selectedMCPs.length > 0) {
|
|
174
|
+
const availableMCPs = getMCPsForLanguage(templateConfig.language);
|
|
175
|
+
const filteredMCPData = filterMCPsBySelection(
|
|
176
|
+
originalMCPData,
|
|
177
|
+
templateConfig.selectedMCPs,
|
|
178
|
+
availableMCPs
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Write the filtered MCP data
|
|
182
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
183
|
+
await fs.writeFile(destPath, JSON.stringify(filteredMCPData, null, 2));
|
|
184
|
+
} else {
|
|
185
|
+
// No MCPs selected, create empty MCP file
|
|
186
|
+
const emptyMCPData = { mcpServers: {} };
|
|
187
|
+
|
|
188
|
+
await fs.ensureDir(path.dirname(destPath));
|
|
189
|
+
await fs.writeFile(destPath, JSON.stringify(emptyMCPData, null, 2));
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error(chalk.red(`Failed to process MCP file: ${error.message}`));
|
|
193
|
+
// Fallback to copying original file
|
|
194
|
+
await fs.copy(sourcePath, destPath);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
158
198
|
async function ensureDirectoryExists(dirPath) {
|
|
159
199
|
try {
|
|
160
200
|
await fs.ensureDir(dirPath);
|
|
@@ -180,5 +220,6 @@ module.exports = {
|
|
|
180
220
|
copyTemplateFiles,
|
|
181
221
|
ensureDirectoryExists,
|
|
182
222
|
checkWritePermissions,
|
|
183
|
-
processSettingsFile
|
|
223
|
+
processSettingsFile,
|
|
224
|
+
processMCPFile
|
|
184
225
|
};
|
package/src/hook-scanner.js
CHANGED
|
@@ -267,7 +267,7 @@ function getHookDescription(hook, matcher, type) {
|
|
|
267
267
|
* @returns {Array} Array of available hooks for the language
|
|
268
268
|
*/
|
|
269
269
|
function getHooksForLanguage(language) {
|
|
270
|
-
const templateDir = path.join(__dirname, '
|
|
270
|
+
const templateDir = path.join(__dirname, '../../', language);
|
|
271
271
|
const settingsPath = path.join(templateDir, '.claude', 'settings.json');
|
|
272
272
|
|
|
273
273
|
return getHooksFromSettings(settingsPath);
|
|
@@ -336,9 +336,113 @@ function filterHooksBySelection(originalSettings, selectedHookIds, availableHook
|
|
|
336
336
|
return filteredSettings;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Extracts and describes MCPs from a .mcp.json file
|
|
341
|
+
* @param {string} mcpPath - Path to the .mcp.json file
|
|
342
|
+
* @returns {Array} Array of MCP descriptions
|
|
343
|
+
*/
|
|
344
|
+
function getMCPsFromFile(mcpPath) {
|
|
345
|
+
if (!fs.existsSync(mcpPath)) {
|
|
346
|
+
return [];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const mcpData = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
351
|
+
const mcps = [];
|
|
352
|
+
|
|
353
|
+
if (mcpData.mcpServers) {
|
|
354
|
+
Object.keys(mcpData.mcpServers).forEach((serverId) => {
|
|
355
|
+
const server = mcpData.mcpServers[serverId];
|
|
356
|
+
mcps.push({
|
|
357
|
+
id: serverId,
|
|
358
|
+
name: server.name || serverId,
|
|
359
|
+
description: server.description || 'No description available',
|
|
360
|
+
command: server.command,
|
|
361
|
+
args: server.args || [],
|
|
362
|
+
env: server.env || {},
|
|
363
|
+
originalServer: server,
|
|
364
|
+
checked: getDefaultMCPSelection(serverId) // Default selection logic
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return mcps;
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error(`Error parsing MCP file ${mcpPath}:`, error.message);
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Determines default selection for MCP servers
|
|
378
|
+
* @param {string} serverId - The MCP server ID
|
|
379
|
+
* @returns {boolean} Whether the MCP should be selected by default
|
|
380
|
+
*/
|
|
381
|
+
function getDefaultMCPSelection(serverId) {
|
|
382
|
+
// Default to checked for commonly useful MCPs
|
|
383
|
+
const defaultSelected = [
|
|
384
|
+
'filesystem',
|
|
385
|
+
'memory-bank',
|
|
386
|
+
'sequential-thinking',
|
|
387
|
+
'typescript-sdk',
|
|
388
|
+
'python-sdk',
|
|
389
|
+
'rust-sdk',
|
|
390
|
+
'go-sdk'
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
return defaultSelected.includes(serverId);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Gets MCPs for a specific language
|
|
398
|
+
* @param {string} language - The programming language
|
|
399
|
+
* @returns {Array} Array of available MCPs for the language
|
|
400
|
+
*/
|
|
401
|
+
function getMCPsForLanguage(language) {
|
|
402
|
+
const templateDir = path.join(__dirname, '../../', language);
|
|
403
|
+
const mcpPath = path.join(templateDir, '.mcp.json');
|
|
404
|
+
|
|
405
|
+
return getMCPsFromFile(mcpPath);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Filters MCPs based on user selection
|
|
410
|
+
* @param {Object} originalMCPData - Original MCP data object
|
|
411
|
+
* @param {Array} selectedMCPIds - Array of selected MCP IDs
|
|
412
|
+
* @param {Array} availableMCPs - Array of available MCPs
|
|
413
|
+
* @returns {Object} Filtered MCP data object
|
|
414
|
+
*/
|
|
415
|
+
function filterMCPsBySelection(originalMCPData, selectedMCPIds, availableMCPs) {
|
|
416
|
+
if (!originalMCPData.mcpServers) {
|
|
417
|
+
return originalMCPData;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const filteredMCPData = {
|
|
421
|
+
mcpServers: {}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Create a map of selected MCPs for quick lookup
|
|
425
|
+
const selectedMCPs = new Map();
|
|
426
|
+
availableMCPs.forEach(mcp => {
|
|
427
|
+
if (selectedMCPIds.includes(mcp.id)) {
|
|
428
|
+
selectedMCPs.set(mcp.id, mcp);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Add selected MCPs to filtered data
|
|
433
|
+
selectedMCPs.forEach((mcp, mcpId) => {
|
|
434
|
+
filteredMCPData.mcpServers[mcpId] = mcp.originalServer;
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
return filteredMCPData;
|
|
438
|
+
}
|
|
439
|
+
|
|
339
440
|
module.exports = {
|
|
340
441
|
getHooksFromSettings,
|
|
341
442
|
getHooksForLanguage,
|
|
342
443
|
filterHooksBySelection,
|
|
343
|
-
getHookDescription
|
|
444
|
+
getHookDescription,
|
|
445
|
+
getMCPsFromFile,
|
|
446
|
+
getMCPsForLanguage,
|
|
447
|
+
filterMCPsBySelection
|
|
344
448
|
};
|
package/src/index.js
CHANGED
|
@@ -4,10 +4,10 @@ const fs = require('fs-extra');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const ora = require('ora');
|
|
6
6
|
const { detectProject } = require('./utils');
|
|
7
|
-
const { getTemplateConfig } = require('./templates');
|
|
7
|
+
const { getTemplateConfig, TEMPLATES_CONFIG } = require('./templates');
|
|
8
8
|
const { createPrompts, interactivePrompts } = require('./prompts');
|
|
9
9
|
const { copyTemplateFiles } = require('./file-operations');
|
|
10
|
-
const { getHooksForLanguage } = require('./hook-scanner');
|
|
10
|
+
const { getHooksForLanguage, getMCPsForLanguage } = require('./hook-scanner');
|
|
11
11
|
|
|
12
12
|
async function createClaudeConfig(options = {}) {
|
|
13
13
|
const targetDir = options.directory || process.cwd();
|
|
@@ -24,14 +24,24 @@ async function createClaudeConfig(options = {}) {
|
|
|
24
24
|
if (options.yes) {
|
|
25
25
|
// Use defaults
|
|
26
26
|
const selectedLanguage = options.language || projectInfo.detectedLanguage || 'common';
|
|
27
|
+
|
|
28
|
+
// Check if selected language is coming soon
|
|
29
|
+
if (selectedLanguage && TEMPLATES_CONFIG[selectedLanguage] && TEMPLATES_CONFIG[selectedLanguage].comingSoon) {
|
|
30
|
+
console.log(chalk.red(`โ ${selectedLanguage} is not available yet. Coming soon!`));
|
|
31
|
+
console.log(chalk.yellow('Available languages: common, javascript-typescript, python'));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
27
34
|
const availableHooks = getHooksForLanguage(selectedLanguage);
|
|
28
35
|
const defaultHooks = availableHooks.filter(hook => hook.checked).map(hook => hook.id);
|
|
36
|
+
const availableMCPs = getMCPsForLanguage(selectedLanguage);
|
|
37
|
+
const defaultMCPs = availableMCPs.filter(mcp => mcp.checked).map(mcp => mcp.id);
|
|
29
38
|
|
|
30
39
|
config = {
|
|
31
40
|
language: selectedLanguage,
|
|
32
41
|
framework: options.framework || projectInfo.detectedFramework || 'none',
|
|
33
42
|
features: [],
|
|
34
|
-
hooks: defaultHooks
|
|
43
|
+
hooks: defaultHooks,
|
|
44
|
+
mcps: defaultMCPs
|
|
35
45
|
};
|
|
36
46
|
} else {
|
|
37
47
|
// Interactive prompts with back navigation
|
|
@@ -53,6 +63,12 @@ async function createClaudeConfig(options = {}) {
|
|
|
53
63
|
templateConfig.language = config.language; // Ensure language is available for hook filtering
|
|
54
64
|
}
|
|
55
65
|
|
|
66
|
+
// Add selected MCPs to template config
|
|
67
|
+
if (config.mcps) {
|
|
68
|
+
templateConfig.selectedMCPs = config.mcps;
|
|
69
|
+
templateConfig.language = config.language; // Ensure language is available for MCP filtering
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
if (options.dryRun) {
|
|
57
73
|
console.log(chalk.yellow('๐ Dry run - showing what would be copied:'));
|
|
58
74
|
templateConfig.files.forEach(file => {
|
|
@@ -89,6 +105,10 @@ async function createClaudeConfig(options = {}) {
|
|
|
89
105
|
if (config.hooks && config.hooks.length > 0) {
|
|
90
106
|
console.log(chalk.magenta(`๐ง ${config.hooks.length} automation hooks have been configured`));
|
|
91
107
|
}
|
|
108
|
+
|
|
109
|
+
if (config.mcps && config.mcps.length > 0) {
|
|
110
|
+
console.log(chalk.blue(`๐ง ${config.mcps.length} MCP servers have been configured`));
|
|
111
|
+
}
|
|
92
112
|
}
|
|
93
113
|
|
|
94
114
|
module.exports = createClaudeConfig;
|
package/src/prompts.js
CHANGED
|
@@ -13,7 +13,7 @@ class CustomCheckboxPrompt extends inquirer.prompt.prompts.checkbox {
|
|
|
13
13
|
inquirer.registerPrompt('checkbox', CustomCheckboxPrompt);
|
|
14
14
|
const { getAvailableLanguages, getFrameworksForLanguage } = require('./templates');
|
|
15
15
|
const { getCommandsForLanguageAndFramework } = require('./command-scanner');
|
|
16
|
-
const { getHooksForLanguage } = require('./hook-scanner');
|
|
16
|
+
const { getHooksForLanguage, getMCPsForLanguage } = require('./hook-scanner');
|
|
17
17
|
|
|
18
18
|
async function interactivePrompts(projectInfo, options = {}) {
|
|
19
19
|
const state = {
|
|
@@ -25,7 +25,7 @@ async function interactivePrompts(projectInfo, options = {}) {
|
|
|
25
25
|
// Build steps array based on options
|
|
26
26
|
if (!options.language) state.steps.push('language');
|
|
27
27
|
if (!options.framework) state.steps.push('framework');
|
|
28
|
-
state.steps.push('commands', 'hooks', 'confirm');
|
|
28
|
+
state.steps.push('commands', 'hooks', 'mcps', 'confirm');
|
|
29
29
|
|
|
30
30
|
while (state.currentStep < state.steps.length) {
|
|
31
31
|
const stepName = state.steps[state.currentStep];
|
|
@@ -170,11 +170,38 @@ function getStepConfig(stepName, currentAnswers, projectInfo, options) {
|
|
|
170
170
|
pageSize: 15
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
+
case 'mcps':
|
|
174
|
+
const mcpLanguage = currentAnswers.language || options.language;
|
|
175
|
+
|
|
176
|
+
if (!mcpLanguage) {
|
|
177
|
+
return null; // Skip if no language selected
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const availableMCPs = getMCPsForLanguage(mcpLanguage);
|
|
181
|
+
|
|
182
|
+
if (availableMCPs.length === 0) {
|
|
183
|
+
return null; // Skip if no MCPs available
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
type: 'checkbox',
|
|
188
|
+
name: 'mcps',
|
|
189
|
+
message: 'Select MCP servers to include (use space to select):',
|
|
190
|
+
choices: availableMCPs.map(mcp => ({
|
|
191
|
+
value: mcp.id,
|
|
192
|
+
name: `${mcp.name} - ${mcp.description}`,
|
|
193
|
+
checked: mcp.checked
|
|
194
|
+
})),
|
|
195
|
+
prefix: chalk.blue('๐ง'),
|
|
196
|
+
pageSize: 15
|
|
197
|
+
};
|
|
198
|
+
|
|
173
199
|
case 'confirm':
|
|
174
200
|
const confirmLanguage = currentAnswers.language || options.language || 'common';
|
|
175
201
|
const confirmFramework = currentAnswers.framework || options.framework || 'none';
|
|
176
202
|
const commandCount = currentAnswers.commands ? currentAnswers.commands.length : 0;
|
|
177
203
|
const hookCount = currentAnswers.hooks ? currentAnswers.hooks.length : 0;
|
|
204
|
+
const mcpCount = currentAnswers.mcps ? currentAnswers.mcps.length : 0;
|
|
178
205
|
|
|
179
206
|
let message = `Setup Claude Code for ${chalk.cyan(confirmLanguage)}`;
|
|
180
207
|
if (confirmFramework !== 'none') {
|
|
@@ -186,6 +213,9 @@ function getStepConfig(stepName, currentAnswers, projectInfo, options) {
|
|
|
186
213
|
if (hookCount > 0) {
|
|
187
214
|
message += ` (${chalk.magenta(hookCount)} hooks)`;
|
|
188
215
|
}
|
|
216
|
+
if (mcpCount > 0) {
|
|
217
|
+
message += ` (${chalk.blue(mcpCount)} MCP)`;
|
|
218
|
+
}
|
|
189
219
|
message += '?';
|
|
190
220
|
|
|
191
221
|
return {
|
|
@@ -312,6 +342,38 @@ function createPrompts(projectInfo, options = {}) {
|
|
|
312
342
|
return availableHooks.length > 0;
|
|
313
343
|
}
|
|
314
344
|
});
|
|
345
|
+
|
|
346
|
+
// MCP selection
|
|
347
|
+
prompts.push({
|
|
348
|
+
type: 'checkbox',
|
|
349
|
+
name: 'mcps',
|
|
350
|
+
message: 'Select MCP servers to include (use space to select):',
|
|
351
|
+
choices: (answers) => {
|
|
352
|
+
const selectedLanguage = answers.language || options.language;
|
|
353
|
+
|
|
354
|
+
if (!selectedLanguage) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const availableMCPs = getMCPsForLanguage(selectedLanguage);
|
|
359
|
+
|
|
360
|
+
return availableMCPs.map(mcp => ({
|
|
361
|
+
value: mcp.id,
|
|
362
|
+
name: `${mcp.name} - ${mcp.description}`,
|
|
363
|
+
checked: mcp.checked
|
|
364
|
+
}));
|
|
365
|
+
},
|
|
366
|
+
prefix: chalk.blue('๐ง'),
|
|
367
|
+
pageSize: 15,
|
|
368
|
+
when: (answers) => {
|
|
369
|
+
const selectedLanguage = answers.language || options.language;
|
|
370
|
+
if (!selectedLanguage) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
const availableMCPs = getMCPsForLanguage(selectedLanguage);
|
|
374
|
+
return availableMCPs.length > 0;
|
|
375
|
+
}
|
|
376
|
+
});
|
|
315
377
|
|
|
316
378
|
// Confirmation
|
|
317
379
|
prompts.push({
|
|
@@ -322,6 +384,7 @@ function createPrompts(projectInfo, options = {}) {
|
|
|
322
384
|
const framework = answers.framework || options.framework || 'none';
|
|
323
385
|
const commandCount = answers.commands ? answers.commands.length : 0;
|
|
324
386
|
const hookCount = answers.hooks ? answers.hooks.length : 0;
|
|
387
|
+
const mcpCount = answers.mcps ? answers.mcps.length : 0;
|
|
325
388
|
|
|
326
389
|
let message = `Setup Claude Code for ${chalk.cyan(language)}`;
|
|
327
390
|
if (framework !== 'none') {
|
|
@@ -333,6 +396,9 @@ function createPrompts(projectInfo, options = {}) {
|
|
|
333
396
|
if (hookCount > 0) {
|
|
334
397
|
message += ` (${chalk.magenta(hookCount)} hooks)`;
|
|
335
398
|
}
|
|
399
|
+
if (mcpCount > 0) {
|
|
400
|
+
message += ` (${chalk.blue(mcpCount)} MCP)`;
|
|
401
|
+
}
|
|
336
402
|
message += '?';
|
|
337
403
|
|
|
338
404
|
return message;
|
package/src/templates.js
CHANGED
|
@@ -7,7 +7,8 @@ const TEMPLATES_CONFIG = {
|
|
|
7
7
|
description: 'Universal configuration for any project',
|
|
8
8
|
files: [
|
|
9
9
|
{ source: 'common/CLAUDE.md', destination: 'CLAUDE.md' },
|
|
10
|
-
{ source: 'common/.claude', destination: '.claude' }
|
|
10
|
+
{ source: 'common/.claude', destination: '.claude' },
|
|
11
|
+
{ source: 'common/.mcp.json', destination: '.mcp.json' }
|
|
11
12
|
]
|
|
12
13
|
},
|
|
13
14
|
'javascript-typescript': {
|
|
@@ -50,23 +51,45 @@ const TEMPLATES_CONFIG = {
|
|
|
50
51
|
description: 'Optimized for Python development',
|
|
51
52
|
files: [
|
|
52
53
|
{ source: 'python/CLAUDE.md', destination: 'CLAUDE.md' },
|
|
53
|
-
{ source: 'python/.claude', destination: '.claude' }
|
|
54
|
-
|
|
54
|
+
{ source: 'python/.claude', destination: '.claude' },
|
|
55
|
+
{ source: 'python/.mcp.json', destination: '.mcp.json' }
|
|
56
|
+
],
|
|
57
|
+
frameworks: {
|
|
58
|
+
'django': {
|
|
59
|
+
name: 'Django',
|
|
60
|
+
additionalFiles: [
|
|
61
|
+
{ source: 'python/examples/django-app/.claude/commands', destination: '.claude/commands' },
|
|
62
|
+
{ source: 'python/examples/django-app/CLAUDE.md', destination: 'CLAUDE.md' }
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
'flask': {
|
|
66
|
+
name: 'Flask',
|
|
67
|
+
additionalFiles: [
|
|
68
|
+
{ source: 'python/examples/flask-app/.claude/commands', destination: '.claude/commands' }
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
'fastapi': {
|
|
72
|
+
name: 'FastAPI',
|
|
73
|
+
additionalFiles: [
|
|
74
|
+
{ source: 'python/examples/fastapi-app/.claude/commands', destination: '.claude/commands' }
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
55
78
|
},
|
|
56
79
|
'rust': {
|
|
57
80
|
name: 'Rust',
|
|
58
81
|
description: 'Optimized for Rust development',
|
|
82
|
+
comingSoon: true,
|
|
59
83
|
files: [
|
|
60
|
-
{ source: 'rust
|
|
61
|
-
{ source: 'rust/.claude', destination: '.claude' }
|
|
84
|
+
{ source: 'rust/.mcp.json', destination: '.mcp.json' }
|
|
62
85
|
]
|
|
63
86
|
},
|
|
64
87
|
'go': {
|
|
65
88
|
name: 'Go',
|
|
66
|
-
description: 'Optimized for Go development',
|
|
89
|
+
description: 'Optimized for Go development',
|
|
90
|
+
comingSoon: true,
|
|
67
91
|
files: [
|
|
68
|
-
{ source: 'go
|
|
69
|
-
{ source: 'go/.claude', destination: '.claude' }
|
|
92
|
+
{ source: 'go/.mcp.json', destination: '.mcp.json' }
|
|
70
93
|
]
|
|
71
94
|
}
|
|
72
95
|
};
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const chalk = require('chalk');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Script to synchronize templates from root folders
|
|
9
|
-
* to cli-tool/templates/
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
async function syncTemplates() {
|
|
13
|
-
console.log(chalk.blue('๐ Synchronizing templates...'));
|
|
14
|
-
|
|
15
|
-
const rootDir = path.join(__dirname, '..', '..');
|
|
16
|
-
const templatesDir = path.join(__dirname, '..', 'templates');
|
|
17
|
-
|
|
18
|
-
// Languages to synchronize
|
|
19
|
-
const languages = ['common', 'javascript-typescript', 'python', 'rust', 'go'];
|
|
20
|
-
|
|
21
|
-
let totalCopied = 0;
|
|
22
|
-
let totalSkipped = 0;
|
|
23
|
-
|
|
24
|
-
for (const language of languages) {
|
|
25
|
-
const sourceDir = path.join(rootDir, language);
|
|
26
|
-
const targetDir = path.join(templatesDir, language);
|
|
27
|
-
|
|
28
|
-
if (!await fs.pathExists(sourceDir)) {
|
|
29
|
-
console.log(chalk.yellow(`โ ๏ธ Source folder does not exist: ${language}`));
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log(chalk.cyan(`\n๐ Synchronizing ${language}...`));
|
|
34
|
-
|
|
35
|
-
// Clean destination directory
|
|
36
|
-
if (await fs.pathExists(targetDir)) {
|
|
37
|
-
await fs.remove(targetDir);
|
|
38
|
-
console.log(chalk.gray(` ๐๏ธ Previous directory removed`));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Copy everything from source
|
|
42
|
-
try {
|
|
43
|
-
await fs.copy(sourceDir, targetDir, {
|
|
44
|
-
filter: (src, dest) => {
|
|
45
|
-
// Filter files we don't want to copy
|
|
46
|
-
const relativePath = path.relative(sourceDir, src);
|
|
47
|
-
|
|
48
|
-
// Exclude specific directories and files
|
|
49
|
-
if (relativePath.includes('node_modules')) return false;
|
|
50
|
-
if (relativePath.includes('.git')) return false;
|
|
51
|
-
if (relativePath.includes('package-lock.json')) return false;
|
|
52
|
-
if (relativePath.endsWith('.log')) return false;
|
|
53
|
-
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Count copied files
|
|
59
|
-
const stats = await getDirectoryStats(targetDir);
|
|
60
|
-
totalCopied += stats.files;
|
|
61
|
-
|
|
62
|
-
console.log(chalk.green(` โ
${stats.files} files copied`));
|
|
63
|
-
|
|
64
|
-
// Show copied structure
|
|
65
|
-
if (stats.files > 0) {
|
|
66
|
-
await showDirectoryStructure(targetDir, ' ');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.error(chalk.red(` โ Error copying ${language}:`), error.message);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.log(chalk.green(`\n๐ Synchronization completed!`));
|
|
75
|
-
console.log(chalk.white(`๐ Total synchronized files: ${totalCopied}`));
|
|
76
|
-
|
|
77
|
-
// Verify that no hook files exist
|
|
78
|
-
await cleanupOldReferences();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function getDirectoryStats(dir) {
|
|
82
|
-
let files = 0;
|
|
83
|
-
let dirs = 0;
|
|
84
|
-
|
|
85
|
-
if (!await fs.pathExists(dir)) {
|
|
86
|
-
return { files: 0, dirs: 0 };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const items = await fs.readdir(dir);
|
|
90
|
-
|
|
91
|
-
for (const item of items) {
|
|
92
|
-
const itemPath = path.join(dir, item);
|
|
93
|
-
const stat = await fs.stat(itemPath);
|
|
94
|
-
|
|
95
|
-
if (stat.isDirectory()) {
|
|
96
|
-
dirs++;
|
|
97
|
-
const subStats = await getDirectoryStats(itemPath);
|
|
98
|
-
files += subStats.files;
|
|
99
|
-
dirs += subStats.dirs;
|
|
100
|
-
} else {
|
|
101
|
-
files++;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return { files, dirs };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function showDirectoryStructure(dir, prefix = '') {
|
|
109
|
-
const items = await fs.readdir(dir);
|
|
110
|
-
|
|
111
|
-
for (let i = 0; i < items.length; i++) {
|
|
112
|
-
const item = items[i];
|
|
113
|
-
const itemPath = path.join(dir, item);
|
|
114
|
-
const stat = await fs.stat(itemPath);
|
|
115
|
-
const isLast = i === items.length - 1;
|
|
116
|
-
const connector = isLast ? 'โโโ ' : 'โโโ ';
|
|
117
|
-
|
|
118
|
-
if (stat.isDirectory()) {
|
|
119
|
-
console.log(chalk.blue(`${prefix}${connector}${item}/`));
|
|
120
|
-
if (item === '.claude' || item === 'commands') {
|
|
121
|
-
// Show only one more level for .claude and commands
|
|
122
|
-
const subItems = await fs.readdir(itemPath);
|
|
123
|
-
const newPrefix = prefix + (isLast ? ' ' : 'โ ');
|
|
124
|
-
for (let j = 0; j < Math.min(subItems.length, 3); j++) {
|
|
125
|
-
const subItem = subItems[j];
|
|
126
|
-
const subConnector = j === Math.min(subItems.length, 3) - 1 ? 'โโโ ' : 'โโโ ';
|
|
127
|
-
console.log(chalk.gray(`${newPrefix}${subConnector}${subItem}`));
|
|
128
|
-
}
|
|
129
|
-
if (subItems.length > 3) {
|
|
130
|
-
console.log(chalk.gray(`${newPrefix}โโโ ... and ${subItems.length - 3} more`));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
} else {
|
|
134
|
-
console.log(chalk.gray(`${prefix}${connector}${item}`));
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async function cleanupOldReferences() {
|
|
140
|
-
console.log(chalk.yellow('\n๐งน Cleaning up obsolete references...'));
|
|
141
|
-
|
|
142
|
-
const templatesDir = path.join(__dirname, '..', 'templates');
|
|
143
|
-
|
|
144
|
-
// Search and remove hooks directories
|
|
145
|
-
const languages = ['javascript-typescript', 'python', 'common'];
|
|
146
|
-
|
|
147
|
-
for (const language of languages) {
|
|
148
|
-
const hooksDir = path.join(templatesDir, language, '.claude', 'hooks');
|
|
149
|
-
if (await fs.pathExists(hooksDir)) {
|
|
150
|
-
await fs.remove(hooksDir);
|
|
151
|
-
console.log(chalk.yellow(` ๐๏ธ Removed: ${language}/.claude/hooks/`));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Check for empty files in commands
|
|
156
|
-
for (const language of languages) {
|
|
157
|
-
const commandsDir = path.join(templatesDir, language, '.claude', 'commands');
|
|
158
|
-
if (await fs.pathExists(commandsDir)) {
|
|
159
|
-
const files = await fs.readdir(commandsDir);
|
|
160
|
-
for (const file of files) {
|
|
161
|
-
const filePath = path.join(commandsDir, file);
|
|
162
|
-
const stat = await fs.stat(filePath);
|
|
163
|
-
if (stat.size < 50) { // Very small files are probably empty
|
|
164
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
165
|
-
if (content.trim().length < 10) {
|
|
166
|
-
console.log(chalk.yellow(` โ ๏ธ Possibly empty file: ${language}/.claude/commands/${file} (${stat.size} bytes)`));
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Function to execute synchronization
|
|
175
|
-
if (require.main === module) {
|
|
176
|
-
syncTemplates().catch(error => {
|
|
177
|
-
console.error(chalk.red('โ Error during synchronization:'), error);
|
|
178
|
-
process.exit(1);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
module.exports = { syncTemplates };
|