claude-code-templates 1.15.0 → 1.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -4,6 +4,152 @@ const chalk = require('chalk');
4
4
  const inquirer = require('inquirer');
5
5
  const { getHooksForLanguage, filterHooksBySelection, getMCPsForLanguage, filterMCPsBySelection } = require('./hook-scanner');
6
6
 
7
+ // GitHub configuration for downloading templates
8
+ const GITHUB_CONFIG = {
9
+ owner: 'davila7',
10
+ repo: 'claude-code-templates',
11
+ branch: 'main',
12
+ templatesPath: 'cli-tool/templates'
13
+ };
14
+
15
+ // Cache for downloaded files to avoid repeated downloads
16
+ const downloadCache = new Map();
17
+
18
+ async function downloadFileFromGitHub(filePath) {
19
+ // Check cache first
20
+ if (downloadCache.has(filePath)) {
21
+ return downloadCache.get(filePath);
22
+ }
23
+
24
+ const githubUrl = `https://raw.githubusercontent.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.templatesPath}/${filePath}`;
25
+
26
+ try {
27
+ const response = await fetch(githubUrl);
28
+ if (!response.ok) {
29
+ throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
30
+ }
31
+
32
+ const content = await response.text();
33
+ downloadCache.set(filePath, content);
34
+ return content;
35
+ } catch (error) {
36
+ console.error(chalk.red(`❌ Error downloading ${filePath} from GitHub:`), error.message);
37
+ throw error;
38
+ }
39
+ }
40
+
41
+ async function downloadDirectoryFromGitHub(dirPath) {
42
+ // For directories, we need to get the list of files first
43
+ // GitHub API endpoint to get directory contents
44
+ const apiUrl = `https://api.github.com/repos/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/contents/${GITHUB_CONFIG.templatesPath}/${dirPath}?ref=${GITHUB_CONFIG.branch}`;
45
+
46
+ try {
47
+ const response = await fetch(apiUrl);
48
+ if (!response.ok) {
49
+ throw new Error(`Failed to get directory listing for ${dirPath}: ${response.status} ${response.statusText}`);
50
+ }
51
+
52
+ const items = await response.json();
53
+ const files = {};
54
+
55
+ for (const item of items) {
56
+ if (item.type === 'file') {
57
+ const relativePath = path.relative(GITHUB_CONFIG.templatesPath, item.path);
58
+ const content = await downloadFileFromGitHub(relativePath);
59
+ files[item.name] = content;
60
+ }
61
+ }
62
+
63
+ return files;
64
+ } catch (error) {
65
+ console.error(chalk.red(`❌ Error downloading directory ${dirPath} from GitHub:`), error.message);
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ // Helper functions for processing downloaded content
71
+ async function processSettingsFileFromContent(settingsContent, destPath, templateConfig) {
72
+ const settings = JSON.parse(settingsContent);
73
+
74
+ // Filter hooks based on selection
75
+ if (templateConfig.selectedHooks && settings.hooks) {
76
+ settings.hooks = filterHooksBySelection(settings.hooks, templateConfig.selectedHooks);
77
+ }
78
+
79
+ const destDir = path.dirname(destPath);
80
+ await fs.ensureDir(destDir);
81
+ await fs.writeJson(destPath, settings, { spaces: 2 });
82
+ }
83
+
84
+ async function mergeSettingsFileFromContent(settingsContent, destPath, templateConfig) {
85
+ const newSettings = JSON.parse(settingsContent);
86
+ let existingSettings = {};
87
+
88
+ if (await fs.pathExists(destPath)) {
89
+ existingSettings = await fs.readJson(destPath);
90
+ }
91
+
92
+ // Filter hooks based on selection
93
+ if (templateConfig.selectedHooks && newSettings.hooks) {
94
+ newSettings.hooks = filterHooksBySelection(newSettings.hooks, templateConfig.selectedHooks);
95
+ }
96
+
97
+ // Merge settings
98
+ const mergedSettings = {
99
+ ...existingSettings,
100
+ ...newSettings,
101
+ hooks: {
102
+ ...existingSettings.hooks,
103
+ ...newSettings.hooks
104
+ }
105
+ };
106
+
107
+ const destDir = path.dirname(destPath);
108
+ await fs.ensureDir(destDir);
109
+ await fs.writeJson(destPath, mergedSettings, { spaces: 2 });
110
+ }
111
+
112
+ async function processMCPFileFromContent(mcpContent, destPath, templateConfig) {
113
+ const mcpConfig = JSON.parse(mcpContent);
114
+
115
+ // Filter MCPs based on selection
116
+ if (templateConfig.selectedMCPs && mcpConfig.mcpServers) {
117
+ mcpConfig.mcpServers = filterMCPsBySelection(mcpConfig.mcpServers, templateConfig.selectedMCPs);
118
+ }
119
+
120
+ const destDir = path.dirname(destPath);
121
+ await fs.ensureDir(destDir);
122
+ await fs.writeJson(destPath, mcpConfig, { spaces: 2 });
123
+ }
124
+
125
+ async function mergeMCPFileFromContent(mcpContent, destPath, templateConfig) {
126
+ const newMcpConfig = JSON.parse(mcpContent);
127
+ let existingMcpConfig = {};
128
+
129
+ if (await fs.pathExists(destPath)) {
130
+ existingMcpConfig = await fs.readJson(destPath);
131
+ }
132
+
133
+ // Filter MCPs based on selection
134
+ if (templateConfig.selectedMCPs && newMcpConfig.mcpServers) {
135
+ newMcpConfig.mcpServers = filterMCPsBySelection(newMcpConfig.mcpServers, templateConfig.selectedMCPs);
136
+ }
137
+
138
+ // Merge MCP configurations
139
+ const mergedMcpConfig = {
140
+ ...existingMcpConfig,
141
+ ...newMcpConfig,
142
+ mcpServers: {
143
+ ...existingMcpConfig.mcpServers,
144
+ ...newMcpConfig.mcpServers
145
+ }
146
+ };
147
+
148
+ const destDir = path.dirname(destPath);
149
+ await fs.ensureDir(destDir);
150
+ await fs.writeJson(destPath, mergedMcpConfig, { spaces: 2 });
151
+ }
152
+
7
153
  async function checkExistingFiles(targetDir, templateConfig) {
8
154
  const existingFiles = [];
9
155
 
@@ -86,7 +232,7 @@ async function createBackups(existingFiles, targetDir) {
86
232
  }
87
233
 
88
234
  async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
89
- const templateDir = path.join(__dirname, '../templates');
235
+ console.log(chalk.gray(`📥 Downloading templates from GitHub (${GITHUB_CONFIG.branch} branch)...`));
90
236
 
91
237
  // Check for existing files and get user preference
92
238
  const existingFiles = await checkExistingFiles(targetDir, templateConfig);
@@ -114,7 +260,6 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
114
260
 
115
261
  // Copy base files and framework-specific files
116
262
  for (const file of templateConfig.files) {
117
- const sourcePath = path.join(templateDir, file.source);
118
263
  const destPath = path.join(targetDir, file.destination);
119
264
 
120
265
  try {
@@ -123,94 +268,115 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
123
268
  // This is a framework-specific commands directory - merge with existing commands
124
269
  await fs.ensureDir(destPath);
125
270
 
126
- // Copy framework-specific commands to the commands directory
127
- const frameworkFiles = await fs.readdir(sourcePath);
128
- for (const frameworkFile of frameworkFiles) {
129
- const srcFile = path.join(sourcePath, frameworkFile);
130
- const destFile = path.join(destPath, frameworkFile);
271
+ // Download framework-specific commands from GitHub
272
+ const frameworkFiles = await downloadDirectoryFromGitHub(file.source);
273
+ for (const [frameworkFileName, content] of Object.entries(frameworkFiles)) {
274
+ const destFile = path.join(destPath, frameworkFileName);
131
275
 
132
276
  // In merge mode, skip if file already exists
133
277
  if (userAction === 'merge' && await fs.pathExists(destFile)) {
134
- console.log(chalk.blue(`⏭️ Skipped ${frameworkFile} (already exists)`));
278
+ console.log(chalk.blue(`⏭️ Skipped ${frameworkFileName} (already exists)`));
135
279
  continue;
136
280
  }
137
281
 
138
- await fs.copy(srcFile, destFile, { overwrite: shouldOverwrite });
282
+ await fs.writeFile(destFile, content, 'utf8');
139
283
  }
140
284
 
141
- console.log(chalk.green(`✓ Copied framework commands ${file.source} → ${file.destination}`));
285
+ console.log(chalk.green(`✓ Downloaded framework commands ${file.source} → ${file.destination}`));
142
286
  } else if (file.source.includes('.claude') && !file.source.includes('examples/')) {
143
- // This is base .claude directory - copy it but handle commands specially
144
- await fs.copy(sourcePath, destPath, {
145
- overwrite: shouldOverwrite,
146
- filter: (src) => {
147
- // Skip the commands directory itself - we'll handle it separately
148
- return !src.endsWith('.claude/commands');
149
- }
150
- });
151
-
152
- // Now handle base commands specifically
153
- const baseCommandsPath = path.join(sourcePath, 'commands');
154
- const destCommandsPath = path.join(destPath, 'commands');
287
+ // This is base .claude directory - download it but handle commands specially
288
+ await fs.ensureDir(destPath);
155
289
 
156
- if (await fs.pathExists(baseCommandsPath)) {
157
- await fs.ensureDir(destCommandsPath);
158
-
159
- // Copy base commands, but exclude framework-specific ones that were moved
160
- const baseCommands = await fs.readdir(baseCommandsPath);
161
- const excludeCommands = ['react-component.md', 'route.md', 'api-endpoint.md']; // Commands moved to framework dirs
290
+ // Download base .claude directory structure from GitHub
291
+ try {
292
+ const baseClaudeFiles = await downloadDirectoryFromGitHub(file.source);
162
293
 
163
- for (const baseCommand of baseCommands) {
164
- if (!excludeCommands.includes(baseCommand)) {
165
- const srcFile = path.join(baseCommandsPath, baseCommand);
166
- const destFile = path.join(destCommandsPath, baseCommand);
294
+ // Write non-command files first
295
+ for (const [fileName, content] of Object.entries(baseClaudeFiles)) {
296
+ if (fileName !== 'commands') { // Skip commands directory, handle separately
297
+ const destFile = path.join(destPath, fileName);
167
298
 
168
299
  // In merge mode, skip if file already exists
169
300
  if (userAction === 'merge' && await fs.pathExists(destFile)) {
170
- console.log(chalk.blue(`⏭️ Skipped ${baseCommand} (already exists)`));
301
+ console.log(chalk.blue(`⏭️ Skipped ${fileName} (already exists)`));
171
302
  continue;
172
303
  }
173
304
 
174
- await fs.copy(srcFile, destFile, { overwrite: shouldOverwrite });
305
+ await fs.writeFile(destFile, content, 'utf8');
175
306
  }
176
307
  }
308
+
309
+ // Now handle base commands specifically
310
+ const destCommandsPath = path.join(destPath, 'commands');
311
+ await fs.ensureDir(destCommandsPath);
312
+
313
+ // Download base commands from GitHub
314
+ const baseCommandsDir = `${file.source}/commands`;
315
+ try {
316
+ const baseCommands = await downloadDirectoryFromGitHub(baseCommandsDir);
317
+ const excludeCommands = ['react-component.md', 'route.md', 'api-endpoint.md']; // Commands moved to framework dirs
318
+
319
+ for (const [baseCommandName, commandContent] of Object.entries(baseCommands)) {
320
+ if (!excludeCommands.includes(baseCommandName)) {
321
+ const destFile = path.join(destCommandsPath, baseCommandName);
322
+
323
+ // In merge mode, skip if file already exists
324
+ if (userAction === 'merge' && await fs.pathExists(destFile)) {
325
+ console.log(chalk.blue(`⏭️ Skipped ${baseCommandName} (already exists)`));
326
+ continue;
327
+ }
328
+
329
+ await fs.writeFile(destFile, commandContent, 'utf8');
330
+ }
331
+ }
332
+ } catch (error) {
333
+ // Commands directory might not exist for some templates, that's ok
334
+ console.log(chalk.yellow(`⚠️ No commands directory found for ${baseCommandsDir}`));
335
+ }
336
+
337
+ } catch (error) {
338
+ console.error(chalk.red(`❌ Error downloading .claude directory: ${error.message}`));
339
+ throw error;
177
340
  }
178
341
 
179
- console.log(chalk.green(`✓ Copied base configuration and commands ${file.source} → ${file.destination}`));
342
+ console.log(chalk.green(`✓ Downloaded base configuration and commands ${file.source} → ${file.destination}`));
180
343
  } else if (file.source.includes('settings.json') && templateConfig.selectedHooks) {
344
+ // Download and process settings.json with hooks
345
+ const settingsContent = await downloadFileFromGitHub(file.source);
346
+
181
347
  // In merge mode, merge settings instead of overwriting
182
348
  if (userAction === 'merge') {
183
- await mergeSettingsFile(sourcePath, destPath, templateConfig);
349
+ await mergeSettingsFileFromContent(settingsContent, destPath, templateConfig);
184
350
  console.log(chalk.green(`✓ Merged ${file.source} → ${file.destination} (with selected hooks)`));
185
351
  } else {
186
- await processSettingsFile(sourcePath, destPath, templateConfig);
187
- console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination} (with selected hooks)`));
352
+ await processSettingsFileFromContent(settingsContent, destPath, templateConfig);
353
+ console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected hooks)`));
188
354
  }
189
355
  } else if (file.source.includes('.mcp.json') && templateConfig.selectedMCPs) {
356
+ // Download and process MCP config with selected MCPs
357
+ const mcpContent = await downloadFileFromGitHub(file.source);
358
+
190
359
  // In merge mode, merge MCP config instead of overwriting
191
360
  if (userAction === 'merge') {
192
- await mergeMCPFile(sourcePath, destPath, templateConfig);
361
+ await mergeMCPFileFromContent(mcpContent, destPath, templateConfig);
193
362
  console.log(chalk.green(`✓ Merged ${file.source} → ${file.destination} (with selected MCPs)`));
194
363
  } else {
195
- await processMCPFile(sourcePath, destPath, templateConfig);
196
- console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination} (with selected MCPs)`));
364
+ await processMCPFileFromContent(mcpContent, destPath, templateConfig);
365
+ console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected MCPs)`));
197
366
  }
198
367
  } else {
199
- // Copy regular files (CLAUDE.md, etc.)
368
+ // Download regular files (CLAUDE.md, etc.)
200
369
  // In merge mode, skip if file already exists
201
370
  if (userAction === 'merge' && await fs.pathExists(destPath)) {
202
371
  console.log(chalk.blue(`⏭️ Skipped ${file.destination} (already exists)`));
203
372
  continue;
204
373
  }
205
374
 
206
- await fs.copy(sourcePath, destPath, {
207
- overwrite: shouldOverwrite,
208
- filter: (src) => {
209
- // Skip commands directory during regular copy - we handle them above
210
- return !src.includes('.claude/commands');
211
- }
212
- });
213
- console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination}`));
375
+ const fileContent = await downloadFileFromGitHub(file.source);
376
+ const destDir = path.dirname(destPath);
377
+ await fs.ensureDir(destDir);
378
+ await fs.writeFile(destPath, fileContent, 'utf8');
379
+ console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination}`));
214
380
  }
