claude-code-templates 1.0.2 → 1.1.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.
Files changed (35) hide show
  1. package/package.json +7 -2
  2. package/scripts/sync-templates.js +182 -0
  3. package/src/command-scanner.js +175 -0
  4. package/src/file-operations.js +34 -2
  5. package/src/prompts.js +47 -1
  6. package/src/templates.js +10 -1
  7. package/templates/common/.claude/commands/git-workflow.md +239 -0
  8. package/templates/common/.claude/commands/project-setup.md +316 -0
  9. package/templates/go/README.md +25 -0
  10. package/templates/javascript-typescript/.claude/commands/api-endpoint.md +51 -1
  11. package/templates/javascript-typescript/.claude/commands/debug.md +52 -1
  12. package/templates/javascript-typescript/.claude/commands/lint.md +48 -1
  13. package/templates/javascript-typescript/.claude/commands/npm-scripts.md +48 -1
  14. package/templates/javascript-typescript/.claude/commands/react-component.md +54 -1
  15. package/templates/javascript-typescript/.claude/commands/refactor.md +55 -1
  16. package/templates/javascript-typescript/.claude/commands/route.md +193 -0
  17. package/templates/javascript-typescript/.claude/commands/test.md +61 -1
  18. package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +51 -1
  19. package/templates/javascript-typescript/.claude/settings.json +41 -2
  20. package/templates/javascript-typescript/.mcp.json +13 -0
  21. package/templates/javascript-typescript/README.md +213 -187
  22. package/templates/python/.claude/commands/django-model.md +124 -0
  23. package/templates/python/.claude/commands/flask-route.md +217 -0
  24. package/templates/python/.claude/commands/lint.md +111 -0
  25. package/templates/python/.claude/commands/test.md +73 -0
  26. package/templates/rust/README.md +26 -0
  27. package/templates/javascript-typescript/.claude/hooks/format-on-save.json +0 -1
  28. package/templates/javascript-typescript/.claude/hooks/lint-on-save.json +0 -1
  29. package/templates/javascript-typescript/.claude/hooks/typescript-check.json +0 -1
  30. package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -1
  31. package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -1
  32. package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -1
  33. package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -1
  34. package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -1
  35. package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.0.2",
3
+ "version": "1.1.1",
4
4
  "description": "CLI tool to setup Claude Code configurations for different programming languages",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -11,7 +11,11 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "node bin/create-claude-config.js",
14
- "test": "echo \"Error: no test specified\" && exit 1"
14
+ "test": "echo \"Error: no test specified\" && exit 1",
15
+ "sync": "node scripts/sync-templates.js",
16
+ "presync": "echo \"🔄 Iniciando sincronización de plantillas...\"",
17
+ "postsync": "echo \"✅ Sincronización completada. Listo para publicar!\"",
18
+ "prepublishOnly": "npm run sync"
15
19
  },
