claude-code-templates 1.15.0 → 1.15.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 (101) hide show
  1. package/README.md +7 -7
  2. package/bin/create-claude-config.js +15 -8
  3. package/package.json +2 -3
  4. package/src/analytics/core/AgentAnalyzer.js +17 -3
  5. package/src/analytics/core/ProcessDetector.js +23 -7
  6. package/src/analytics/core/StateCalculator.js +102 -33
  7. package/src/analytics/data/DataCache.js +7 -7
  8. package/src/analytics-web/chats_mobile.html +2590 -0
  9. package/src/analytics-web/components/App.js +10 -10
  10. package/src/analytics-web/components/SessionTimer.js +1 -1
  11. package/src/analytics-web/components/Sidebar.js +5 -14
  12. package/src/analytics-web/index.html +932 -78
  13. package/src/analytics.js +263 -5
  14. package/src/chats-mobile.js +682 -0
  15. package/src/claude-api-proxy.js +460 -0
  16. package/src/file-operations.js +422 -83
  17. package/src/health-check.js +310 -0
  18. package/src/index.js +944 -56
  19. package/src/tracking-service.js +31 -34
  20. package/components/agents/api-security-audit.md +0 -92
  21. package/components/agents/database-optimization.md +0 -94
  22. package/components/agents/react-performance-optimization.md +0 -64
  23. package/components/commands/check-file.md +0 -53
  24. package/components/commands/generate-tests.md +0 -68
  25. package/components/mcps/deepgraph-nextjs.json +0 -12
  26. package/components/mcps/deepgraph-react.json +0 -12
  27. package/components/mcps/deepgraph-typescript.json +0 -12
  28. package/components/mcps/deepgraph-vue.json +0 -12
  29. package/components/mcps/filesystem-access.json +0 -12
  30. package/components/mcps/github-integration.json +0 -11
  31. package/components/mcps/memory-integration.json +0 -8
  32. package/components/mcps/mysql-integration.json +0 -11
  33. package/components/mcps/postgresql-integration.json +0 -11
  34. package/components/mcps/web-fetch.json +0 -8
  35. package/src/analytics-web/components/AgentsPage.js +0 -4761
  36. package/templates/common/.claude/commands/git-workflow.md +0 -239
  37. package/templates/common/.claude/commands/project-setup.md +0 -316
  38. package/templates/common/.mcp.json +0 -41
  39. package/templates/common/CLAUDE.md +0 -109
  40. package/templates/common/README.md +0 -96
  41. package/templates/go/.mcp.json +0 -78
  42. package/templates/go/README.md +0 -25
  43. package/templates/javascript-typescript/.claude/commands/api-endpoint.md +0 -51
  44. package/templates/javascript-typescript/.claude/commands/debug.md +0 -52
  45. package/templates/javascript-typescript/.claude/commands/lint.md +0 -48
  46. package/templates/javascript-typescript/.claude/commands/npm-scripts.md +0 -48
  47. package/templates/javascript-typescript/.claude/commands/refactor.md +0 -55
  48. package/templates/javascript-typescript/.claude/commands/test.md +0 -61
  49. package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +0 -51
  50. package/templates/javascript-typescript/.claude/settings.json +0 -142
  51. package/templates/javascript-typescript/.mcp.json +0 -80
  52. package/templates/javascript-typescript/CLAUDE.md +0 -185
  53. package/templates/javascript-typescript/README.md +0 -259
  54. package/templates/javascript-typescript/examples/angular-app/.claude/commands/components.md +0 -63
  55. package/templates/javascript-typescript/examples/angular-app/.claude/commands/services.md +0 -62
  56. package/templates/javascript-typescript/examples/node-api/.claude/commands/api-endpoint.md +0 -46
  57. package/templates/javascript-typescript/examples/node-api/.claude/commands/database.md +0 -56
  58. package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -61
  59. package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -57
  60. package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -102
  61. package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -29
  62. package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -44
  63. package/templates/javascript-typescript/examples/react-app/.claude/commands/state-management.md +0 -45
  64. package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -81
  65. package/templates/javascript-typescript/examples/react-app/agents/react-performance-optimization.md +0 -530
  66. package/templates/javascript-typescript/examples/react-app/agents/react-state-management.md +0 -295
  67. package/templates/javascript-typescript/examples/vue-app/.claude/commands/components.md +0 -46
  68. package/templates/javascript-typescript/examples/vue-app/.claude/commands/composables.md +0 -51
  69. package/templates/python/.claude/commands/lint.md +0 -111
  70. package/templates/python/.claude/commands/test.md +0 -73
  71. package/templates/python/.claude/settings.json +0 -153
  72. package/templates/python/.mcp.json +0 -78
  73. package/templates/python/CLAUDE.md +0 -276
  74. package/templates/python/examples/django-app/.claude/commands/admin.md +0 -264
  75. package/templates/python/examples/django-app/.claude/commands/django-model.md +0 -124
  76. package/templates/python/examples/django-app/.claude/commands/views.md +0 -222
  77. package/templates/python/examples/django-app/CLAUDE.md +0 -313
  78. package/templates/python/examples/django-app/agents/django-api-security.md +0 -642
  79. package/templates/python/examples/django-app/agents/django-database-optimization.md +0 -752
  80. package/templates/python/examples/fastapi-app/.claude/commands/api-endpoints.md +0 -513
  81. package/templates/python/examples/fastapi-app/.claude/commands/auth.md +0 -775
  82. package/templates/python/examples/fastapi-app/.claude/commands/database.md +0 -657
  83. package/templates/python/examples/fastapi-app/.claude/commands/deployment.md +0 -160
  84. package/templates/python/examples/fastapi-app/.claude/commands/testing.md +0 -927
  85. package/templates/python/examples/fastapi-app/CLAUDE.md +0 -229
  86. package/templates/python/examples/flask-app/.claude/commands/app-factory.md +0 -384
  87. package/templates/python/examples/flask-app/.claude/commands/blueprint.md +0 -243
  88. package/templates/python/examples/flask-app/.claude/commands/database.md +0 -410
  89. package/templates/python/examples/flask-app/.claude/commands/deployment.md +0 -620
  90. package/templates/python/examples/flask-app/.claude/commands/flask-route.md +0 -217
  91. package/templates/python/examples/flask-app/.claude/commands/testing.md +0 -559
  92. package/templates/python/examples/flask-app/CLAUDE.md +0 -391
  93. package/templates/ruby/.claude/commands/model.md +0 -360
  94. package/templates/ruby/.claude/commands/test.md +0 -480
  95. package/templates/ruby/.claude/settings.json +0 -146
  96. package/templates/ruby/.mcp.json +0 -83
  97. package/templates/ruby/CLAUDE.md +0 -284
  98. package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +0 -490
  99. package/templates/ruby/examples/rails-app/CLAUDE.md +0 -376
  100. package/templates/rust/.mcp.json +0 -78
  101. package/templates/rust/README.md +0 -26
