claude-code-templates 1.14.12 → 1.14.13
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/bin/create-claude-config.js +1 -0
- package/package.json +1 -1
- package/src/file-operations.js +239 -36
- package/src/index.js +347 -9
- package/templates/python/examples/django-app/agents/django-api-security.md +0 -642
- package/templates/python/examples/django-app/agents/django-database-optimization.md +0 -752
|
@@ -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.
|
|
3
|
+
"version": "1.14.13",
|
|
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": {
|
package/src/file-operations.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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 &&
|
|
117
|
-
|
|
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,
|
|
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 &&
|
|
135
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
411
|
+
try {
|
|
412
|
+
const frameworkFiles = await downloadDirectoryFromGitHub(file.source);
|
|
413
|
+
let filesWritten = 0;
|
|
275
414
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
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.
|
|
339
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
383
|
-
|
|
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
|
-
|
|
388
|
-
return true; // Indicate successful completion
|
|
591
|
+
return hasEssentialFiles;
|
|
389
592
|
}
|
|
390
593
|
|
|
391
594
|
async function runPostInstallationValidation(targetDir, templateConfig) {
|