claude-code-templates 1.11.0 → 1.12.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.
@@ -5,32 +5,39 @@ const chalk = require('chalk');
5
5
  const boxen = require('boxen');
6
6
  const { createClaudeConfig } = require('../src/index');
7
7
 
8
- // ASCII Art for Claude Code Templates
9
- const banner = chalk.hex('#D97706')(`
10
- ██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗
11
- ██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝
12
- ██║ ██║ ███████║██║ ██║██║ ██║█████╗
13
- ██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝
14
- ╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗
15
- ╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
16
-
17
- ██████╗ ██████╗ ██████╗ ███████╗
18
- ██╔════╝██╔═══██╗██╔══██╗██╔════╝
19
- ██║ ██║ ██║██║ ██║█████╗
20
- ██║ ██║ ██║██║ ██║██╔══╝
21
- ╚██████╗╚██████╔╝██████╔╝███████╗
22
- ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
23
-
24
- ████████╗███████╗███╗ ███╗██████╗ ██╗ █████╗ ████████╗███████╗███████╗
25
- ╚══██╔══╝██╔════╝████╗ ████║██╔══██╗██║ ██╔══██╗╚══██╔══╝██╔════╝██╔════╝
26
- ██║ █████╗ ██╔████╔██║██████╔╝██║ ███████║ ██║ █████╗ ███████╗
27
- ██║ ██╔══╝ ██║╚██╔╝██║██╔═══╝ ██║ ██╔══██║ ██║ ██╔══╝ ╚════██║
28
- ██║ ███████╗██║ ╚═╝ ██║██║ ███████╗██║ ██║ ██║ ███████╗███████║
29
- ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝
30
- `) +
31
- chalk.yellow('\n 🚀 Setup Claude Code for any project language 🚀');
32
-
33
- console.log(banner);
8
+ const pkg = require('../package.json');
9
+
10
+ const title = 'Claude Code Templates';
11
+ const subtitle = 'Your starting point for Claude Code projects';
12
+
13
+ const colorGradient = ['#EA580C', '#F97316', '#FB923C', '#FDBA74', '#FED7AA', '#FFEBD6'];
14
+
15
+ function colorizeTitle(text) {
16
+ const chars = text.split('');
17
+ const steps = colorGradient.length;
18
+ return chars
19
+ .map((char, i) => {
20
+ const color = colorGradient[i % steps];
21
+ return chalk.hex(color)(char);
22
+ })
23
+ .join('');
24
+ }
25
+
26
+ console.clear();
27
+ console.log(chalk.hex('#F97316')('════════════════════════════════════════════════════════════════'));
28
+ console.log('\n');
29
+ console.log(' 🔮 ' + colorizeTitle(title));
30
+ console.log('\n');
31
+ console.log(' ' + chalk.hex('#FDBA74')(subtitle));
32
+ console.log('\n');
33
+ console.log(chalk.hex('#F97316')('════════════════════════════════════════════════════════════════\n'));
34
+
35
+ console.log(
36
+ chalk.hex('#D97706')('🚀 Setup Claude Code for any project language 🚀') +
37
+ chalk.gray(`\n v${pkg.version}\n\n`) +
38
+ chalk.blue('🌐 Templates: ') + chalk.underline('https://davila7.github.io/claude-code-templates/') + '\n' +
39
+ chalk.blue('📖 Documentation: ') + chalk.underline('https://davila7.github.io/claude-code-templates/docu/')
40
+ );
34
41
 
35
42
  program
36
43
  .name('create-claude-config')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
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": {
@@ -31,7 +31,6 @@
31
31
  "dev:link": "npm link",
32
32
  "dev:unlink": "npm unlink -g claude-code-templates",
33
33
  "pretest:commands": "npm run dev:link",
34
- "prepublishOnly": "echo 'Skipping tests for emergency publish'",
35
34
  "analytics:start": "node src/analytics.js",
36
35
  "analytics:test": "npm run test:analytics"
37
36
  },