@@ -4,6 +4,285 @@ 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, retryCount = 0) {
19
+ // Check cache first
20
+ if (downloadCache.has(filePath)) {
21
+ return downloadCache.get(filePath);
22
+ }
23
+
24
+ const maxRetries = 3;
25
+ const baseDelay = 1000;
26
+ const retryDelay = baseDelay * Math.pow(2, retryCount); // Exponential backoff: 1s, 2s, 4s
27
+ const githubUrl = `https://raw.githubusercontent.com/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/${GITHUB_CONFIG.branch}/${GITHUB_CONFIG.templatesPath}/${filePath}`;
28
+
29
+ try {
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
+
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
+
55
+ throw new Error(`Failed to download ${filePath}: ${response.status} ${response.statusText}`);
56
+ }
57
+
58
+ const content = await response.text();
59
+ downloadCache.set(filePath, content);
60
+ return content;
61
+ } catch (error) {
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
70
+ throw error;
71
+ }
72
+ }
73
+
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
+
79
+ // For directories, we need to get the list of files first
80
+ // GitHub API endpoint to get directory contents
81
+ const apiUrl = `https://api.github.com/repos/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/contents/${GITHUB_CONFIG.templatesPath}/${dirPath}?ref=${GITHUB_CONFIG.branch}`;
82
+
83
+ try {
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
+
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
+
134
+ throw new Error(`Failed to get directory listing for ${dirPath}: ${response.status} ${response.statusText}`);
135
+ }
136
+
137
+ const items = await response.json();
138
+ const files = {};
139
+ let successCount = 0;
140
+ let skipCount = 0;
141
+
142
+ for (const item of items) {
143
+ if (item.type === 'file') {
144
+ const relativePath = path.relative(GITHUB_CONFIG.templatesPath, item.path);
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
+ }
158
+ }
159
+ }
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
+
167
+ return files;
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
+
175
+ console.error(chalk.red(`❌ Error downloading directory ${dirPath} from GitHub:`), error.message);
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
178
+ }
179
+ }
180
+
181
+ // Helper functions for processing downloaded content
182
+ async function processSettingsFileFromContent(settingsContent, destPath, templateConfig) {
183
+ const settings = JSON.parse(settingsContent);
184
+
185
+ // Filter hooks based on selection
186
+ if (templateConfig.selectedHooks && settings.hooks) {
187
+ settings.hooks = filterHooksBySelection(settings.hooks, templateConfig.selectedHooks);
188
+ }
189
+
190
+ const destDir = path.dirname(destPath);
191
+ await fs.ensureDir(destDir);
192
+ await fs.writeJson(destPath, settings, { spaces: 2 });
193
+ }
194
+
195
+ async function mergeSettingsFileFromContent(settingsContent, destPath, templateConfig) {
196
+ const newSettings = JSON.parse(settingsContent);
197
+ let existingSettings = {};
198
+
199
+ if (await fs.pathExists(destPath)) {
200
+ existingSettings = await fs.readJson(destPath);
201
+ }
202
+
203
+ // Filter hooks based on selection
204
+ if (templateConfig.selectedHooks && newSettings.hooks) {
205
+ newSettings.hooks = filterHooksBySelection(newSettings.hooks, templateConfig.selectedHooks);
206
+ }
207
+
208
+ // Merge settings
209
+ const mergedSettings = {
210
+ ...existingSettings,
211
+ ...newSettings,
212
+ hooks: {
213
+ ...existingSettings.hooks,
214
+ ...newSettings.hooks
215
+ }
216
+ };
217
+
218
+ const destDir = path.dirname(destPath);
219
+ await fs.ensureDir(destDir);
220
+ await fs.writeJson(destPath, mergedSettings, { spaces: 2 });
221
+ }
222
+
223
+ async function processMCPFileFromContent(mcpContent, destPath, templateConfig) {
224
+ const mcpConfig = JSON.parse(mcpContent);
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
+
238
+ // Filter MCPs based on selection
239
+ if (templateConfig.selectedMCPs && cleanMcpConfig.mcpServers) {
240
+ cleanMcpConfig.mcpServers = filterMCPsBySelection(cleanMcpConfig.mcpServers, templateConfig.selectedMCPs);
241
+ }
242
+
243
+ const destDir = path.dirname(destPath);
244
+ await fs.ensureDir(destDir);
245
+ await fs.writeJson(destPath, cleanMcpConfig, { spaces: 2 });
246
+ }
247
+
248
+ async function mergeMCPFileFromContent(mcpContent, destPath, templateConfig) {
249
+ const newMcpConfig = JSON.parse(mcpContent);
250
+ let existingMcpConfig = {};
251
+
252
+ if (await fs.pathExists(destPath)) {
253
+ existingMcpConfig = await fs.readJson(destPath);
254
+ }
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
+
268
+ // Filter MCPs based on selection
269
+ if (templateConfig.selectedMCPs && cleanNewMcpConfig.mcpServers) {
270
+ cleanNewMcpConfig.mcpServers = filterMCPsBySelection(cleanNewMcpConfig.mcpServers, templateConfig.selectedMCPs);
271
+ }
272
+
273
+ // Merge MCP configurations (only keep mcpServers)
274
+ const mergedMcpConfig = {
275
+ mcpServers: {
276
+ ...existingMcpConfig.mcpServers,
277
+ ...cleanNewMcpConfig.mcpServers
278
+ }
279
+ };
280
+
281
+ const destDir = path.dirname(destPath);
282
+ await fs.ensureDir(destDir);
283
+ await fs.writeJson(destPath, mergedMcpConfig, { spaces: 2 });
284
+ }
285
+
7
286
  async function checkExistingFiles(targetDir, templateConfig) {
8
287
  const existingFiles = [];
9
288
 
@@ -86,7 +365,7 @@ async function createBackups(existingFiles, targetDir) {
86
365
  }
87
366
 
88
367
  async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
89
- const templateDir = path.join(__dirname, '../templates');
368
+ console.log(chalk.gray(`📥 Downloading templates from GitHub (${GITHUB_CONFIG.branch} branch)...`));
90
369
 
91
370
  // Check for existing files and get user preference
92
371
  const existingFiles = await checkExistingFiles(targetDir, templateConfig);
@@ -112,9 +391,14 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
112
391
  // Determine overwrite behavior based on user choice
113
392
  const shouldOverwrite = userAction !== 'merge';
114
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
+
115
400
  // Copy base files and framework-specific files
116
401
  for (const file of templateConfig.files) {
117
- const sourcePath = path.join(templateDir, file.source);
118
402
  const destPath = path.join(targetDir, file.destination);
119
403
 
120
404
  try {
@@ -123,133 +407,188 @@ async function copyTemplateFiles(templateConfig, targetDir, options = {}) {
123
407
  // This is a framework-specific commands directory - merge with existing commands
124
408
  await fs.ensureDir(destPath);
125
409
 
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);
410
+ // Download framework-specific commands from GitHub
411
+ try {
412
+ const frameworkFiles = await downloadDirectoryFromGitHub(file.source);
413
+ let filesWritten = 0;
131
414
 
132
- // In merge mode, skip if file already exists
133
- if (userAction === 'merge' && await fs.pathExists(destFile)) {
134
- console.log(chalk.blue(`⏭️ Skipped ${frameworkFile} (already exists)`));
135
- 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++;
136
426
  }
137
427
 
138
- await fs.copy(srcFile, destFile, { overwrite: shouldOverwrite });
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++;
139
439
  }
140
-
141
- console.log(chalk.green(`✓ Copied framework commands ${file.source} → ${file.destination}`));
142
440
  } 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');
441
+ // This is base .claude directory - download it but handle commands specially
442
+ await fs.ensureDir(destPath);
155
443
 
156
- if (await fs.pathExists(baseCommandsPath)) {
157
- await fs.ensureDir(destCommandsPath);
444
+ // Download base .claude directory structure from GitHub
445
+ try {
446
+ const baseClaudeFiles = await downloadDirectoryFromGitHub(file.source);
158
447
 
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
162
-
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);
448
+ // Write non-command files first
449
+ for (const [fileName, content] of Object.entries(baseClaudeFiles)) {
450
+ if (fileName !== 'commands') { // Skip commands directory, handle separately
451
+ const destFile = path.join(destPath, fileName);
167
452
 
168
453
  // In merge mode, skip if file already exists
169
454
  if (userAction === 'merge' && await fs.pathExists(destFile)) {
170
- console.log(chalk.blue(`⏭️ Skipped ${baseCommand} (already exists)`));
455
+ console.log(chalk.blue(`⏭️ Skipped ${fileName} (already exists)`));
171
456
  continue;
172
457
  }
173
458
 
174
- await fs.copy(srcFile, destFile, { overwrite: shouldOverwrite });
459
+ await fs.writeFile(destFile, content, 'utf8');
460
+ }
461
+ }
462
+
463
+ // Now handle base commands specifically
464
+ const destCommandsPath = path.join(destPath, 'commands');
465
+ await fs.ensureDir(destCommandsPath);
466
+
467
+ // Download base commands from GitHub
468
+ const baseCommandsDir = `${file.source}/commands`;
469
+ try {
470
+ const baseCommands = await downloadDirectoryFromGitHub(baseCommandsDir);
471
+ const excludeCommands = ['react-component.md', 'route.md', 'api-endpoint.md']; // Commands moved to framework dirs
472
+
473
+ for (const [baseCommandName, commandContent] of Object.entries(baseCommands)) {
474
+ if (!excludeCommands.includes(baseCommandName)) {
475
+ const destFile = path.join(destCommandsPath, baseCommandName);
476
+
477
+ // In merge mode, skip if file already exists
478
+ if (userAction === 'merge' && await fs.pathExists(destFile)) {
479
+ console.log(chalk.blue(`⏭️ Skipped ${baseCommandName} (already exists)`));
480
+ continue;
481
+ }
482
+
483
+ await fs.writeFile(destFile, commandContent, 'utf8');
484
+ }
175
485
  }
486
+ } catch (error) {
487
+ // Commands directory might not exist for some templates, that's ok
488
+ console.log(chalk.yellow(`⚠️ No commands directory found for ${baseCommandsDir}`));
176
489
  }
490
+
491
+ } catch (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
177
497
  }
178
498
 
179
- console.log(chalk.green(`✓ Copied base configuration and commands ${file.source} → ${file.destination}`));
499
+ console.log(chalk.green(`✓ Downloaded base configuration and commands ${file.source} → ${file.destination}`));
500
+ successfulFiles++;
180
501
  } else if (file.source.includes('settings.json') && templateConfig.selectedHooks) {
502
+ // Download and process settings.json with hooks
503
+ const settingsContent = await downloadFileFromGitHub(file.source);
504
+
181
505
  // In merge mode, merge settings instead of overwriting
182
506
  if (userAction === 'merge') {
183
- await mergeSettingsFile(sourcePath, destPath, templateConfig);
507
+ await mergeSettingsFileFromContent(settingsContent, destPath, templateConfig);
184
508
  console.log(chalk.green(`✓ Merged ${file.source} → ${file.destination} (with selected hooks)`));
185
509
  } else {
186
- await processSettingsFile(sourcePath, destPath, templateConfig);
187
- console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination} (with selected hooks)`));
510
+ await processSettingsFileFromContent(settingsContent, destPath, templateConfig);
511
+ console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected hooks)`));
188
512
  }