215
381
  } catch (error) {
216
382
  console.error(chalk.red(`✗ Failed to copy ${file.source}:`), error.message);
@@ -218,38 +384,8 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
218
384
  }
219
385
  }
220
386
 
387
+ console.log(chalk.cyan(`📦 All templates downloaded from: https://github.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/tree/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.templatesPath}`));
221
388
  return true; // Indicate successful completion
222
-
223
- // Copy selected commands individually
224
- if (templateConfig.selectedCommands && templateConfig.selectedCommands.length > 0) {
225
- const commandsDir = path.join(targetDir, '.claude', 'commands');
226
- await fs.ensureDir(commandsDir);
227
-
228
- for (const command of templateConfig.selectedCommands) {
229
- try {
230
- const commandFileName = `${command.name}.md`;
231
- const destPath = path.join(commandsDir, commandFileName);
232
-
233
- await fs.copy(command.filePath, destPath);
234
- console.log(chalk.green(`✓ Added command: ${command.displayName}`));
235
- } catch (error) {
236
- console.error(chalk.red(`✗ Failed to copy command ${command.name}:`), error.message);
237
- // Don't throw - continue with other commands
238
- }
239
- }
240
-
241
- console.log(chalk.cyan(`📋 Installed ${templateConfig.selectedCommands.length} commands`));
242
- }
243
-
244
- // Report hook selection
245
- if (templateConfig.selectedHooks && templateConfig.selectedHooks.length > 0) {
246
- console.log(chalk.magenta(`🔧 Installed ${templateConfig.selectedHooks.length} automation hooks`));
247
- }
248
-
249
- // Report MCP selection
250
- if (templateConfig.selectedMCPs && templateConfig.selectedMCPs.length > 0) {
251
- console.log(chalk.blue(`🔧 Installed ${templateConfig.selectedMCPs.length} MCP`));
252
- }
253
389
  }
254
390
 
255
391
  async function runPostInstallationValidation(targetDir, templateConfig) {