package/src/agents.js ADDED
@@ -0,0 +1,262 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
6
+
7
+ /**
8
+ * Get all available agents from the templates directory structure
9
+ * @returns {Array} Array of agent objects with name, description, and color
10
+ */
11
+ function getAvailableAgents() {
12
+ const agents = [];
13
+
14
+ try {
15
+ // Scan all language directories
16
+ const languageDirs = fs.readdirSync(TEMPLATES_DIR)
17
+ .filter(dir => {
18
+ const dirPath = path.join(TEMPLATES_DIR, dir);
19
+ return fs.statSync(dirPath).isDirectory() && dir !== 'common';
20
+ });
21
+
22
+ for (const langDir of languageDirs) {
23
+ const frameworksPath = path.join(TEMPLATES_DIR, langDir, 'examples');
24
+
25
+ if (fs.existsSync(frameworksPath)) {
26
+ const frameworks = fs.readdirSync(frameworksPath)
27
+ .filter(dir => {
28
+ const dirPath = path.join(frameworksPath, dir);
29
+ return fs.statSync(dirPath).isDirectory();
30
+ });
31
+
32
+ for (const framework of frameworks) {
33
+ const agentsPath = path.join(frameworksPath, framework, 'agents');
34
+
35
+ if (fs.existsSync(agentsPath)) {
36
+ const agentFiles = fs.readdirSync(agentsPath)
37
+ .filter(file => file.endsWith('.md'));
38
+
39
+ for (const file of agentFiles) {
40
+ const filePath = path.join(agentsPath, file);
41
+ const content = fs.readFileSync(filePath, 'utf8');
42
+ const agent = parseAgentFile(content, file);
43
+
44
+ if (agent) {
45
+ agent.language = langDir;
46
+ agent.framework = framework;
47
+ agent.filePath = filePath;
48
+ agents.push(agent);
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ return agents;
57
+ } catch (error) {
58
+ console.log(chalk.yellow('⚠️ No agents templates found'));
59
+ return [];
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Parse agent markdown file to extract frontmatter
65
+ * @param {string} content - File content
66
+ * @param {string} filename - File name
67
+ * @returns {Object|null} Agent object or null if parsing fails
68
+ */
69
+ function parseAgentFile(content, filename) {
70
+ try {
71
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
72
+ if (!frontmatterMatch) {
73
+ return null;
74
+ }
75
+
76
+ const frontmatter = frontmatterMatch[1];
77
+ const lines = frontmatter.split('\n');
78
+ const agent = { filename };
79
+
80
+ for (const line of lines) {
81
+ const [key, ...valueParts] = line.split(':');
82
+ if (key && valueParts.length > 0) {
83
+ const value = valueParts.join(':').trim();
84
+ agent[key.trim()] = value;
85
+ }
86
+ }
87
+
88
+ // Extract description without examples for display
89
+ if (agent.description) {
90
+ const shortDesc = agent.description.split('Examples:')[0].trim();
91
+ agent.shortDescription = shortDesc;
92
+ }
93
+
94
+ return agent;
95
+ } catch (error) {
96
+ console.log(chalk.yellow(`⚠️ Failed to parse agent file: ${filename}`));
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Get agents that are relevant for a specific language/framework
103
+ * @param {string} language - Programming language
104
+ * @param {string} framework - Framework (optional)
105
+ * @returns {Array} Array of relevant agents
106
+ */
107
+ function getAgentsForLanguageAndFramework(language, framework) {
108
+ const allAgents = getAvailableAgents();
109
+
110
+ // Filter agents based on language and framework
111
+ return allAgents.filter(agent => {
112
+ // First check exact language match
113
+ if (agent.language !== language) {
114
+ return false;
115
+ }
116
+
117
+ // If framework is specified and not 'none', check framework match
118
+ if (framework && framework !== 'none') {
119
+ // Extract framework name from framework path (e.g., 'react-app' -> 'react')
120
+ const frameworkName = framework.includes('-') ? framework.split('-')[0] : framework;
121
+ const agentFrameworkName = agent.framework.includes('-') ? agent.framework.split('-')[0] : agent.framework;
122
+
123
+ return agentFrameworkName.toLowerCase().includes(frameworkName.toLowerCase());
124
+ }
125
+
126
+ // If no specific framework, return all agents for this language
127
+ return true;
128
+ });
129
+ }
130
+
131
+ /**
132
+ * Install selected agents to the project's .claude/agents directory
133
+ * @param {Array} selectedAgents - Array of agent names to install
134
+ * @param {string} projectPath - Project directory path
135
+ * @returns {Promise<boolean>} Success status
136
+ */
137
+ async function installAgents(selectedAgents, projectPath = process.cwd()) {
138
+ try {
139
+ const claudeDir = path.join(projectPath, '.claude');
140
+ const agentsDir = path.join(claudeDir, 'agents');
141
+
142
+ // Create .claude/agents directory if it doesn't exist
143
+ await fs.ensureDir(agentsDir);
144
+
145
+ let installedCount = 0;
146
+ const allAgents = getAvailableAgents();
147
+
148
+ for (const agentName of selectedAgents) {
149
+ // Find the agent by name in the available agents
150
+ const agent = allAgents.find(a => a.name === agentName);
151
+
152
+ if (agent && agent.filePath) {
153
+ const targetFile = path.join(agentsDir, `${agentName}.md`);
154
+
155
+ if (await fs.pathExists(agent.filePath)) {
156
+ await fs.copy(agent.filePath, targetFile);
157
+ installedCount++;
158
+ console.log(chalk.green(`✓ Installed agent: ${agentName} (${agent.language}/${agent.framework})`));
159
+ } else {
160
+ console.log(chalk.yellow(`⚠️ Agent source file not found: ${agent.filePath}`));
161
+ }
162
+ } else {
163
+ console.log(chalk.yellow(`⚠️ Agent not found: ${agentName}`));
164
+ }
165
+ }
166
+
167
+ if (installedCount > 0) {
168
+ console.log(chalk.green(`\n🎉 Successfully installed ${installedCount} agent(s) to .claude/agents/`));
169
+ console.log(chalk.blue(' You can now use these agents in your Claude Code conversations!'));
170
+ return true;
171
+ } else {
172
+ console.log(chalk.yellow('⚠️ No agents were installed'));
173
+ return false;
174
+ }
175
+
176
+ } catch (error) {
177
+ console.error(chalk.red('❌ Failed to install agents:'), error.message);
178
+ return false;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Check if project already has agents installed
184
+ * @param {string} projectPath - Project directory path
185
+ * @returns {Promise<Array>} Array of installed agent names
186
+ */
187
+ async function getInstalledAgents(projectPath = process.cwd()) {
188
+ try {
189
+ const agentsDir = path.join(projectPath, '.claude', 'agents');
190
+
191
+ if (!(await fs.pathExists(agentsDir))) {
192
+ return [];
193
+ }
194
+
195
+ const agentFiles = await fs.readdir(agentsDir);
196
+ return agentFiles
197
+ .filter(file => file.endsWith('.md'))
198
+ .map(file => path.basename(file, '.md'));
199
+
200
+ } catch (error) {
201
+ return [];
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Format agent choices for inquirer prompt
207
+ * @param {Array} agents - Array of agent objects
208
+ * @param {Array} installedAgents - Array of already installed agent names
209
+ * @returns {Array} Formatted choices for inquirer
210
+ */
211
+ function formatAgentChoices(agents, installedAgents = []) {
212
+ return agents.map(agent => {
213
+ const isInstalled = installedAgents.includes(agent.name);
214
+ const colorFn = getColorFunction(agent.color);
215
+
216
+ const name = isInstalled
217
+ ? `${colorFn(agent.name)} ${chalk.dim('(already installed)')}`
218
+ : colorFn(agent.name);
219
+
220
+ const description = agent.shortDescription || agent.description || 'No description available';
221
+ // Truncate description if too long
222
+ const truncatedDesc = description.length > 80
223
+ ? description.substring(0, 80) + '...'
224
+ : description;
225
+
226
+ return {
227
+ name: `${name}\n ${chalk.dim(truncatedDesc)}`,
228
+ value: agent.name,
229
+ short: agent.name,
230
+ disabled: isInstalled ? 'Already installed' : false
231
+ };
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Get chalk color function based on color name
237
+ * @param {string} colorName - Color name
238
+ * @returns {Function} Chalk color function
239
+ */
240
+ function getColorFunction(colorName) {
241
+ const colorMap = {
242
+ red: chalk.red,
243
+ green: chalk.green,
244
+ yellow: chalk.yellow,
245
+ blue: chalk.blue,
246
+ magenta: chalk.magenta,
247
+ cyan: chalk.cyan,
248
+ white: chalk.white,
249
+ gray: chalk.gray,
250
+ grey: chalk.gray
251
+ };
252
+
253
+ return colorMap[colorName] || chalk.white;
254
+ }
255
+
256
+ module.exports = {
257
+ getAvailableAgents,
258
+ getAgentsForLanguageAndFramework,
259
+ installAgents,
260
+ getInstalledAgents,
261
+ formatAgentChoices
262
+ };
@@ -101,7 +101,7 @@ class ConversationAnalyzer {
101
101
 
102
102
  try {
103
103
  // Extract project name from path
104
- const projectFromPath = this.extractProjectFromPath(filePath);
104
+ const projectFromPath = await this.extractProjectFromPath(filePath);
105
105
 
106
106
  // Use cached parsed conversation if available
107
107
  const parsedMessages = await this.getParsedConversation(filePath);
@@ -113,6 +113,9 @@ class ConversationAnalyzer {
113
113
  // Calculate tool usage data with caching
114
114
  const toolUsage = await this.getCachedToolUsage(filePath, parsedMessages);
115
115
 
116
+ const projectFromConversation = await this.extractProjectFromConversation(filePath);
117
+ const finalProject = projectFromConversation || projectFromPath;
118
+
116
119
  const conversation = {
117
120
  id: filename.replace('.jsonl', ''),
118
121
  filename: filename,
@@ -125,7 +128,7 @@ class ConversationAnalyzer {
125
128
  tokenUsage: tokenUsage,
126
129
  modelInfo: modelInfo,
127
130
  toolUsage: toolUsage,
128
- project: projectFromPath || this.extractProjectFromConversation(parsedMessages),
131
+ project: finalProject,
129
132
  status: stateCalculator.determineConversationStatus(parsedMessages, stats.mtime),
130
133
  conversationState: stateCalculator.determineConversationState(parsedMessages, stats.mtime),
131
134
  statusSquares: await this.getCachedStatusSquares(filePath, parsedMessages),
@@ -433,11 +436,11 @@ class ConversationAnalyzer {
433
436
  }
434
437
 
435
438
  /**
436
- * Extract project name from Claude directory file path
439
+ * Extract project name from Claude directory file path using settings.json
437
440
  * @param {string} filePath - Full path to conversation file
438
- * @returns {string|null} Project name or null
441
+ * @returns {Promise<string|null>} Project name or null
439
442
  */
440
- extractProjectFromPath(filePath) {
443
+ async extractProjectFromPath(filePath) {
441
444
  // Extract project name from file path like:
442
445
  // /Users/user/.claude/projects/-Users-user-Projects-MyProject/conversation.jsonl
443
446
  const pathParts = filePath.split('/');
@@ -445,34 +448,73 @@ class ConversationAnalyzer {
445
448
 
446
449
  if (projectIndex !== -1 && projectIndex + 1 < pathParts.length) {
447
450
  const projectDir = pathParts[projectIndex + 1];
448
- // Clean up the project directory name
449
- const cleanName = projectDir
450
- .replace(/^-/, '')
451
- .replace(/-/g, '/')
452
- .split('/')
453
- .pop() || 'Unknown';
454
-
455
- return cleanName;
451
+
452
+ // Try to read the settings.json file for this project
453
+ try {
454
+ const projectPath = path.join(path.dirname(filePath)); // Directory containing the conversation file
455
+ const settingsPath = path.join(projectPath, 'settings.json');
456
+
457
+ if (await fs.pathExists(settingsPath)) {
458
+ const settingsContent = await fs.readFile(settingsPath, 'utf8');
459
+ const settings = JSON.parse(settingsContent);
460
+
461
+ if (settings.projectName) {
462
+ return settings.projectName;
463
+ }
464
+
465
+ // If no projectName in settings, try to extract from projectPath
466
+ if (settings.projectPath) {
467
+ return path.basename(settings.projectPath);
468
+ }
469
+ }
470
+ } catch (error) {
471
+ // If we can't read settings.json, fall back to parsing the directory name
472
+ console.warn(chalk.yellow(`Warning: Could not read settings.json for project ${projectDir}:`, error.message));
473
+ }
474
+
475
+ // Fallback: we'll extract project name from conversation content instead
476
+ // For now, return null to trigger reading from conversation file
477
+ return null;
456
478
  }
457
479
 
458
480
  return null;
459
481
  }
460
482
 
483
+
461
484
  /**
462
485
  * Attempt to extract project information from conversation content
463
- * @param {Array} messages - Array of message objects
464
- * @returns {string} Project name or 'Unknown'
465
- */
466
- extractProjectFromConversation(messages) {
467
- // Try to extract project information from conversation
468
- for (const message of messages.slice(0, 5)) {
469
- if (message.content && typeof message.content === 'string') {
470
- const pathMatch = message.content.match(/\/([^\/\s]+)$/);
471
- if (pathMatch) {
472
- return pathMatch[1];
486
+ * @param {string} filePath - Path to the conversation file
487
+ * @returns {Promise<string>} Project name or 'Unknown'
488
+ */
489
+ async extractProjectFromConversation(filePath) {
490
+ try {
491
+ // Read the conversation file and look for cwd field
492
+ const content = await this.getFileContent(filePath);
493
+ const lines = content.trim().split('\n').filter(line => line.trim());
494
+
495
+ for (const line of lines.slice(0, 10)) { // Check first 10 lines
496
+ try {
497
+ const item = JSON.parse(line);
498
+
499
+ // Look for cwd field in the message
500
+ if (item.cwd) {
501
+ const projectName = path.basename(item.cwd);
502
+ return projectName;
503
+ }
504
+
505
+ // Also check if it's in nested objects
506
+ if (item.message && item.message.cwd) {
507
+ return path.basename(item.message.cwd);
508
+ }
509
+ } catch (parseError) {
510
+ // Skip invalid JSON lines
511
+ continue;
473
512
  }
474
513
  }
514
+ } catch (error) {
515
+ console.warn(chalk.yellow(`Warning: Could not extract project from conversation ${filePath}:`, error.message));
475
516
  }
517
+
476
518
  return 'Unknown';
477
519
  }
478
520