claude-code-templates 1.14.12 → 1.14.14

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 (70) hide show
  1. package/bin/create-claude-config.js +1 -0
  2. package/package.json +1 -2
  3. package/src/file-operations.js +239 -36
  4. package/src/index.js +347 -9
  5. package/templates/common/.claude/commands/git-workflow.md +0 -239
  6. package/templates/common/.claude/commands/project-setup.md +0 -316
  7. package/templates/common/.mcp.json +0 -41
  8. package/templates/common/CLAUDE.md +0 -109
  9. package/templates/common/README.md +0 -96
  10. package/templates/go/.mcp.json +0 -78
  11. package/templates/go/README.md +0 -25
  12. package/templates/javascript-typescript/.claude/commands/api-endpoint.md +0 -51
  13. package/templates/javascript-typescript/.claude/commands/debug.md +0 -52
  14. package/templates/javascript-typescript/.claude/commands/lint.md +0 -48
  15. package/templates/javascript-typescript/.claude/commands/npm-scripts.md +0 -48
  16. package/templates/javascript-typescript/.claude/commands/refactor.md +0 -55
  17. package/templates/javascript-typescript/.claude/commands/test.md +0 -61
  18. package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +0 -51
  19. package/templates/javascript-typescript/.claude/settings.json +0 -142
  20. package/templates/javascript-typescript/.mcp.json +0 -80
  21. package/templates/javascript-typescript/CLAUDE.md +0 -185
  22. package/templates/javascript-typescript/README.md +0 -259
  23. package/templates/javascript-typescript/examples/angular-app/.claude/commands/components.md +0 -63
  24. package/templates/javascript-typescript/examples/angular-app/.claude/commands/services.md +0 -62
  25. package/templates/javascript-typescript/examples/node-api/.claude/commands/api-endpoint.md +0 -46
  26. package/templates/javascript-typescript/examples/node-api/.claude/commands/database.md +0 -56
  27. package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -61
  28. package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -57
  29. package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -102
  30. package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -29
  31. package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -44
  32. package/templates/javascript-typescript/examples/react-app/.claude/commands/state-management.md +0 -45
  33. package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -81
  34. package/templates/javascript-typescript/examples/react-app/agents/react-performance-optimization.md +0 -530
  35. package/templates/javascript-typescript/examples/react-app/agents/react-state-management.md +0 -295
  36. package/templates/javascript-typescript/examples/vue-app/.claude/commands/components.md +0 -46
  37. package/templates/javascript-typescript/examples/vue-app/.claude/commands/composables.md +0 -51
  38. package/templates/python/.claude/commands/lint.md +0 -111
  39. package/templates/python/.claude/commands/test.md +0 -73
  40. package/templates/python/.claude/settings.json +0 -153
  41. package/templates/python/.mcp.json +0 -78
  42. package/templates/python/CLAUDE.md +0 -276
  43. package/templates/python/examples/django-app/.claude/commands/admin.md +0 -264
  44. package/templates/python/examples/django-app/.claude/commands/django-model.md +0 -124
  45. package/templates/python/examples/django-app/.claude/commands/views.md +0 -222
  46. package/templates/python/examples/django-app/CLAUDE.md +0 -313
  47. package/templates/python/examples/django-app/agents/django-api-security.md +0 -642
  48. package/templates/python/examples/django-app/agents/django-database-optimization.md +0 -752
  49. package/templates/python/examples/fastapi-app/.claude/commands/api-endpoints.md +0 -513
  50. package/templates/python/examples/fastapi-app/.claude/commands/auth.md +0 -775
  51. package/templates/python/examples/fastapi-app/.claude/commands/database.md +0 -657
  52. package/templates/python/examples/fastapi-app/.claude/commands/deployment.md +0 -160
  53. package/templates/python/examples/fastapi-app/.claude/commands/testing.md +0 -927
  54. package/templates/python/examples/fastapi-app/CLAUDE.md +0 -229
  55. package/templates/python/examples/flask-app/.claude/commands/app-factory.md +0 -384
  56. package/templates/python/examples/flask-app/.claude/commands/blueprint.md +0 -243
  57. package/templates/python/examples/flask-app/.claude/commands/database.md +0 -410
  58. package/templates/python/examples/flask-app/.claude/commands/deployment.md +0 -620
  59. package/templates/python/examples/flask-app/.claude/commands/flask-route.md +0 -217
  60. package/templates/python/examples/flask-app/.claude/commands/testing.md +0 -559
  61. package/templates/python/examples/flask-app/CLAUDE.md +0 -391
  62. package/templates/ruby/.claude/commands/model.md +0 -360
  63. package/templates/ruby/.claude/commands/test.md +0 -480
  64. package/templates/ruby/.claude/settings.json +0 -146
  65. package/templates/ruby/.mcp.json +0 -83
  66. package/templates/ruby/CLAUDE.md +0 -284
  67. package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +0 -490
  68. package/templates/ruby/examples/rails-app/CLAUDE.md +0 -376
  69. package/templates/rust/.mcp.json +0 -78
  70. package/templates/rust/README.md +0 -26
