Skip to content

Commit b0209b1

Browse files
davila7claude
andcommitted
feat: Implement individual component installation system
- Add --agent, --command, --mcp options to CLI - Implement installIndividualAgent, installIndividualCommand, installIndividualMCP functions - Dynamic component detection from cli-tool/components directory - Update web interface install commands to use new CLI options - Fix infinite recursion in agent installation - Proper error handling and component listing when not found Installation examples: - npx claude-code-templates@latest --agent=react-performance-optimization --yes - npx claude-code-templates@latest --command=generate-tests --yes - npx claude-code-templates@latest --mcp=postgresql-integration --yes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8426d7c commit b0209b1

File tree

3 files changed

+189
-34
lines changed

3 files changed

+189
-34
lines changed

cli-tool/bin/create-claude-config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ program
5454
.option('--analytics', 'launch real-time Claude Code analytics dashboard')
5555
.option('--chats, --agents', 'launch Claude Code chats/agents dashboard (opens directly to conversations)')
5656
.option('--health-check, --health, --check, --verify', 'run comprehensive health check to verify Claude Code setup')
57+
.option('--agent <agent>', 'install specific agent component')
58+
.option('--command <command>', 'install specific command component')
59+
.option('--mcp <mcp>', 'install specific MCP component')
5760
.action(async (options) => {
5861
try {
5962
await createClaudeConfig(options);

cli-tool/src/index.js

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ async function showMainMenu() {
8080
async function createClaudeConfig(options = {}) {
8181
const targetDir = options.directory || process.cwd();
8282

83+
// Handle individual component installation
84+
if (options.agent) {
85+
await installIndividualAgent(options.agent, targetDir, options);
86+
return;
87+
}
88+
89+
if (options.command) {
90+
await installIndividualCommand(options.command, targetDir, options);
91+
return;
92+
}
93+
94+
if (options.mcp) {
95+
await installIndividualMCP(options.mcp, targetDir, options);
96+
return;
97+
}
98+
8399
// Handle command stats analysis (both singular and plural)
84100
if (options.commandStats || options.commandsStats) {
85101
await runCommandStats(options);
@@ -253,4 +269,171 @@ async function createClaudeConfig(options = {}) {
253269
}
254270
}
255271

272+
// Individual component installation functions
273+
async function installIndividualAgent(agentName, targetDir, options) {
274+
console.log(chalk.blue(`🤖 Installing agent: ${agentName}`));
275+
276+
try {
277+
// Check if components directory exists
278+
const componentsPath = path.join(__dirname, '..', 'components', 'agents');
279+
const agentFile = path.join(componentsPath, `${agentName}.md`);
280+
281+
if (!await fs.pathExists(agentFile)) {
282+
console.log(chalk.red(`❌ Agent "${agentName}" not found`));
283+
console.log(chalk.yellow('Available agents:'));
284+
285+
// List available agents
286+
if (await fs.pathExists(componentsPath)) {
287+
const agents = await fs.readdir(componentsPath);
288+
agents.filter(f => f.endsWith('.md')).forEach(agent => {
289+
console.log(chalk.cyan(` - ${agent.replace('.md', '')}`));
290+
});
291+
}
292+
return;
293+
}
294+
295+
// For agents, they are typically part of templates, so we need to determine
296+
// the appropriate language/framework and install the complete template
297+
const agentContent = await fs.readFile(agentFile, 'utf8');
298+
const language = extractLanguageFromAgent(agentContent, agentName);
299+
const framework = extractFrameworkFromAgent(agentContent, agentName);
300+
301+
console.log(chalk.yellow(`📝 Agent "${agentName}" is part of ${language}/${framework} template`));
302+
console.log(chalk.blue('🚀 Installing complete template with this agent...'));
303+
304+
// Install the template that contains this agent (avoid recursion)
305+
const setupOptions = {
306+
...options,
307+
language,
308+
framework,
309+
yes: true,
310+
targetDirectory: targetDir,
311+
agent: null // Remove agent to avoid recursion
312+
};
313+
delete setupOptions.agent;
314+
await createClaudeConfig(setupOptions);
315+
316+
console.log(chalk.green(`✅ Agent "${agentName}" installed successfully!`));
317+
318+
} catch (error) {
319+
console.log(chalk.red(`❌ Error installing agent: ${error.message}`));
320+
}
321+
}
322+
323+
async function installIndividualCommand(commandName, targetDir, options) {
324+
console.log(chalk.blue(`⚡ Installing command: ${commandName}`));
325+
326+
try {
327+
// Check if components directory exists
328+
const componentsPath = path.join(__dirname, '..', 'components', 'commands');
329+
const commandFile = path.join(componentsPath, `${commandName}.md`);
330+
331+
if (!await fs.pathExists(commandFile)) {
332+
console.log(chalk.red(`❌ Command "${commandName}" not found`));
333+
console.log(chalk.yellow('Available commands:'));
334+
335+
// List available commands
336+
if (await fs.pathExists(componentsPath)) {
337+
const commands = await fs.readdir(componentsPath);
338+
commands.filter(f => f.endsWith('.md')).forEach(command => {
339+
console.log(chalk.cyan(` - ${command.replace('.md', '')}`));
340+
});
341+
}
342+
return;
343+
}
344+
345+
// Create .claude/commands directory if it doesn't exist
346+
const commandsDir = path.join(targetDir, '.claude', 'commands');
347+
await fs.ensureDir(commandsDir);
348+
349+
// Copy the command file
350+
const targetFile = path.join(commandsDir, `${commandName}.md`);
351+
await fs.copy(commandFile, targetFile);
352+
353+
console.log(chalk.green(`✅ Command "${commandName}" installed successfully!`));
354+
console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
355+
356+
} catch (error) {
357+
console.log(chalk.red(`❌ Error installing command: ${error.message}`));
358+
}
359+
}
360+
361+
async function installIndividualMCP(mcpName, targetDir, options) {
362+
console.log(chalk.blue(`🔌 Installing MCP: ${mcpName}`));
363+
364+
try {
365+
// Check if components directory exists
366+
const componentsPath = path.join(__dirname, '..', 'components', 'mcps');
367+
const mcpFile = path.join(componentsPath, `${mcpName}.json`);
368+
369+
if (!await fs.pathExists(mcpFile)) {
370+
console.log(chalk.red(`❌ MCP "${mcpName}" not found`));
371+
console.log(chalk.yellow('Available MCPs:'));
372+
373+
// List available MCPs
374+
if (await fs.pathExists(componentsPath)) {
375+
const mcps = await fs.readdir(componentsPath);
376+
mcps.filter(f => f.endsWith('.json')).forEach(mcp => {
377+
console.log(chalk.cyan(` - ${mcp.replace('.json', '')}`));
378+
});
379+
}
380+
return;
381+
}
382+
383+
// Read the MCP configuration
384+
const mcpConfig = await fs.readJson(mcpFile);
385+
386+
// Check if .mcp.json exists in target directory
387+
const targetMcpFile = path.join(targetDir, '.mcp.json');
388+
let existingConfig = {};
389+
390+
if (await fs.pathExists(targetMcpFile)) {
391+
existingConfig = await fs.readJson(targetMcpFile);
392+
console.log(chalk.yellow('📝 Existing .mcp.json found, merging configurations...'));
393+
}
394+
395+
// Merge configurations
396+
const mergedConfig = {
397+
...existingConfig,
398+
...mcpConfig
399+
};
400+
401+
// Write the merged configuration
402+
await fs.writeJson(targetMcpFile, mergedConfig, { spaces: 2 });
403+
404+
console.log(chalk.green(`✅ MCP "${mcpName}" installed successfully!`));
405+
console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetMcpFile)}`));
406+
407+
} catch (error) {
408+
console.log(chalk.red(`❌ Error installing MCP: ${error.message}`));
409+
}
410+
}
411+
412+
// Helper functions to extract language/framework from agent content
413+
function extractLanguageFromAgent(content, agentName) {
414+
// Try to determine language from agent content or filename
415+
if (agentName.includes('react') || content.includes('React')) return 'javascript-typescript';
416+
if (agentName.includes('django') || content.includes('Django')) return 'python';
417+
if (agentName.includes('fastapi') || content.includes('FastAPI')) return 'python';
418+
if (agentName.includes('flask') || content.includes('Flask')) return 'python';
419+
if (agentName.includes('rails') || content.includes('Rails')) return 'ruby';
420+
if (agentName.includes('api-security') || content.includes('API security')) return 'javascript-typescript';
421+
if (agentName.includes('database') || content.includes('database')) return 'javascript-typescript';
422+
423+
// Default to javascript-typescript for general agents
424+
return 'javascript-typescript';
425+
}
426+
427+
function extractFrameworkFromAgent(content, agentName) {
428+
// Try to determine framework from agent content or filename
429+
if (agentName.includes('react') || content.includes('React')) return 'react';
430+
if (agentName.includes('django') || content.includes('Django')) return 'django';
431+
if (agentName.includes('fastapi') || content.includes('FastAPI')) return 'fastapi';
432+
if (agentName.includes('flask') || content.includes('Flask')) return 'flask';
433+
if (agentName.includes('rails') || content.includes('Rails')) return 'rails';
434+
435+
// For general agents, return none to install the base template
436+
return 'none';
437+
}
438+
256439
module.exports = { createClaudeConfig, showMainMenu };

docs/script.js

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,46 +1029,15 @@ function createComponentCard(component) {
10291029
// Generate install command for component
10301030
function generateInstallCommand(component) {
10311031
if (component.type === 'agents') {
1032-
const language = getLanguageFromAgent(component);
1033-
const framework = getFrameworkFromAgent(component);
1034-
return `npx claude-code-templates@latest --language=${language} --framework=${framework}`;
1032+
return `npx claude-code-templates@latest --agent=${component.name} --yes`;
10351033
} else if (component.type === 'commands') {
1036-
return `mkdir -p .claude/commands && curl -o .claude/commands/${component.filename} https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/commands/${component.filename}`;
1034+
return `npx claude-code-templates@latest --command=${component.name} --yes`;
10371035
} else if (component.type === 'mcps') {
1038-
return `curl -o ./${component.filename} https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/mcps/${component.filename}`;
1036+
return `npx claude-code-templates@latest --mcp=${component.name} --yes`;
10391037
}
10401038
return `npx claude-code-templates@latest`;
10411039
}
10421040

1043-
// Helper function to extract language from agent
1044-
function getLanguageFromAgent(component) {
1045-
// Parse agent name to determine best language match
1046-
const name = component.name.toLowerCase();
1047-
1048-
if (name.includes('react') || name.includes('performance')) return 'javascript-typescript';
1049-
if (name.includes('api') || name.includes('database') || name.includes('security')) return 'javascript-typescript';
1050-
if (name.includes('python') || name.includes('django') || name.includes('flask')) return 'python';
1051-
if (name.includes('ruby') || name.includes('rails')) return 'ruby';
1052-
1053-
// Default to JavaScript/TypeScript for most agents
1054-
return 'javascript-typescript';
1055-
}
1056-
1057-
// Helper function to extract framework from agent
1058-
function getFrameworkFromAgent(component) {
1059-
const name = component.name.toLowerCase();
1060-
1061-
if (name.includes('react')) return 'react';
1062-
if (name.includes('vue')) return 'vue';
1063-
if (name.includes('angular')) return 'angular';
1064-
if (name.includes('django')) return 'django';
1065-
if (name.includes('flask')) return 'flask';
1066-
if (name.includes('fastapi')) return 'fastapi';
1067-
if (name.includes('rails')) return 'rails';
1068-
1069-
// Default to node for JavaScript/TypeScript agents
1070-
return 'node';
1071-
}
10721041

10731042
// Get installation notes (removed to match template cards design)
10741043
function getInstallationNotes() {

0 commit comments

Comments
 (0)