16
20
  "keywords": [
17
21
  "claude",
@@ -46,6 +50,7 @@
46
50
  "files": [
47
51
  "bin/",
48
52
  "src/",
53
+ "scripts/",
49
54
  "templates/",
50
55
  "README.md"
51
56
  ]
@@ -0,0 +1,182 @@
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 para sincronizar las plantillas desde las carpetas root
9
+ * hacia cli-tool/templates/
10
+ */
11
+
12
+ async function syncTemplates() {
13
+ console.log(chalk.blue('🔄 Sincronizando plantillas...'));
14
+
15
+ const rootDir = path.join(__dirname, '..', '..');
16
+ const templatesDir = path.join(__dirname, '..', 'templates');
17
+
18
+ // Lenguajes a sincronizar
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(`⚠️ Carpeta source no existe: ${language}`));
30
+ continue;
31
+ }
32
+
33
+ console.log(chalk.cyan(`\n📂 Sincronizando ${language}...`));
34
+
35
+ // Limpiar directorio destino
36
+ if (await fs.pathExists(targetDir)) {
37
+ await fs.remove(targetDir);
38
+ console.log(chalk.gray(` 🗑️ Directorio anterior eliminado`));
39
+ }
40
+
41
+ // Copiar todo desde source
42
+ try {
43
+ await fs.copy(sourceDir, targetDir, {
44
+ filter: (src, dest) => {
45
+ // Filtrar archivos que no queremos copiar
46
+ const relativePath = path.relative(sourceDir, src);
47
+
48
+ // Excluir directorios y archivos específicos
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
+ // Contar archivos copiados
59
+ const stats = await getDirectoryStats(targetDir);
60
+ totalCopied += stats.files;
61
+
62
+ console.log(chalk.green(` ✅ ${stats.files} archivos copiados`));
63
+
64
+ // Mostrar estructura copiada
65
+ if (stats.files > 0) {
66
+ await showDirectoryStructure(targetDir, ' ');
67
+ }
68
+
69
+ } catch (error) {
70
+ console.error(chalk.red(` ❌ Error copiando ${language}:`), error.message);
71
+ }
72
+ }
73
+
74
+ console.log(chalk.green(`\n🎉 Sincronización completada!`));
75
+ console.log(chalk.white(`📊 Total de archivos sincronizados: ${totalCopied}`));
76
+
77
+ // Verificar que no existan archivos hooks
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
+ // Mostrar solo un nivel más para .claude y 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}└── ... y ${subItems.length - 3} más`));
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🧹 Limpiando referencias obsoletas...'));
141
+
142
+ const templatesDir = path.join(__dirname, '..', 'templates');
143
+
144
+ // Buscar y eliminar directorios hooks
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(` 🗑️ Eliminado: ${language}/.claude/hooks/`));
152
+ }
153
+ }
154
+
155
+ // Verificar archivos vacíos en 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) { // Archivos muy pequeños probablemente estén vacíos
164
+ const content = await fs.readFile(filePath, 'utf8');
165
+ if (content.trim().length < 10) {
166
+ console.log(chalk.yellow(` ⚠️ Archivo posiblemente vacío: ${language}/.claude/commands/${file} (${stat.size} bytes)`));
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ // Función para ejecutar la sincronización
175
+ if (require.main === module) {
176
+ syncTemplates().catch(error => {
177
+ console.error(chalk.red('❌ Error durante la sincronización:'), error);
178
+ process.exit(1);
179
+ });
180
+ }
181
+
182
+ module.exports = { syncTemplates };
@@ -0,0 +1,175 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Scans and returns available commands for a given language
6
+ * @param {string} language - The language template to scan
7
+ * @returns {Array} Array of available commands with metadata
8
+ */
9
+ function getAvailableCommands(language) {
10
+ const templatesDir = path.join(__dirname, '..', 'templates');
11
+ const languageDir = path.join(templatesDir, language);
12
+
13
+ // Check if language directory exists
14
+ if (!fs.existsSync(languageDir)) {
15
+ return [];
16
+ }
17
+
18
+ const commands = [];
19
+
20
+ // Scan main .claude/commands directory
21
+ const mainCommandsDir = path.join(languageDir, '.claude', 'commands');
22
+ if (fs.existsSync(mainCommandsDir)) {
23
+ const mainCommands = scanCommandsInDirectory(mainCommandsDir, 'core');
24
+ commands.push(...mainCommands);
25
+ }
26
+
27
+ // Scan framework-specific commands in examples
28
+ const examplesDir = path.join(languageDir, 'examples');
29
+ if (fs.existsSync(examplesDir)) {
30
+ const frameworkDirs = fs.readdirSync(examplesDir).filter(dir => {
31
+ return fs.statSync(path.join(examplesDir, dir)).isDirectory();
32
+ });
33
+
34
+ frameworkDirs.forEach(framework => {
35
+ const frameworkCommandsDir = path.join(examplesDir, framework, '.claude', 'commands');
36
+ if (fs.existsSync(frameworkCommandsDir)) {
37
+ const frameworkCommands = scanCommandsInDirectory(frameworkCommandsDir, framework);
38
+ commands.push(...frameworkCommands);
39
+ }
40
+ });
41
+ }
42
+
43
+ // Remove duplicates based on command name
44
+ const uniqueCommands = commands.reduce((acc, command) => {
45
+ const existing = acc.find(c => c.name === command.name);
46
+ if (!existing) {
47
+ acc.push(command);
48
+ } else {
49
+ // If duplicate, prefer core commands over framework-specific ones
50
+ if (command.category === 'core' && existing.category !== 'core') {
51
+ const index = acc.findIndex(c => c.name === command.name);
52
+ acc[index] = command;
53
+ }
54
+ }
55
+ return acc;
56
+ }, []);
57
+
58
+ return uniqueCommands.sort((a, b) => {
59
+ // Sort by category first (core first), then by name
60
+ if (a.category !== b.category) {
61
+ return a.category === 'core' ? -1 : 1;
62
+ }
63
+ return a.name.localeCompare(b.name);
64
+ });
65
+ }
66
+
67
+ /**
68
+ * Scans a directory for command files and returns command metadata
69
+ * @param {string} commandsDir - Directory to scan
70
+ * @param {string} category - Category of commands (core, react-app, node-api, etc.)
71
+ * @returns {Array} Array of command objects
72
+ */
73
+ function scanCommandsInDirectory(commandsDir, category) {
74
+ const commands = [];
75
+
76
+ try {
77
+ const files = fs.readdirSync(commandsDir);
78
+
79
+ files.forEach(file => {
80
+ if (path.extname(file) === '.md') {
81
+ const commandName = path.basename(file, '.md');
82
+ const filePath = path.join(commandsDir, file);
83
+
84
+ // Read the command file to extract metadata
85
+ const content = fs.readFileSync(filePath, 'utf8');
86
+ const metadata = parseCommandMetadata(content, commandName);
87
+
88
+ commands.push({
89
+ name: commandName,
90
+ displayName: metadata.title || commandName,
91
+ description: metadata.description || `${commandName} command`,
92
+ category: category,
93
+ filePath: filePath,
94
+ checked: metadata.defaultChecked || false
95
+ });
96
+ }
97
+ });
98
+ } catch (error) {
99
+ console.warn(`Warning: Could not scan commands in ${commandsDir}:`, error.message);
100
+ }
101
+
102
+ return commands;
103
+ }
104
+
105
+ /**
106
+ * Parses command metadata from markdown file content
107
+ * @param {string} content - The markdown content
108
+ * @param {string} commandName - The command name
109
+ * @returns {Object} Parsed metadata
110
+ */
111
+ function parseCommandMetadata(content, commandName) {
112
+ const metadata = {
113
+ title: null,
114
+ description: null,
115
+ defaultChecked: false
116
+ };
117
+
118
+ const lines = content.split('\n');
119
+
120
+ // Extract title from first heading
121
+ for (let i = 0; i < Math.min(lines.length, 10); i++) {
122
+ const line = lines[i].trim();
123
+ if (line.startsWith('# ')) {
124
+ metadata.title = line.substring(2).trim();
125
+ break;
126
+ }
127
+ }
128
+
129
+ // Extract description from purpose section or first paragraph
130
+ const purposeMatch = content.match(/## Purpose\s*\n\s*(.+?)(?=\n\n|\n##|$)/s);
131
+ if (purposeMatch) {
132
+ metadata.description = purposeMatch[1].trim().replace(/\n/g, ' ');
133
+ } else {
134
+ // Try to find first paragraph after title
135
+ const paragraphMatch = content.match(/^#[^\n]*\n\s*\n(.+?)(?=\n\n|\n##|$)/s);
136
+ if (paragraphMatch) {
137
+ metadata.description = paragraphMatch[1].trim().replace(/\n/g, ' ');
138
+ }
139
+ }
140
+
141
+ // Determine default checked state (core commands usually checked by default)
142
+ const coreCommands = ['test', 'lint', 'debug', 'refactor'];
143
+ metadata.defaultChecked = coreCommands.includes(commandName);
144
+
145
+ return metadata;
146
+ }
147
+
148
+ /**
149
+ * Get commands available for a specific language and framework combination
150
+ * @param {string} language - The language template
151
+ * @param {string} framework - The framework (optional)
152
+ * @returns {Array} Array of available commands
153
+ */
154
+ function getCommandsForLanguageAndFramework(language, framework = null) {
155
+ const allCommands = getAvailableCommands(language);
156
+
157
+ if (!framework || framework === 'none') {
158
+ // Return only core commands
159
+ return allCommands.filter(cmd => cmd.category === 'core');
160
+ }
161
+
162
+ // Return core commands + framework-specific commands
163
+ return allCommands.filter(cmd =>
164
+ cmd.category === 'core' ||
165
+ cmd.category === framework ||
166
+ cmd.category === `${framework}-app`
167
+ );
168
+ }
169
+
170
+ module.exports = {
171
+ getAvailableCommands,
172
+ getCommandsForLanguageAndFramework,
173
+ scanCommandsInDirectory,
174
+ parseCommandMetadata
175
+ };
@@ -23,19 +23,51 @@ async function copyTemplateFiles(templateConfig, targetDir) {
23
23
  console.log(chalk.yellow(`📁 Existing .claude directory backed up to .claude.backup`));
24
24
  }
25
25
 
26
- // Copy files
26
+ // Copy base files (but skip .claude/commands for now)
27
27
  for (const file of templateConfig.files) {
28
28
  const sourcePath = path.join(templateDir, file.source);
29
29
  const destPath = path.join(targetDir, file.destination);
30
30
 
31
31
  try {
32
- await fs.copy(sourcePath, destPath, { overwrite: true });
32
+ // Skip .claude/commands directories - we'll handle them separately
33
+ if (file.source.includes('.claude/commands')) {
34
+ continue;
35
+ }
36
+
37
+ await fs.copy(sourcePath, destPath, {
38
+ overwrite: true,
39
+ filter: (src) => {
40
+ // Skip commands directory during base copy
41
+ return !src.includes('.claude/commands');
42
+ }
43
+ });
33
44
  console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination}`));
34
45
  } catch (error) {
35
46
  console.error(chalk.red(`✗ Failed to copy ${file.source}:`), error.message);
36
47
  throw error;
37
48
  }
38
49
  }
50
+
51
+ // Copy selected commands individually
52
+ if (templateConfig.selectedCommands && templateConfig.selectedCommands.length > 0) {
53
+ const commandsDir = path.join(targetDir, '.claude', 'commands');
54
+ await fs.ensureDir(commandsDir);
55
+
56
+ for (const command of templateConfig.selectedCommands) {
57
+ try {
58
+ const commandFileName = `${command.name}.md`;
59
+ const destPath = path.join(commandsDir, commandFileName);
60
+
61
+ await fs.copy(command.filePath, destPath);
62
+ console.log(chalk.green(`✓ Added command: ${command.displayName}`));
63
+ } catch (error) {
64
+ console.error(chalk.red(`✗ Failed to copy command ${command.name}:`), error.message);
65
+ // Don't throw - continue with other commands
66
+ }
67
+ }
68
+
69
+ console.log(chalk.cyan(`📋 Installed ${templateConfig.selectedCommands.length} commands`));
70
+ }
39
71
  }
40
72
 
41
73
  async function ensureDirectoryExists(dirPath) {
package/src/prompts.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const chalk = require('chalk');
2
2
  const { getAvailableLanguages, getFrameworksForLanguage } = require('./templates');
3
+ const { getCommandsForLanguageAndFramework } = require('./command-scanner');
3
4
 
4
5
  function createPrompts(projectInfo, options = {}) {
5
6
  const prompts = [];
@@ -41,6 +42,40 @@ function createPrompts(projectInfo, options = {}) {
41
42
  });
42
43
  }
43
44
 
45
+ // Command selection
46
+ prompts.push({
47
+ type: 'checkbox',
48
+ name: 'commands',
49
+ message: 'Select commands to include (use space to select):',
50
+ choices: (answers) => {
51
+ const selectedLanguage = answers.language || options.language;
52
+ const selectedFramework = answers.framework || options.framework;
53
+
54
+ if (!selectedLanguage || selectedLanguage === 'common') {
55
+ return [
56
+ {
57
+ value: 'basic-commands',
58
+ name: 'Basic development commands',
59
+ checked: true
60
+ }
61
+ ];
62
+ }
63
+
64
+ const availableCommands = getCommandsForLanguageAndFramework(selectedLanguage, selectedFramework);
65
+
66
+ return availableCommands.map(cmd => ({
67
+ value: cmd.name,
68
+ name: `${cmd.displayName} - ${cmd.description}`,
69
+ checked: cmd.checked
70
+ }));
71
+ },
72
+ prefix: chalk.cyan('📋'),
73
+ when: (answers) => {
74
+ const selectedLanguage = answers.language || options.language;
75
+ return selectedLanguage && selectedLanguage !== 'common';
76
+ }
77
+ });
78
+
44
79
  // Features selection
45
80
  prompts.push({
46
81
  type: 'checkbox',
@@ -78,7 +113,18 @@ function createPrompts(projectInfo, options = {}) {
78
113
  message: (answers) => {
79
114
  const language = answers.language || options.language || 'common';
80
115
  const framework = answers.framework || options.framework || 'none';
81
- return `Setup Claude Code for ${chalk.cyan(language)}${framework !== 'none' ? ` with ${chalk.green(framework)}` : ''}?`;
116
+ const commandCount = answers.commands ? answers.commands.length : 0;
117
+
118
+ let message = `Setup Claude Code for ${chalk.cyan(language)}`;
119
+ if (framework !== 'none') {
120
+ message += ` with ${chalk.green(framework)}`;
121
+ }
122
+ if (commandCount > 0) {
123
+ message += ` (${chalk.yellow(commandCount)} commands)`;
124
+ }
125
+ message += '?';
126
+
127
+ return message;
82
128
  },
83
129
  default: true,
84
130
  prefix: chalk.red('🚀')
package/src/templates.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const path = require('path');
2
+ const { getCommandsForLanguageAndFramework } = require('./command-scanner');
2
3
 
3
4
  const TEMPLATES_CONFIG = {
4
5
  'common': {
@@ -101,7 +102,7 @@ function getFrameworksForLanguage(language) {
101
102
  }
102
103
 
103
104
  function getTemplateConfig(selections) {
104
- const { language, framework } = selections;
105
+ const { language, framework, commands = [] } = selections;
105
106
  const baseConfig = TEMPLATES_CONFIG[language];
106
107
 
107
108
  if (!baseConfig) {
@@ -118,10 +119,18 @@ function getTemplateConfig(selections) {
118
119
  }
119
120
  }
120
121
 
122
+ // Handle command selection
123
+ let selectedCommands = [];
124
+ if (commands && commands.length > 0) {
125
+ const availableCommands = getCommandsForLanguageAndFramework(language, framework);
126
+ selectedCommands = availableCommands.filter(cmd => commands.includes(cmd.name));
127
+ }
128
+
121
129
  return {
122
130
  language,
123
131
  framework,
124
132
  files,
133
+ selectedCommands,
125
134
  config: baseConfig
126
135
  };
127
136
  }