@@ -58,6 +58,7 @@ program
58
58
  .option('--agent <agent>', 'install specific agent component')
59
59
  .option('--command <command>', 'install specific command component')
60
60
  .option('--mcp <mcp>', 'install specific MCP component')
61
+ .option('--workflow <workflow>', 'install workflow from hash (format: #hash)')
61
62
  .action(async (options) => {
62
63
  try {
63
64
  await createClaudeConfig(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.14.12",
3
+ "version": "1.14.14",
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": {
@@ -85,7 +85,6 @@
85
85
  "files": [
86
86
  "bin/",
87
87
  "src/",
88
- "templates/",
89
88
  "README.md"
90
89
  ],
91
90
  "devDependencies": {
@@ -15,17 +15,43 @@ const GITHUB_CONFIG = {
15
15
  // Cache for downloaded files to avoid repeated downloads
16
16
  const downloadCache = new Map();
17
17
 
18
- async function downloadFileFromGitHub(filePath) {
18
+ async function downloadFileFromGitHub(filePath, retryCount = 0) {
19
19
  // Check cache first
20
20
  if (downloadCache.has(filePath)) {
21
21
  return downloadCache.get(filePath);
22
22
  }
23
23
 
24
+ const maxRetries = 3;
25
+ const baseDelay = 1000;
26
+ const retryDelay = baseDelay * Math.pow(2, retryCount); // Exponential backoff: 1s, 2s, 4s
24
27
  const githubUrl = `https://raw.githubusercontent.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.templatesPath}/${filePath}`;
25
28
 
26
29
  try {
27
30
  const response = await fetch(githubUrl);
31
+
32
+ // Handle rate limiting for raw.githubusercontent.com (though less common)
33
+ if (response.status === 403 && retryCount < maxRetries) {
34
+ const rateLimitMsg = response.statusText.toLowerCase();
35
+ if (rateLimitMsg.includes('rate limit') || rateLimitMsg.includes('forbidden')) {
36
+ console.log(chalk.yellow(`⏳ Rate limited downloading ${filePath}, retrying in ${Math.ceil(retryDelay / 1000)}s...`));
37
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
38
+ return downloadFileFromGitHub(filePath, retryCount + 1);
39
+ }
40
+ }
41
+
28
42
  if (!response.ok) {
43
+ // For 404s, just throw - these are legitimate missing files
44
+ if (response.status === 404) {
45
+ throw new Error(`File not found: ${filePath} (404)`);
46
+ }
47
+
48
+ // For other errors, retry if possible
49
+ if (retryCount < maxRetries) {
50
+ console.log(chalk.yellow(`⚠️ Error ${response.status} downloading ${filePath}, retrying...`));
51
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
52
+ return downloadFileFromGitHub(filePath, retryCount + 1);
53
+ }
54
+
29
55
  throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
30
56
  }
31
57
 
@@ -33,37 +59,122 @@ async function downloadFileFromGitHub(filePath) {
33
59
  downloadCache.set(filePath, content);
34
60
  return content;
35
61
  } catch (error) {
36
- console.error(chalk.red(`❌ Error downloading ${filePath} from GitHub:`), error.message);
62
+ // Network errors - retry if possible
63
+ if (retryCount < maxRetries && (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT' || error.message.includes('fetch'))) {
64
+ console.log(chalk.yellow(`⚠️ Network error downloading ${filePath}, retrying in ${Math.ceil(retryDelay / 1000)}s...`));
65
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
66
+ return downloadFileFromGitHub(filePath, retryCount + 1);
67
+ }
68
+
69
+ // Don't log error here - let caller handle it
37
70
  throw error;
38
71
  }
39
72
  }
40
73
 
41
- async function downloadDirectoryFromGitHub(dirPath) {
74
+ async function downloadDirectoryFromGitHub(dirPath, retryCount = 0) {
75
+ const maxRetries = 5; // Increased retry attempts
76
+ const baseDelay = 2000; // Base delay of 2 seconds
77
+ const retryDelay = baseDelay * Math.pow(2, retryCount); // Exponential backoff: 2s, 4s, 8s, 16s, 32s
78
+
42
79
  // For directories, we need to get the list of files first
43
80
  // GitHub API endpoint to get directory contents
44
81
  const apiUrl = `https://api.github.com/repos/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/contents/${GITHUB_CONFIG.templatesPath}/${dirPath}?ref=${GITHUB_CONFIG.branch}`;
45
82
 
46
83
  try {
47
84
  const response = await fetch(apiUrl);
85
+
86
+ // Handle rate limiting with more sophisticated detection
87
+ if (response.status === 403) {
88
+ const rateLimitRemaining = response.headers.get('x-ratelimit-remaining');
89
+ const rateLimitReset = response.headers.get('x-ratelimit-reset');
90
+ const isRateLimit = rateLimitRemaining === '0' || response.statusText.toLowerCase().includes('rate limit');
91
+
92
+ if (isRateLimit && retryCount < maxRetries) {
93
+ let waitTime = retryDelay;
94
+
95
+ // If we have reset time, calculate exact wait time
96
+ if (rateLimitReset) {
97
+ const resetTime = parseInt(rateLimitReset) * 1000;
98
+ const currentTime = Date.now();
99
+ const exactWaitTime = Math.max(resetTime - currentTime + 1000, retryDelay); // Add 1s buffer
100
+ waitTime = Math.min(exactWaitTime, 60000); // Cap at 60 seconds
101
+ }
102
+
103
+ console.log(chalk.yellow(`⏳ GitHub API rate limit exceeded for ${dirPath}`));
104
+ console.log(chalk.yellow(` Waiting ${Math.ceil(waitTime / 1000)}s before retry ${retryCount + 1}/${maxRetries}...`));
105
+ console.log(chalk.gray(` Rate limit resets at: ${rateLimitReset ? new Date(parseInt(rateLimitReset) * 1000).toLocaleTimeString() : 'unknown'}`));
106
+
107
+ await new Promise(resolve => setTimeout(resolve, waitTime));
108
+ return downloadDirectoryFromGitHub(dirPath, retryCount + 1);
109
+ } else if (isRateLimit) {
110
+ console.log(chalk.red(`❌ GitHub API rate limit exceeded after ${maxRetries} retries`));
111
+ console.log(chalk.yellow(` Directory ${dirPath} will be skipped (some template files may be missing)`));
112
+ return {}; // Return empty object instead of throwing error
113
+ } else {
114
+ // Different 403 error (permissions, etc.)
115
+ console.log(chalk.yellow(`⚠️ Access denied for ${dirPath} (403). This may be normal for some templates.`));
116
+ return {};
117
+ }
118
+ }
119
+
48
120
  if (!response.ok) {
121
+ // If it's a 404, the directory doesn't exist - that's ok for some templates
122
+ if (response.status === 404) {
123
+ console.log(chalk.yellow(`⚠️ Directory ${dirPath} not found (this is normal for some templates)`));
124
+ return {};
125
+ }
126
+
127
+ // For other errors, retry if we haven't exceeded max retries
128
+ if (retryCount < maxRetries) {
129
+ console.log(chalk.yellow(`⚠️ Error ${response.status} for ${dirPath}, retrying in ${Math.ceil(retryDelay / 1000)}s...`));
130
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
131
+ return downloadDirectoryFromGitHub(dirPath, retryCount + 1);
132
+ }
133
+
49
134
  throw new Error(`Failed to get directory listing for ${dirPath}: ${response.status} ${response.statusText}`);
50
135
  }
51
136
 
52
137
  const items = await response.json();
53
138
  const files = {};
139
+ let successCount = 0;
140
+ let skipCount = 0;
54
141
 
55
142
  for (const item of items) {
56
143
  if (item.type === 'file') {
57
144
  const relativePath = path.relative(GITHUB_CONFIG.templatesPath, item.path);
58
- const content = await downloadFileFromGitHub(relativePath);
59
- files[item.name] = content;
145
+ try {
146
+ const content = await downloadFileFromGitHub(relativePath);
147
+ files[item.name] = content;
148
+ successCount++;
149
+ } catch (fileError) {
150
+ skipCount++;
151
+ if (fileError.message.includes('rate limit') || fileError.message.includes('403')) {
152
+ console.log(chalk.yellow(`⚠️ Rate limited while downloading ${item.name}, skipping...`));
153
+ } else {
154
+ console.log(chalk.yellow(`⚠️ Skipped ${item.name}: ${fileError.message}`));
155
+ }
156
+ // Continue with other files instead of failing completely
157
+ }
60
158
  }
61
159
  }
62
160
 
161
+ if (successCount > 0) {
162
+ console.log(chalk.green(`✓ Downloaded ${successCount} files from ${dirPath}${skipCount > 0 ? ` (${skipCount} skipped)` : ''}`));
163
+ } else if (skipCount > 0) {
164
+ console.log(chalk.yellow(`⚠️ All ${skipCount} files in ${dirPath} were skipped due to errors`));
165
+ }
166
+
63
167
  return files;
64
168
  } catch (error) {
169
+ if (retryCount < maxRetries && (error.message.includes('rate limit') || error.message.includes('ECONNRESET'))) {
170
+ console.log(chalk.yellow(`⚠️ Network error for ${dirPath}, retrying in ${Math.ceil(retryDelay / 1000)}s...`));
171
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
172
+ return downloadDirectoryFromGitHub(dirPath, retryCount + 1);
173
+ }
174
+
65
175
  console.error(chalk.red(`❌ Error downloading directory ${dirPath} from GitHub:`), error.message);
66
- throw error;
176
+ console.log(chalk.yellow(` Continuing with available files (some template files may be missing)`));
177
+ return {}; // Return empty object to continue with other parts of the template
67
178
  }
68
179
  }
69
180
 
@@ -112,14 +223,26 @@ async function mergeSettingsFileFromContent(settingsContent, destPath, templateC
112
223
  async function processMCPFileFromContent(mcpContent, destPath, templateConfig) {
113
224
  const mcpConfig = JSON.parse(mcpContent);
114
225
 
226
+ // Clean and prepare MCP config (only keep mcpServers without descriptions)
227
+ const cleanMcpConfig = { mcpServers: {} };
228
+ if (mcpConfig.mcpServers) {
229
+ for (const serverName in mcpConfig.mcpServers) {
230
+ if (mcpConfig.mcpServers[serverName] && typeof mcpConfig.mcpServers[serverName] === 'object') {
231
+ const serverConfig = { ...mcpConfig.mcpServers[serverName] };
232
+ delete serverConfig.description; // Remove description field
233
+ cleanMcpConfig.mcpServers[serverName] = serverConfig;
234
+ }
235
+ }
236
+ }
237
+
115
238
  // Filter MCPs based on selection
116
- if (templateConfig.selectedMCPs && mcpConfig.mcpServers) {
117
- mcpConfig.mcpServers = filterMCPsBySelection(mcpConfig.mcpServers, templateConfig.selectedMCPs);
239
+ if (templateConfig.selectedMCPs && cleanMcpConfig.mcpServers) {
240
+ cleanMcpConfig.mcpServers = filterMCPsBySelection(cleanMcpConfig.mcpServers, templateConfig.selectedMCPs);
118
241
  }
119
242
 
120
243
  const destDir = path.dirname(destPath);
121
244
  await fs.ensureDir(destDir);
122
- await fs.writeJson(destPath, mcpConfig, { spaces: 2 });
245
+ await fs.writeJson(destPath, cleanMcpConfig, { spaces: 2 });
123
246
  }
124
247
 
125
248
  async function mergeMCPFileFromContent(mcpContent, destPath, templateConfig) {
@@ -130,18 +253,28 @@ async function mergeMCPFileFromContent(mcpContent, destPath, templateConfig) {
130
253
  existingMcpConfig = await fs.readJson(destPath);
131
254
  }
132
255
 
256
+ // Clean new MCP config (only keep mcpServers without descriptions)
257
+ const cleanNewMcpConfig = { mcpServers: {} };
258
+ if (newMcpConfig.mcpServers) {
259
+ for (const serverName in newMcpConfig.mcpServers) {
260
+ if (newMcpConfig.mcpServers[serverName] && typeof newMcpConfig.mcpServers[serverName] === 'object') {
261
+ const serverConfig = { ...newMcpConfig.mcpServers[serverName] };
262
+ delete serverConfig.description; // Remove description field
263
+ cleanNewMcpConfig.mcpServers[serverName] = serverConfig;
264
+ }
265
+ }
266
+ }
267
+
133
268
  // Filter MCPs based on selection
134
- if (templateConfig.selectedMCPs && newMcpConfig.mcpServers) {
135
- newMcpConfig.mcpServers = filterMCPsBySelection(newMcpConfig.mcpServers, templateConfig.selectedMCPs);
269
+ if (templateConfig.selectedMCPs && cleanNewMcpConfig.mcpServers) {
270
+ cleanNewMcpConfig.mcpServers = filterMCPsBySelection(cleanNewMcpConfig.mcpServers, templateConfig.selectedMCPs);
136
271
  }
137
272
 
138
- // Merge MCP configurations
273
+ // Merge MCP configurations (only keep mcpServers)
139
274
  const mergedMcpConfig = {
140
- ...existingMcpConfig,
141
- ...newMcpConfig,
142
275
  mcpServers: {
143
276
  ...existingMcpConfig.mcpServers,
144
- ...newMcpConfig.mcpServers
277
+ ...cleanNewMcpConfig.mcpServers
145
278
  }
146
279
  };
147
280
 
@@ -258,6 +391,12 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
258
391
  // Determine overwrite behavior based on user choice
259
392
  const shouldOverwrite = userAction !== 'merge';
260
393
 
394
+ // Track success/failure statistics
395
+ let totalFiles = templateConfig.files.length;
396
+ let successfulFiles = 0;
397
+ let skippedFiles = 0;
398
+ let failedFiles = 0;
399
+
261
400
  // Copy base files and framework-specific files
262
401
  for (const file of templateConfig.files) {
263
402
  const destPath = path.join(targetDir, file.destination);
@@ -269,20 +408,35 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
269
408
  await fs.ensureDir(destPath);
270
409
 
271
410
  // 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);
411
+ try {
412
+ const frameworkFiles = await downloadDirectoryFromGitHub(file.source);
413
+ let filesWritten = 0;
275
414
 
276
- // In merge mode, skip if file already exists
277
- if (userAction === 'merge' && await fs.pathExists(destFile)) {
278
- console.log(chalk.blue(`⏭️ Skipped ${frameworkFileName} (already exists)`));
279
- continue;
415
+ for (const [frameworkFileName, content] of Object.entries(frameworkFiles)) {
416
+ const destFile = path.join(destPath, frameworkFileName);
417
+
418
+ // In merge mode, skip if file already exists
419
+ if (userAction === 'merge' && await fs.pathExists(destFile)) {
420
+ console.log(chalk.blue(`⏭️ Skipped ${frameworkFileName} (already exists)`));
421
+ continue;
422
+ }
423
+
424
+ await fs.writeFile(destFile, content, 'utf8');
425
+ filesWritten++;
280
426
  }
281
427
 
282
- await fs.writeFile(destFile, content, 'utf8');
428
+ if (filesWritten > 0) {
429
+ console.log(chalk.green(`✓ Downloaded ${filesWritten} framework commands ${file.source} → ${file.destination}`));
430
+ successfulFiles++;
431
+ } else {
432
+ console.log(chalk.yellow(`⚠️ No framework commands available for ${file.source}`));
433
+ skippedFiles++;
434
+ }
435
+ } catch (error) {
436
+ console.log(chalk.yellow(`⚠️ Could not download framework commands from ${file.source}: ${error.message}`));
437
+ console.log(chalk.yellow(` This is normal for some templates - continuing...`));
438
+ failedFiles++;
283
439
  }
284
-
285
- console.log(chalk.green(`✓ Downloaded framework commands ${file.source} → ${file.destination}`));
286
440
  } else if (file.source.includes('.claude') && !file.source.includes('examples/')) {
287
441
  // This is base .claude directory - download it but handle commands specially
288
442
  await fs.ensureDir(destPath);
@@ -335,11 +489,15 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
335
489
  }
336
490
 
337
491
  } catch (error) {
338
- console.error(chalk.red(`❌ Error downloading .claude directory: ${error.message}`));
339
- throw error;
492
+ console.log(chalk.yellow(`⚠️ Could not download .claude directory (${error.message})`));
493
+ console.log(chalk.yellow(` Continuing with other template files...`));
494
+ failedFiles++;
495
+ // Don't throw - continue with other files
496
+ continue; // Skip the success message
340
497
  }
341
498
 
342
499
  console.log(chalk.green(`✓ Downloaded base configuration and commands ${file.source} → ${file.destination}`));
500
+ successfulFiles++;
343
501
  } else if (file.source.includes('settings.json') && templateConfig.selectedHooks) {
344
502
  // Download and process settings.json with hooks
345
503
  const settingsContent = await downloadFileFromGitHub(file.source);
@@ -352,6 +510,7 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
352
510
  await processSettingsFileFromContent(settingsContent, destPath, templateConfig);
353
511
  console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected hooks)`));
354
512
  }
513
+ successfulFiles++;
355
514
  } else if (file.source.includes('.mcp.json') && templateConfig.selectedMCPs) {
356
515
  // Download and process MCP config with selected MCPs
357
516
  const mcpContent = await downloadFileFromGitHub(file.source);
@@ -364,28 +523,72 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
364
523
  await processMCPFileFromContent(mcpContent, destPath, templateConfig);
365
524
  console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected MCPs)`));
366
525
  }
526
+ successfulFiles++;
367
527
  } else {
368
528
  // Download regular files (CLAUDE.md, etc.)
369
529
  // In merge mode, skip if file already exists
370
530
  if (userAction === 'merge' && await fs.pathExists(destPath)) {
371
531
  console.log(chalk.blue(`⏭️ Skipped ${file.destination} (already exists)`));
532
+ skippedFiles++;
372
533
  continue;
373
534
  }
374
535
 
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}`));
536
+ try {
537
+ const fileContent = await downloadFileFromGitHub(file.source);
538
+ const destDir = path.dirname(destPath);
539
+ await fs.ensureDir(destDir);
540
+ await fs.writeFile(destPath, fileContent, 'utf8');
541
+ console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination}`));
542
+ successfulFiles++;
543
+ } catch (error) {
544
+ if (error.message.includes('404')) {
545
+ console.log(chalk.yellow(`⚠️ File ${file.source} not found (404) - this is normal for some templates`));
546
+ skippedFiles++;
547
+ } else {
548
+ console.log(chalk.yellow(`⚠️ Could not download ${file.source}: ${error.message}`));
549
+ console.log(chalk.yellow(` Continuing with other template files...`));
550
+ failedFiles++;
551
+ }
552
+ }
380
553
  }
381
554
  } catch (error) {
382
- console.error(chalk.red(`✗ Failed to copy ${file.source}:`), error.message);
383
- throw error;
555
+ // Only throw for critical errors that should stop the entire process
556
+ if (error.message.includes('EACCES') || error.message.includes('permission denied')) {
557
+ console.error(chalk.red(`✗ Permission error copying ${file.source}:`), error.message);
558
+ throw error;
559
+ } else {
560
+ console.log(chalk.yellow(`⚠️ Could not process ${file.source}: ${error.message}`));
561
+ console.log(chalk.yellow(` Skipping this file and continuing...`));
562
+ failedFiles++;
563
+ }
564
+ }
565
+ }
566
+
567
+ // Show download summary
568
+ console.log(chalk.cyan('\n📦 Template Installation Summary:'));
569
+ if (successfulFiles > 0) {
570
+ console.log(chalk.green(` ✓ ${successfulFiles} files downloaded successfully`));
571
+ }
572
+ if (skippedFiles > 0) {
573
+ console.log(chalk.blue(` ⏭️ ${skippedFiles} files skipped (already exist or not needed)`));
574
+ }
575
+ if (failedFiles > 0) {
576
+ console.log(chalk.yellow(` ⚠️ ${failedFiles} files failed to download (continuing anyway)`));
577
+ }
578
+
579
+ console.log(chalk.gray(`\n📚 Source: https://github.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/tree/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.templatesPath}`));
580
+
581
+ // Consider it successful if we got at least some files
582
+ const hasEssentialFiles = successfulFiles > 0;
583
+ if (hasEssentialFiles) {
584
+ console.log(chalk.green('\n✅ Template installation completed successfully!'));
585
+ if (failedFiles > 0) {
586
+ console.log(chalk.yellow(' Some optional files were skipped due to rate limits or missing files.'));
587
+ console.log(chalk.yellow(' This is normal and your Claude Code configuration should work properly.'));
384
588
  }
385
589
  }
386
590
 
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}`));
388
- return true; // Indicate successful completion
591
+ return hasEssentialFiles;
389
592
  }
390
593
 
391
594
  async function runPostInstallationValidation(targetDir, templateConfig) {