513
+ successfulFiles++;
189
514
  } else if (file.source.includes('.mcp.json') && templateConfig.selectedMCPs) {
515
+ // Download and process MCP config with selected MCPs
516
+ const mcpContent = await downloadFileFromGitHub(file.source);
517
+
190
518
  // In merge mode, merge MCP config instead of overwriting
191
519
  if (userAction === 'merge') {
192
- await mergeMCPFile(sourcePath, destPath, templateConfig);
520
+ await mergeMCPFileFromContent(mcpContent, destPath, templateConfig);
193
521
  console.log(chalk.green(`✓ Merged ${file.source} → ${file.destination} (with selected MCPs)`));
194
522
  } else {
195
- await processMCPFile(sourcePath, destPath, templateConfig);
196
- console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination} (with selected MCPs)`));
523
+ await processMCPFileFromContent(mcpContent, destPath, templateConfig);
524
+ console.log(chalk.green(`✓ Downloaded ${file.source} → ${file.destination} (with selected MCPs)`));
197
525
  }
526
+ successfulFiles++;
198
527
  } else {
199
- // Copy regular files (CLAUDE.md, etc.)
528
+ // Download regular files (CLAUDE.md, etc.)
200
529
  // In merge mode, skip if file already exists
201
530
  if (userAction === 'merge' && await fs.pathExists(destPath)) {
202
531
  console.log(chalk.blue(`⏭️ Skipped ${file.destination} (already exists)`));
532
+ skippedFiles++;
203
533
  continue;
204
534
  }
205
535
 
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');
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++;
211
551
  }
212
- });
213
- console.log(chalk.green(`✓ Copied ${file.source} → ${file.destination}`));
552
+ }
214
553
  }
215
554
  } catch (error) {
216
- console.error(chalk.red(`✗ Failed to copy ${file.source}:`), error.message);
217
- throw error;
218
- }
219
- }
220
-
221
- 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
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++;
238
563
  }
239
564
  }
240
-
241
- console.log(chalk.cyan(`📋 Installed ${templateConfig.selectedCommands.length} commands`));
242
565
  }
243
566
 
244
- // Report hook selection
245
- if (templateConfig.selectedHooks && templateConfig.selectedHooks.length > 0) {
246
- console.log(chalk.magenta(`🔧 Installed ${templateConfig.selectedHooks.length} automation hooks`));
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)`));
247
577
  }
248
578
 
249
- // Report MCP selection
250
- if (templateConfig.selectedMCPs && templateConfig.selectedMCPs.length > 0) {
251
- console.log(chalk.blue(`🔧 Installed ${templateConfig.selectedMCPs.length} MCP`));
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.'));
588
+ }
252
589
  }
590
+
591
+ return hasEssentialFiles;
253
592
  }
254
593
 
255
594
  async function runPostInstallationValidation(targetDir, templateConfig) {