claude-code-templates 1.16.1 → 1.17.0
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/README.md +7 -7
- package/bin/create-claude-config.js +17 -8
- package/package.json +2 -3
- package/src/analytics/core/AgentAnalyzer.js +17 -3
- package/src/analytics/core/ProcessDetector.js +23 -7
- package/src/analytics/core/StateCalculator.js +102 -33
- package/src/analytics/data/DataCache.js +7 -7
- package/src/analytics-web/chats_mobile.html +2590 -0
- package/src/analytics-web/components/App.js +10 -10
- package/src/analytics-web/components/SessionTimer.js +1 -1
- package/src/analytics-web/components/Sidebar.js +5 -14
- package/src/analytics-web/index.html +932 -78
- package/src/analytics.js +263 -5
- package/src/chats-mobile.js +682 -0
- package/src/claude-api-proxy.js +460 -0
- package/src/file-operations.js +239 -36
- package/src/health-check.js +310 -0
- package/src/index.js +1245 -36
- package/src/tracking-service.js +31 -34
- package/components/agents/api-security-audit.md +0 -92
- package/components/agents/database-optimization.md +0 -94
- package/components/agents/react-performance-optimization.md +0 -64
- package/components/commands/check-file.md +0 -53
- package/components/commands/generate-tests.md +0 -68
- package/components/mcps/deepgraph-nextjs.json +0 -12
- package/components/mcps/deepgraph-react.json +0 -12
- package/components/mcps/deepgraph-typescript.json +0 -12
- package/components/mcps/deepgraph-vue.json +0 -12
- package/components/mcps/filesystem-access.json +0 -12
- package/components/mcps/github-integration.json +0 -11
- package/components/mcps/memory-integration.json +0 -8
- package/components/mcps/mysql-integration.json +0 -11
- package/components/mcps/postgresql-integration.json +0 -11
- package/components/mcps/web-fetch.json +0 -8
- package/src/analytics-web/components/AgentsPage.js +0 -4761
- package/templates/common/.claude/commands/git-workflow.md +0 -239
- package/templates/common/.claude/commands/project-setup.md +0 -316
- package/templates/common/.mcp.json +0 -41
- package/templates/common/CLAUDE.md +0 -109
- package/templates/common/README.md +0 -96
- package/templates/go/.mcp.json +0 -78
- package/templates/go/README.md +0 -25
- package/templates/javascript-typescript/.claude/commands/api-endpoint.md +0 -51
- package/templates/javascript-typescript/.claude/commands/debug.md +0 -52
- package/templates/javascript-typescript/.claude/commands/lint.md +0 -48
- package/templates/javascript-typescript/.claude/commands/npm-scripts.md +0 -48
- package/templates/javascript-typescript/.claude/commands/refactor.md +0 -55
- package/templates/javascript-typescript/.claude/commands/test.md +0 -61
- package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +0 -51
- package/templates/javascript-typescript/.claude/settings.json +0 -142
- package/templates/javascript-typescript/.mcp.json +0 -80
- package/templates/javascript-typescript/CLAUDE.md +0 -185
- package/templates/javascript-typescript/README.md +0 -259
- package/templates/javascript-typescript/examples/angular-app/.claude/commands/components.md +0 -63
- package/templates/javascript-typescript/examples/angular-app/.claude/commands/services.md +0 -62
- package/templates/javascript-typescript/examples/node-api/.claude/commands/api-endpoint.md +0 -46
- package/templates/javascript-typescript/examples/node-api/.claude/commands/database.md +0 -56
- package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -61
- package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -57
- package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -102
- package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -29
- package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -44
- package/templates/javascript-typescript/examples/react-app/.claude/commands/state-management.md +0 -45
- package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -81
- package/templates/javascript-typescript/examples/react-app/agents/react-performance-optimization.md +0 -530
- package/templates/javascript-typescript/examples/react-app/agents/react-state-management.md +0 -295
- package/templates/javascript-typescript/examples/vue-app/.claude/commands/components.md +0 -46
- package/templates/javascript-typescript/examples/vue-app/.claude/commands/composables.md +0 -51
- package/templates/python/.claude/commands/lint.md +0 -111
- package/templates/python/.claude/commands/test.md +0 -73
- package/templates/python/.claude/settings.json +0 -153
- package/templates/python/.mcp.json +0 -78
- package/templates/python/CLAUDE.md +0 -276
- package/templates/python/examples/django-app/.claude/commands/admin.md +0 -264
- package/templates/python/examples/django-app/.claude/commands/django-model.md +0 -124
- package/templates/python/examples/django-app/.claude/commands/views.md +0 -222
- package/templates/python/examples/django-app/CLAUDE.md +0 -313
- 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
- package/templates/python/examples/fastapi-app/.claude/commands/api-endpoints.md +0 -513
- package/templates/python/examples/fastapi-app/.claude/commands/auth.md +0 -775
- package/templates/python/examples/fastapi-app/.claude/commands/database.md +0 -657
- package/templates/python/examples/fastapi-app/.claude/commands/deployment.md +0 -160
- package/templates/python/examples/fastapi-app/.claude/commands/testing.md +0 -927
- package/templates/python/examples/fastapi-app/CLAUDE.md +0 -229
- package/templates/python/examples/flask-app/.claude/commands/app-factory.md +0 -384
- package/templates/python/examples/flask-app/.claude/commands/blueprint.md +0 -243
- package/templates/python/examples/flask-app/.claude/commands/database.md +0 -410
- package/templates/python/examples/flask-app/.claude/commands/deployment.md +0 -620
- package/templates/python/examples/flask-app/.claude/commands/flask-route.md +0 -217
- package/templates/python/examples/flask-app/.claude/commands/testing.md +0 -559
- package/templates/python/examples/flask-app/CLAUDE.md +0 -391
- package/templates/ruby/.claude/commands/model.md +0 -360
- package/templates/ruby/.claude/commands/test.md +0 -480
- package/templates/ruby/.claude/settings.json +0 -146
- package/templates/ruby/.mcp.json +0 -83
- package/templates/ruby/CLAUDE.md +0 -284
- package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +0 -490
- package/templates/ruby/examples/rails-app/CLAUDE.md +0 -376
- package/templates/rust/.mcp.json +0 -78
- package/templates/rust/README.md +0 -26
package/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ const { runCommandStats } = require('./command-stats');
|
|
|
13
13
|
const { runHookStats } = require('./hook-stats');
|
|
14
14
|
const { runMCPStats } = require('./mcp-stats');
|
|
15
15
|
const { runAnalytics } = require('./analytics');
|
|
16
|
+
const { startChatsMobile } = require('./chats-mobile');
|
|
16
17
|
const { runHealthCheck } = require('./health-check');
|
|
17
18
|
const { trackingService } = require('./tracking-service');
|
|
18
19
|
|
|
@@ -30,9 +31,14 @@ async function showMainMenu() {
|
|
|
30
31
|
short: 'Analytics Dashboard'
|
|
31
32
|
},
|
|
32
33
|
{
|
|
33
|
-
name: '💬 Chats
|
|
34
|
+
name: '💬 Chats Mobile - AI-first mobile interface for conversations',
|
|
34
35
|
value: 'chats',
|
|
35
|
-
short: 'Chats
|
|
36
|
+
short: 'Chats Mobile'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: '🤖 Agents Dashboard - View and analyze Claude conversations with agent tools',
|
|
40
|
+
value: 'agents',
|
|
41
|
+
short: 'Agents Dashboard'
|
|
36
42
|
},
|
|
37
43
|
{
|
|
38
44
|
name: '⚙️ Project Setup - Configure Claude Code for your project',
|
|
@@ -56,12 +62,20 @@ async function showMainMenu() {
|
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
if (initialChoice.action === 'chats') {
|
|
59
|
-
console.log(chalk.blue('💬 Launching Claude Code Chats
|
|
65
|
+
console.log(chalk.blue('💬 Launching Claude Code Mobile Chats...'));
|
|
66
|
+
trackingService.trackAnalyticsDashboard({ page: 'chats-mobile', source: 'interactive_menu' });
|
|
67
|
+
await startChatsMobile({});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (initialChoice.action === 'agents') {
|
|
72
|
+
console.log(chalk.blue('🤖 Launching Claude Code Agents Dashboard...'));
|
|
60
73
|
trackingService.trackAnalyticsDashboard({ page: 'agents', source: 'interactive_menu' });
|
|
61
74
|
await runAnalytics({ openTo: 'agents' });
|
|
62
75
|
return;
|
|
63
76
|
}
|
|
64
77
|
|
|
78
|
+
|
|
65
79
|
if (initialChoice.action === 'health') {
|
|
66
80
|
console.log(chalk.blue('🔍 Running Health Check...'));
|
|
67
81
|
const healthResult = await runHealthCheck();
|
|
@@ -90,19 +104,29 @@ async function showMainMenu() {
|
|
|
90
104
|
async function createClaudeConfig(options = {}) {
|
|
91
105
|
const targetDir = options.directory || process.cwd();
|
|
92
106
|
|
|
93
|
-
//
|
|
94
|
-
if (options.
|
|
95
|
-
|
|
107
|
+
// Validate --tunnel usage
|
|
108
|
+
if (options.tunnel && !options.analytics && !options.chats && !options.agents && !options.chatsMobile) {
|
|
109
|
+
console.log(chalk.red('❌ Error: --tunnel can only be used with --analytics, --chats, or --chats-mobile'));
|
|
110
|
+
console.log(chalk.yellow('💡 Examples:'));
|
|
111
|
+
console.log(chalk.gray(' cct --analytics --tunnel'));
|
|
112
|
+
console.log(chalk.gray(' cct --chats --tunnel'));
|
|
113
|
+
console.log(chalk.gray(' cct --chats-mobile'));
|
|
96
114
|
return;
|
|
97
115
|
}
|
|
98
116
|
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
// Handle multiple components installation (new approach)
|
|
118
|
+
if (options.agent || options.command || options.mcp || options.setting || options.hook) {
|
|
119
|
+
// If --workflow is used with components, treat it as YAML
|
|
120
|
+
if (options.workflow) {
|
|
121
|
+
options.yaml = options.workflow;
|
|
122
|
+
}
|
|
123
|
+
await installMultipleComponents(options, targetDir);
|
|
101
124
|
return;
|
|
102
125
|
}
|
|
103
126
|
|
|
104
|
-
|
|
105
|
-
|
|
127
|
+
// Handle workflow installation (hash-based)
|
|
128
|
+
if (options.workflow) {
|
|
129
|
+
await installWorkflow(options.workflow, targetDir, options);
|
|
106
130
|
return;
|
|
107
131
|
}
|
|
108
132
|
|
|
@@ -131,13 +155,27 @@ async function createClaudeConfig(options = {}) {
|
|
|
131
155
|
return;
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
// Handle chats
|
|
135
|
-
if (options.chats
|
|
158
|
+
// Handle chats dashboard (now points to mobile chats interface)
|
|
159
|
+
if (options.chats) {
|
|
160
|
+
trackingService.trackAnalyticsDashboard({ page: 'chats-mobile', source: 'command_line' });
|
|
161
|
+
await startChatsMobile(options);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Handle agents dashboard (separate from chats)
|
|
166
|
+
if (options.agents) {
|
|
136
167
|
trackingService.trackAnalyticsDashboard({ page: 'agents', source: 'command_line' });
|
|
137
168
|
await runAnalytics({ ...options, openTo: 'agents' });
|
|
138
169
|
return;
|
|
139
170
|
}
|
|
140
171
|
|
|
172
|
+
// Handle mobile chats interface
|
|
173
|
+
if (options.chatsMobile) {
|
|
174
|
+
trackingService.trackAnalyticsDashboard({ page: 'chats-mobile', source: 'command_line' });
|
|
175
|
+
await startChatsMobile(options);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
141
179
|
// Handle health check
|
|
142
180
|
let shouldRunSetup = false;
|
|
143
181
|
if (options.healthCheck || options.health || options.check || options.verify) {
|
|
@@ -175,8 +213,8 @@ async function createClaudeConfig(options = {}) {
|
|
|
175
213
|
|
|
176
214
|
let config;
|
|
177
215
|
if (options.yes) {
|
|
178
|
-
// Use defaults
|
|
179
|
-
const selectedLanguage = options.language || projectInfo.detectedLanguage || 'common';
|
|
216
|
+
// Use defaults - prioritize --template over --language for backward compatibility
|
|
217
|
+
const selectedLanguage = options.template || options.language || projectInfo.detectedLanguage || 'common';
|
|
180
218
|
|
|
181
219
|
// Check if selected language is coming soon
|
|
182
220
|
if (selectedLanguage && TEMPLATES_CONFIG[selectedLanguage] && TEMPLATES_CONFIG[selectedLanguage].comingSoon) {
|
|
@@ -265,7 +303,7 @@ async function createClaudeConfig(options = {}) {
|
|
|
265
303
|
console.log(chalk.white(' 3. Start using Claude Code with: claude'));
|
|
266
304
|
console.log('');
|
|
267
305
|
console.log(chalk.blue('🌐 View all available templates at: https://aitmpl.com/'));
|
|
268
|
-
console.log(chalk.blue('📖 Read the complete documentation at: https://aitmpl.com/
|
|
306
|
+
console.log(chalk.blue('📖 Read the complete documentation at: https://docs.aitmpl.com/'));
|
|
269
307
|
|
|
270
308
|
if (config.language !== 'common') {
|
|
271
309
|
console.log(chalk.yellow(`💡 Language-specific features for ${config.language} have been configured`));
|
|
@@ -298,6 +336,11 @@ async function createClaudeConfig(options = {}) {
|
|
|
298
336
|
if (!options.dryRun) {
|
|
299
337
|
await runPostInstallationValidation(targetDir, templateConfig);
|
|
300
338
|
}
|
|
339
|
+
|
|
340
|
+
// Handle prompt execution if provided
|
|
341
|
+
if (options.prompt) {
|
|
342
|
+
await handlePromptExecution(options.prompt, targetDir);
|
|
343
|
+
}
|
|
301
344
|
}
|
|
302
345
|
|
|
303
346
|
// Individual component installation functions
|
|
@@ -305,15 +348,23 @@ async function installIndividualAgent(agentName, targetDir, options) {
|
|
|
305
348
|
console.log(chalk.blue(`🤖 Installing agent: ${agentName}`));
|
|
306
349
|
|
|
307
350
|
try {
|
|
308
|
-
//
|
|
309
|
-
|
|
351
|
+
// Support both category/agent-name and direct agent-name formats
|
|
352
|
+
let githubUrl;
|
|
353
|
+
if (agentName.includes('/')) {
|
|
354
|
+
// Category/agent format: deep-research-team/academic-researcher
|
|
355
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/agents/${agentName}.md`;
|
|
356
|
+
} else {
|
|
357
|
+
// Direct agent format: api-security-audit
|
|
358
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/agents/${agentName}.md`;
|
|
359
|
+
}
|
|
360
|
+
|
|
310
361
|
console.log(chalk.gray(`📥 Downloading from GitHub (main branch)...`));
|
|
311
362
|
|
|
312
363
|
const response = await fetch(githubUrl);
|
|
313
364
|
if (!response.ok) {
|
|
314
365
|
if (response.status === 404) {
|
|
315
366
|
console.log(chalk.red(`❌ Agent "${agentName}" not found`));
|
|
316
|
-
|
|
367
|
+
await showAvailableAgents();
|
|
317
368
|
return;
|
|
318
369
|
}
|
|
319
370
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
@@ -325,13 +376,23 @@ async function installIndividualAgent(agentName, targetDir, options) {
|
|
|
325
376
|
const agentsDir = path.join(targetDir, '.claude', 'agents');
|
|
326
377
|
await fs.ensureDir(agentsDir);
|
|
327
378
|
|
|
328
|
-
// Write the agent file
|
|
329
|
-
|
|
379
|
+
// Write the agent file - always to flat .claude/agents directory
|
|
380
|
+
let fileName;
|
|
381
|
+
if (agentName.includes('/')) {
|
|
382
|
+
const [category, filename] = agentName.split('/');
|
|
383
|
+
fileName = filename; // Extract just the filename, ignore category for installation
|
|
384
|
+
} else {
|
|
385
|
+
fileName = agentName;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const targetFile = path.join(agentsDir, `${fileName}.md`);
|
|
330
389
|
await fs.writeFile(targetFile, agentContent, 'utf8');
|
|
331
390
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
391
|
+
if (!options.silent) {
|
|
392
|
+
console.log(chalk.green(`✅ Agent "${agentName}" installed successfully!`));
|
|
393
|
+
console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
|
|
394
|
+
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
395
|
+
}
|
|
335
396
|
|
|
336
397
|
// Track successful agent installation
|
|
337
398
|
trackingService.trackDownload('agent', agentName, {
|
|
@@ -349,8 +410,16 @@ async function installIndividualCommand(commandName, targetDir, options) {
|
|
|
349
410
|
console.log(chalk.blue(`⚡ Installing command: ${commandName}`));
|
|
350
411
|
|
|
351
412
|
try {
|
|
352
|
-
//
|
|
353
|
-
|
|
413
|
+
// Support both category/command-name and direct command-name formats
|
|
414
|
+
let githubUrl;
|
|
415
|
+
if (commandName.includes('/')) {
|
|
416
|
+
// Category/command format: security/vulnerability-scan
|
|
417
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/commands/${commandName}.md`;
|
|
418
|
+
} else {
|
|
419
|
+
// Direct command format: check-file
|
|
420
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/commands/${commandName}.md`;
|
|
421
|
+
}
|
|
422
|
+
|
|
354
423
|
console.log(chalk.gray(`📥 Downloading from GitHub (main branch)...`));
|
|
355
424
|
|
|
356
425
|
const response = await fetch(githubUrl);
|
|
@@ -369,13 +438,24 @@ async function installIndividualCommand(commandName, targetDir, options) {
|
|
|
369
438
|
const commandsDir = path.join(targetDir, '.claude', 'commands');
|
|
370
439
|
await fs.ensureDir(commandsDir);
|
|
371
440
|
|
|
372
|
-
// Write the command file
|
|
373
|
-
|
|
441
|
+
// Write the command file - always to flat .claude/commands directory
|
|
442
|
+
let fileName;
|
|
443
|
+
if (commandName.includes('/')) {
|
|
444
|
+
const [category, filename] = commandName.split('/');
|
|
445
|
+
fileName = filename; // Extract just the filename, ignore category for installation
|
|
446
|
+
} else {
|
|
447
|
+
fileName = commandName;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const targetFile = path.join(commandsDir, `${fileName}.md`);
|
|
451
|
+
|
|
374
452
|
await fs.writeFile(targetFile, commandContent, 'utf8');
|
|
375
453
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
454
|
+
if (!options.silent) {
|
|
455
|
+
console.log(chalk.green(`✅ Command "${commandName}" installed successfully!`));
|
|
456
|
+
console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
|
|
457
|
+
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
458
|
+
}
|
|
379
459
|
|
|
380
460
|
// Track successful command installation
|
|
381
461
|
trackingService.trackDownload('command', commandName, {
|
|
@@ -393,8 +473,16 @@ async function installIndividualMCP(mcpName, targetDir, options) {
|
|
|
393
473
|
console.log(chalk.blue(`🔌 Installing MCP: ${mcpName}`));
|
|
394
474
|
|
|
395
475
|
try {
|
|
396
|
-
//
|
|
397
|
-
|
|
476
|
+
// Support both category/mcp-name and direct mcp-name formats
|
|
477
|
+
let githubUrl;
|
|
478
|
+
if (mcpName.includes('/')) {
|
|
479
|
+
// Category/mcp format: database/mysql-integration
|
|
480
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/mcps/${mcpName}.json`;
|
|
481
|
+
} else {
|
|
482
|
+
// Direct mcp format: web-fetch
|
|
483
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/mcps/${mcpName}.json`;
|
|
484
|
+
}
|
|
485
|
+
|
|
398
486
|
console.log(chalk.gray(`📥 Downloading from GitHub (main branch)...`));
|
|
399
487
|
|
|
400
488
|
const response = await fetch(githubUrl);
|
|
@@ -409,6 +497,15 @@ async function installIndividualMCP(mcpName, targetDir, options) {
|
|
|
409
497
|
|
|
410
498
|
const mcpConfigText = await response.text();
|
|
411
499
|
const mcpConfig = JSON.parse(mcpConfigText);
|
|
500
|
+
|
|
501
|
+
// Remove description field from each MCP server before merging
|
|
502
|
+
if (mcpConfig.mcpServers) {
|
|
503
|
+
for (const serverName in mcpConfig.mcpServers) {
|
|
504
|
+
if (mcpConfig.mcpServers[serverName] && typeof mcpConfig.mcpServers[serverName] === 'object') {
|
|
505
|
+
delete mcpConfig.mcpServers[serverName].description;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
412
509
|
|
|
413
510
|
// Check if .mcp.json exists in target directory
|
|
414
511
|
const targetMcpFile = path.join(targetDir, '.mcp.json');
|
|
@@ -419,18 +516,28 @@ async function installIndividualMCP(mcpName, targetDir, options) {
|
|
|
419
516
|
console.log(chalk.yellow('📝 Existing .mcp.json found, merging configurations...'));
|
|
420
517
|
}
|
|
421
518
|
|
|
422
|
-
// Merge configurations
|
|
519
|
+
// Merge configurations with deep merge for mcpServers
|
|
423
520
|
const mergedConfig = {
|
|
424
521
|
...existingConfig,
|
|
425
522
|
...mcpConfig
|
|
426
523
|
};
|
|
427
524
|
|
|
525
|
+
// Deep merge mcpServers specifically to avoid overwriting existing servers
|
|
526
|
+
if (existingConfig.mcpServers && mcpConfig.mcpServers) {
|
|
527
|
+
mergedConfig.mcpServers = {
|
|
528
|
+
...existingConfig.mcpServers,
|
|
529
|
+
...mcpConfig.mcpServers
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
428
533
|
// Write the merged configuration
|
|
429
534
|
await fs.writeJson(targetMcpFile, mergedConfig, { spaces: 2 });
|
|
430
535
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
536
|
+
if (!options.silent) {
|
|
537
|
+
console.log(chalk.green(`✅ MCP "${mcpName}" installed successfully!`));
|
|
538
|
+
console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetMcpFile)}`));
|
|
539
|
+
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
540
|
+
}
|
|
434
541
|
|
|
435
542
|
// Track successful MCP installation
|
|
436
543
|
trackingService.trackDownload('mcp', mcpName, {
|
|
@@ -445,6 +552,286 @@ async function installIndividualMCP(mcpName, targetDir, options) {
|
|
|
445
552
|
}
|
|
446
553
|
}
|
|
447
554
|
|
|
555
|
+
async function installIndividualSetting(settingName, targetDir, options) {
|
|
556
|
+
console.log(chalk.blue(`⚙️ Installing setting: ${settingName}`));
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
// Support both category/setting-name and direct setting-name formats
|
|
560
|
+
let githubUrl;
|
|
561
|
+
if (settingName.includes('/')) {
|
|
562
|
+
// Category/setting format: permissions/allow-npm-commands
|
|
563
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/settings/${settingName}.json`;
|
|
564
|
+
} else {
|
|
565
|
+
// Direct setting format: allow-npm-commands
|
|
566
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/settings/${settingName}.json`;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
console.log(chalk.gray(`📥 Downloading from GitHub (main branch)...`));
|
|
570
|
+
|
|
571
|
+
const response = await fetch(githubUrl);
|
|
572
|
+
if (!response.ok) {
|
|
573
|
+
if (response.status === 404) {
|
|
574
|
+
console.log(chalk.red(`❌ Setting "${settingName}" not found`));
|
|
575
|
+
console.log(chalk.yellow('Available settings: enable-telemetry, disable-telemetry, allow-npm-commands, deny-sensitive-files, use-sonnet, use-haiku, retention-7-days, retention-90-days'));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const settingConfigText = await response.text();
|
|
582
|
+
const settingConfig = JSON.parse(settingConfigText);
|
|
583
|
+
|
|
584
|
+
// Remove description field before merging
|
|
585
|
+
if (settingConfig && typeof settingConfig === 'object') {
|
|
586
|
+
delete settingConfig.description;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Check if .claude/settings.json exists in target directory
|
|
590
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
591
|
+
const targetSettingsFile = path.join(claudeDir, 'settings.json');
|
|
592
|
+
let existingConfig = {};
|
|
593
|
+
|
|
594
|
+
// Ensure .claude directory exists
|
|
595
|
+
await fs.ensureDir(claudeDir);
|
|
596
|
+
|
|
597
|
+
if (await fs.pathExists(targetSettingsFile)) {
|
|
598
|
+
existingConfig = await fs.readJson(targetSettingsFile);
|
|
599
|
+
console.log(chalk.yellow('📝 Existing .claude/settings.json found, merging configurations...'));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Check for conflicts before merging
|
|
603
|
+
const conflicts = [];
|
|
604
|
+
|
|
605
|
+
// Check for conflicting environment variables
|
|
606
|
+
if (existingConfig.env && settingConfig.env) {
|
|
607
|
+
Object.keys(settingConfig.env).forEach(key => {
|
|
608
|
+
if (existingConfig.env[key] && existingConfig.env[key] !== settingConfig.env[key]) {
|
|
609
|
+
conflicts.push(`Environment variable "${key}" (current: "${existingConfig.env[key]}", new: "${settingConfig.env[key]}")`);
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Check for conflicting top-level settings
|
|
615
|
+
Object.keys(settingConfig).forEach(key => {
|
|
616
|
+
if (key !== 'permissions' && key !== 'env' && key !== 'hooks' &&
|
|
617
|
+
existingConfig[key] !== undefined && existingConfig[key] !== settingConfig[key]) {
|
|
618
|
+
conflicts.push(`Setting "${key}" (current: "${existingConfig[key]}", new: "${settingConfig[key]}")`);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Ask user about conflicts if any exist and not in silent mode
|
|
623
|
+
if (conflicts.length > 0 && !options.silent) {
|
|
624
|
+
console.log(chalk.yellow(`\n⚠️ Conflicts detected while installing setting "${settingName}":`));
|
|
625
|
+
conflicts.forEach(conflict => console.log(chalk.gray(` • ${conflict}`)));
|
|
626
|
+
|
|
627
|
+
const inquirer = require('inquirer');
|
|
628
|
+
const { shouldOverwrite } = await inquirer.prompt([{
|
|
629
|
+
type: 'confirm',
|
|
630
|
+
name: 'shouldOverwrite',
|
|
631
|
+
message: 'Do you want to overwrite the existing configuration?',
|
|
632
|
+
default: false
|
|
633
|
+
}]);
|
|
634
|
+
|
|
635
|
+
if (!shouldOverwrite) {
|
|
636
|
+
console.log(chalk.yellow(`⏹️ Installation of setting "${settingName}" cancelled by user.`));
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
} else if (conflicts.length > 0 && options.silent) {
|
|
640
|
+
// In silent mode (batch installation), skip conflicting settings and warn
|
|
641
|
+
console.log(chalk.yellow(`⚠️ Skipping setting "${settingName}" due to conflicts (use individual installation to resolve)`));
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Deep merge configurations
|
|
646
|
+
const mergedConfig = {
|
|
647
|
+
...existingConfig,
|
|
648
|
+
...settingConfig
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// Deep merge specific sections (only if no conflicts or user approved overwrite)
|
|
652
|
+
if (existingConfig.permissions && settingConfig.permissions) {
|
|
653
|
+
mergedConfig.permissions = {
|
|
654
|
+
...existingConfig.permissions,
|
|
655
|
+
...settingConfig.permissions
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// Merge arrays for allow, deny, ask (no conflicts here, just merge)
|
|
659
|
+
['allow', 'deny', 'ask'].forEach(key => {
|
|
660
|
+
if (existingConfig.permissions[key] && settingConfig.permissions[key]) {
|
|
661
|
+
mergedConfig.permissions[key] = [
|
|
662
|
+
...new Set([...existingConfig.permissions[key], ...settingConfig.permissions[key]])
|
|
663
|
+
];
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (existingConfig.env && settingConfig.env) {
|
|
669
|
+
mergedConfig.env = {
|
|
670
|
+
...existingConfig.env,
|
|
671
|
+
...settingConfig.env
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (existingConfig.hooks && settingConfig.hooks) {
|
|
676
|
+
mergedConfig.hooks = {
|
|
677
|
+
...existingConfig.hooks,
|
|
678
|
+
...settingConfig.hooks
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Write the merged configuration
|
|
683
|
+
await fs.writeJson(targetSettingsFile, mergedConfig, { spaces: 2 });
|
|
684
|
+
|
|
685
|
+
if (!options.silent) {
|
|
686
|
+
console.log(chalk.green(`✅ Setting "${settingName}" installed successfully!`));
|
|
687
|
+
console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetSettingsFile)}`));
|
|
688
|
+
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Track successful setting installation
|
|
692
|
+
trackingService.trackDownload('setting', settingName, {
|
|
693
|
+
installation_type: 'individual_setting',
|
|
694
|
+
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
695
|
+
source: 'github_main'
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
} catch (error) {
|
|
699
|
+
console.log(chalk.red(`❌ Error installing setting: ${error.message}`));
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async function installIndividualHook(hookName, targetDir, options) {
|
|
704
|
+
console.log(chalk.blue(`🪝 Installing hook: ${hookName}`));
|
|
705
|
+
|
|
706
|
+
try {
|
|
707
|
+
// Support both category/hook-name and direct hook-name formats
|
|
708
|
+
let githubUrl;
|
|
709
|
+
if (hookName.includes('/')) {
|
|
710
|
+
// Category/hook format: pre-tool/backup-before-edit
|
|
711
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/hooks/${hookName}.json`;
|
|
712
|
+
} else {
|
|
713
|
+
// Direct hook format: backup-before-edit
|
|
714
|
+
githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/hooks/${hookName}.json`;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
console.log(chalk.gray(`📥 Downloading from GitHub (main branch)...`));
|
|
718
|
+
|
|
719
|
+
const response = await fetch(githubUrl);
|
|
720
|
+
if (!response.ok) {
|
|
721
|
+
if (response.status === 404) {
|
|
722
|
+
console.log(chalk.red(`❌ Hook "${hookName}" not found`));
|
|
723
|
+
console.log(chalk.yellow('Available hooks: notify-before-bash, format-python-files, format-javascript-files, git-add-changes, backup-before-edit, run-tests-after-changes'));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const hookConfigText = await response.text();
|
|
730
|
+
const hookConfig = JSON.parse(hookConfigText);
|
|
731
|
+
|
|
732
|
+
// Remove description field before merging
|
|
733
|
+
if (hookConfig && typeof hookConfig === 'object') {
|
|
734
|
+
delete hookConfig.description;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Check if .claude/settings.json exists in target directory (hooks go in settings.json)
|
|
738
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
739
|
+
const targetSettingsFile = path.join(claudeDir, 'settings.json');
|
|
740
|
+
let existingConfig = {};
|
|
741
|
+
|
|
742
|
+
// Ensure .claude directory exists
|
|
743
|
+
await fs.ensureDir(claudeDir);
|
|
744
|
+
|
|
745
|
+
if (await fs.pathExists(targetSettingsFile)) {
|
|
746
|
+
existingConfig = await fs.readJson(targetSettingsFile);
|
|
747
|
+
console.log(chalk.yellow('📝 Existing .claude/settings.json found, merging hook configurations...'));
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Check for conflicts before merging
|
|
751
|
+
const conflicts = [];
|
|
752
|
+
|
|
753
|
+
// Check for conflicting hooks
|
|
754
|
+
if (existingConfig.hooks && hookConfig.hooks) {
|
|
755
|
+
['PreToolUse', 'PostToolUse'].forEach(hookType => {
|
|
756
|
+
if (existingConfig.hooks[hookType] && hookConfig.hooks[hookType]) {
|
|
757
|
+
Object.keys(hookConfig.hooks[hookType]).forEach(toolName => {
|
|
758
|
+
if (existingConfig.hooks[hookType][toolName] &&
|
|
759
|
+
existingConfig.hooks[hookType][toolName] !== hookConfig.hooks[hookType][toolName]) {
|
|
760
|
+
conflicts.push(`Hook "${hookType}.${toolName}" (current: "${existingConfig.hooks[hookType][toolName]}", new: "${hookConfig.hooks[hookType][toolName]}")`);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Ask user about conflicts if any exist and not in silent mode
|
|
768
|
+
if (conflicts.length > 0 && !options.silent) {
|
|
769
|
+
console.log(chalk.yellow(`\n⚠️ Conflicts detected while installing hook "${hookName}":`));
|
|
770
|
+
conflicts.forEach(conflict => console.log(chalk.gray(` • ${conflict}`)));
|
|
771
|
+
|
|
772
|
+
const inquirer = require('inquirer');
|
|
773
|
+
const { shouldOverwrite } = await inquirer.prompt([{
|
|
774
|
+
type: 'confirm',
|
|
775
|
+
name: 'shouldOverwrite',
|
|
776
|
+
message: 'Do you want to overwrite the existing hook configuration?',
|
|
777
|
+
default: false
|
|
778
|
+
}]);
|
|
779
|
+
|
|
780
|
+
if (!shouldOverwrite) {
|
|
781
|
+
console.log(chalk.yellow(`⏹️ Installation of hook "${hookName}" cancelled by user.`));
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
} else if (conflicts.length > 0 && options.silent) {
|
|
785
|
+
// In silent mode (batch installation), skip conflicting hooks and warn
|
|
786
|
+
console.log(chalk.yellow(`⚠️ Skipping hook "${hookName}" due to conflicts (use individual installation to resolve)`));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// Deep merge configurations
|
|
791
|
+
const mergedConfig = {
|
|
792
|
+
...existingConfig,
|
|
793
|
+
...hookConfig
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
// Deep merge hooks specifically (only if no conflicts or user approved overwrite)
|
|
797
|
+
if (existingConfig.hooks && hookConfig.hooks) {
|
|
798
|
+
mergedConfig.hooks = {
|
|
799
|
+
...existingConfig.hooks,
|
|
800
|
+
...hookConfig.hooks
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// Deep merge hook types (PreToolUse, PostToolUse)
|
|
804
|
+
['PreToolUse', 'PostToolUse'].forEach(hookType => {
|
|
805
|
+
if (existingConfig.hooks[hookType] && hookConfig.hooks[hookType]) {
|
|
806
|
+
mergedConfig.hooks[hookType] = {
|
|
807
|
+
...existingConfig.hooks[hookType],
|
|
808
|
+
...hookConfig.hooks[hookType]
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Write the merged configuration
|
|
815
|
+
await fs.writeJson(targetSettingsFile, mergedConfig, { spaces: 2 });
|
|
816
|
+
|
|
817
|
+
if (!options.silent) {
|
|
818
|
+
console.log(chalk.green(`✅ Hook "${hookName}" installed successfully!`));
|
|
819
|
+
console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetSettingsFile)}`));
|
|
820
|
+
console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Track successful hook installation
|
|
824
|
+
trackingService.trackDownload('hook', hookName, {
|
|
825
|
+
installation_type: 'individual_hook',
|
|
826
|
+
merged_with_existing: Object.keys(existingConfig).length > 0,
|
|
827
|
+
source: 'github_main'
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
} catch (error) {
|
|
831
|
+
console.log(chalk.red(`❌ Error installing hook: ${error.message}`));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
448
835
|
// Helper functions to extract language/framework from agent content
|
|
449
836
|
function extractLanguageFromAgent(content, agentName) {
|
|
450
837
|
// Try to determine language from agent content or filename
|
|
@@ -472,4 +859,826 @@ function extractFrameworkFromAgent(content, agentName) {
|
|
|
472
859
|
return 'none';
|
|
473
860
|
}
|
|
474
861
|
|
|
862
|
+
/**
|
|
863
|
+
* Fetch available agents dynamically from GitHub repository
|
|
864
|
+
*/
|
|
865
|
+
async function getAvailableAgentsFromGitHub() {
|
|
866
|
+
try {
|
|
867
|
+
const response = await fetch('https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/agents');
|
|
868
|
+
if (!response.ok) {
|
|
869
|
+
throw new Error(`GitHub API error: ${response.status}`);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const contents = await response.json();
|
|
873
|
+
const agents = [];
|
|
874
|
+
|
|
875
|
+
for (const item of contents) {
|
|
876
|
+
if (item.type === 'file' && item.name.endsWith('.md')) {
|
|
877
|
+
// Direct agent file
|
|
878
|
+
agents.push({
|
|
879
|
+
name: item.name.replace('.md', ''),
|
|
880
|
+
path: item.name.replace('.md', ''),
|
|
881
|
+
category: 'root'
|
|
882
|
+
});
|
|
883
|
+
} else if (item.type === 'dir') {
|
|
884
|
+
// Category directory, fetch its contents
|
|
885
|
+
try {
|
|
886
|
+
const categoryResponse = await fetch(`https://api.github.com/repos/davila7/claude-code-templates/contents/cli-tool/components/agents/${item.name}`);
|
|
887
|
+
if (categoryResponse.ok) {
|
|
888
|
+
const categoryContents = await categoryResponse.json();
|
|
889
|
+
for (const categoryItem of categoryContents) {
|
|
890
|
+
if (categoryItem.type === 'file' && categoryItem.name.endsWith('.md')) {
|
|
891
|
+
agents.push({
|
|
892
|
+
name: categoryItem.name.replace('.md', ''),
|
|
893
|
+
path: `${item.name}/${categoryItem.name.replace('.md', '')}`,
|
|
894
|
+
category: item.name
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
} catch (error) {
|
|
900
|
+
console.warn(`Warning: Could not fetch category ${item.name}:`, error.message);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
return agents;
|
|
906
|
+
} catch (error) {
|
|
907
|
+
console.warn('Warning: Could not fetch agents from GitHub, using fallback list');
|
|
908
|
+
// Fallback to basic list if GitHub API fails
|
|
909
|
+
return [
|
|
910
|
+
{ name: 'api-security-audit', path: 'api-security-audit', category: 'root' },
|
|
911
|
+
{ name: 'database-optimization', path: 'database-optimization', category: 'root' },
|
|
912
|
+
{ name: 'react-performance-optimization', path: 'react-performance-optimization', category: 'root' }
|
|
913
|
+
];
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Install multiple components with optional YAML workflow
|
|
919
|
+
*/
|
|
920
|
+
async function installMultipleComponents(options, targetDir) {
|
|
921
|
+
console.log(chalk.blue('🔧 Installing multiple components...'));
|
|
922
|
+
|
|
923
|
+
try {
|
|
924
|
+
const components = {
|
|
925
|
+
agents: [],
|
|
926
|
+
commands: [],
|
|
927
|
+
mcps: [],
|
|
928
|
+
settings: [],
|
|
929
|
+
hooks: []
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
// Parse comma-separated values for each component type
|
|
933
|
+
if (options.agent) {
|
|
934
|
+
const agentsInput = Array.isArray(options.agent) ? options.agent.join(',') : options.agent;
|
|
935
|
+
components.agents = agentsInput.split(',').map(a => a.trim()).filter(a => a);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (options.command) {
|
|
939
|
+
const commandsInput = Array.isArray(options.command) ? options.command.join(',') : options.command;
|
|
940
|
+
components.commands = commandsInput.split(',').map(c => c.trim()).filter(c => c);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (options.mcp) {
|
|
944
|
+
const mcpsInput = Array.isArray(options.mcp) ? options.mcp.join(',') : options.mcp;
|
|
945
|
+
components.mcps = mcpsInput.split(',').map(m => m.trim()).filter(m => m);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (options.setting) {
|
|
949
|
+
const settingsInput = Array.isArray(options.setting) ? options.setting.join(',') : options.setting;
|
|
950
|
+
components.settings = settingsInput.split(',').map(s => s.trim()).filter(s => s);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
if (options.hook) {
|
|
954
|
+
const hooksInput = Array.isArray(options.hook) ? options.hook.join(',') : options.hook;
|
|
955
|
+
components.hooks = hooksInput.split(',').map(h => h.trim()).filter(h => h);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const totalComponents = components.agents.length + components.commands.length + components.mcps.length + components.settings.length + components.hooks.length;
|
|
959
|
+
|
|
960
|
+
if (totalComponents === 0) {
|
|
961
|
+
console.log(chalk.yellow('⚠️ No components specified to install.'));
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
console.log(chalk.cyan(`📦 Installing ${totalComponents} components:`));
|
|
966
|
+
console.log(chalk.gray(` Agents: ${components.agents.length}`));
|
|
967
|
+
console.log(chalk.gray(` Commands: ${components.commands.length}`));
|
|
968
|
+
console.log(chalk.gray(` MCPs: ${components.mcps.length}`));
|
|
969
|
+
console.log(chalk.gray(` Settings: ${components.settings.length}`));
|
|
970
|
+
console.log(chalk.gray(` Hooks: ${components.hooks.length}`));
|
|
971
|
+
|
|
972
|
+
// Install agents
|
|
973
|
+
for (const agent of components.agents) {
|
|
974
|
+
console.log(chalk.gray(` Installing agent: ${agent}`));
|
|
975
|
+
await installIndividualAgent(agent, targetDir, { ...options, silent: true });
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Install commands
|
|
979
|
+
for (const command of components.commands) {
|
|
980
|
+
console.log(chalk.gray(` Installing command: ${command}`));
|
|
981
|
+
await installIndividualCommand(command, targetDir, { ...options, silent: true });
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Install MCPs
|
|
985
|
+
for (const mcp of components.mcps) {
|
|
986
|
+
console.log(chalk.gray(` Installing MCP: ${mcp}`));
|
|
987
|
+
await installIndividualMCP(mcp, targetDir, { ...options, silent: true });
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// Install settings
|
|
991
|
+
for (const setting of components.settings) {
|
|
992
|
+
console.log(chalk.gray(` Installing setting: ${setting}`));
|
|
993
|
+
await installIndividualSetting(setting, targetDir, { ...options, silent: true });
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Install hooks
|
|
997
|
+
for (const hook of components.hooks) {
|
|
998
|
+
console.log(chalk.gray(` Installing hook: ${hook}`));
|
|
999
|
+
await installIndividualHook(hook, targetDir, { ...options, silent: true });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Handle YAML workflow if provided
|
|
1003
|
+
if (options.yaml) {
|
|
1004
|
+
console.log(chalk.blue('\n📄 Processing workflow YAML...'));
|
|
1005
|
+
|
|
1006
|
+
try {
|
|
1007
|
+
// Decode the YAML from base64
|
|
1008
|
+
const yamlContent = Buffer.from(options.yaml, 'base64').toString('utf8');
|
|
1009
|
+
|
|
1010
|
+
// Parse workflow name from YAML (try to extract from name: field)
|
|
1011
|
+
let workflowName = 'custom-workflow';
|
|
1012
|
+
const nameMatch = yamlContent.match(/name:\s*["']?([^"'\n]+)["']?/);
|
|
1013
|
+
if (nameMatch) {
|
|
1014
|
+
workflowName = nameMatch[1].trim().replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Save YAML to workflows directory
|
|
1018
|
+
const workflowsDir = path.join(targetDir, '.claude', 'workflows');
|
|
1019
|
+
const workflowFile = path.join(workflowsDir, `${workflowName}.yaml`);
|
|
1020
|
+
|
|
1021
|
+
await fs.ensureDir(workflowsDir);
|
|
1022
|
+
await fs.writeFile(workflowFile, yamlContent, 'utf8');
|
|
1023
|
+
|
|
1024
|
+
console.log(chalk.green(`✅ Workflow YAML saved: ${path.relative(targetDir, workflowFile)}`));
|
|
1025
|
+
|
|
1026
|
+
} catch (yamlError) {
|
|
1027
|
+
console.log(chalk.red(`❌ Error processing YAML: ${yamlError.message}`));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
console.log(chalk.green(`\n✅ Successfully installed ${totalComponents} components!`));
|
|
1032
|
+
console.log(chalk.cyan(`📁 Components installed to: .claude/`));
|
|
1033
|
+
|
|
1034
|
+
if (options.yaml) {
|
|
1035
|
+
console.log(chalk.cyan(`📄 Workflow file created in: .claude/workflows/`));
|
|
1036
|
+
console.log(chalk.cyan(`🚀 Use the workflow file with Claude Code to execute the complete setup`));
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// Track installation
|
|
1040
|
+
trackingService.trackDownload('multi-component', 'batch', {
|
|
1041
|
+
installation_type: 'multi-component',
|
|
1042
|
+
agents_count: components.agents.length,
|
|
1043
|
+
commands_count: components.commands.length,
|
|
1044
|
+
mcps_count: components.mcps.length,
|
|
1045
|
+
settings_count: components.settings.length,
|
|
1046
|
+
hooks_count: components.hooks.length,
|
|
1047
|
+
has_yaml: !!options.yaml,
|
|
1048
|
+
target_directory: path.relative(process.cwd(), targetDir)
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
// Handle prompt execution if provided
|
|
1052
|
+
if (options.prompt) {
|
|
1053
|
+
await handlePromptExecution(options.prompt, targetDir);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
console.log(chalk.red(`❌ Error installing components: ${error.message}`));
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Show available agents organized by category
|
|
1063
|
+
*/
|
|
1064
|
+
async function showAvailableAgents() {
|
|
1065
|
+
console.log(chalk.yellow('\n📋 Available Agents:'));
|
|
1066
|
+
console.log(chalk.gray('Use format: category/agent-name or just agent-name for root level\n'));
|
|
1067
|
+
console.log(chalk.gray('⏳ Fetching latest agents from GitHub...\n'));
|
|
1068
|
+
|
|
1069
|
+
const agents = await getAvailableAgentsFromGitHub();
|
|
1070
|
+
|
|
1071
|
+
// Group agents by category
|
|
1072
|
+
const groupedAgents = agents.reduce((acc, agent) => {
|
|
1073
|
+
const category = agent.category === 'root' ? '🤖 General Agents' : `📁 ${agent.category}`;
|
|
1074
|
+
if (!acc[category]) acc[category] = [];
|
|
1075
|
+
acc[category].push(agent);
|
|
1076
|
+
return acc;
|
|
1077
|
+
}, {});
|
|
1078
|
+
|
|
1079
|
+
// Display agents by category
|
|
1080
|
+
Object.entries(groupedAgents).forEach(([category, categoryAgents]) => {
|
|
1081
|
+
console.log(chalk.cyan(category));
|
|
1082
|
+
categoryAgents.forEach(agent => {
|
|
1083
|
+
console.log(chalk.gray(` • ${agent.path}`));
|
|
1084
|
+
});
|
|
1085
|
+
console.log('');
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
console.log(chalk.blue('Examples:'));
|
|
1089
|
+
console.log(chalk.gray(' cct --agent api-security-audit'));
|
|
1090
|
+
console.log(chalk.gray(' cct --agent deep-research-team/academic-researcher'));
|
|
1091
|
+
console.log('');
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Install workflow from hash
|
|
1096
|
+
*/
|
|
1097
|
+
async function installWorkflow(workflowHash, targetDir, options) {
|
|
1098
|
+
console.log(chalk.blue(`🔧 Installing workflow from hash: ${workflowHash}`));
|
|
1099
|
+
|
|
1100
|
+
try {
|
|
1101
|
+
// Extract hash from format #hash
|
|
1102
|
+
const hash = workflowHash.startsWith('#') ? workflowHash.substring(1) : workflowHash;
|
|
1103
|
+
|
|
1104
|
+
if (!hash || hash.length < 3) {
|
|
1105
|
+
throw new Error('Invalid workflow hash format. Expected format: #hash');
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
console.log(chalk.gray(`📥 Fetching workflow configuration...`));
|
|
1109
|
+
|
|
1110
|
+
// Fetch workflow configuration from a remote service
|
|
1111
|
+
// For now, we'll simulate this by using a local storage approach
|
|
1112
|
+
// In production, this would fetch from a workflow registry
|
|
1113
|
+
const workflowData = await fetchWorkflowData(hash);
|
|
1114
|
+
|
|
1115
|
+
if (!workflowData) {
|
|
1116
|
+
throw new Error(`Workflow with hash "${hash}" not found. Please check the hash and try again.`);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
console.log(chalk.green(`✅ Workflow found: ${workflowData.name}`));
|
|
1120
|
+
console.log(chalk.cyan(`📝 Description: ${workflowData.description}`));
|
|
1121
|
+
console.log(chalk.cyan(`🏷️ Tags: ${workflowData.tags.join(', ')}`));
|
|
1122
|
+
console.log(chalk.cyan(`📊 Steps: ${workflowData.steps.length}`));
|
|
1123
|
+
|
|
1124
|
+
// Install all required components
|
|
1125
|
+
const installPromises = [];
|
|
1126
|
+
|
|
1127
|
+
// Group components by type
|
|
1128
|
+
const agents = workflowData.steps.filter(step => step.type === 'agent');
|
|
1129
|
+
const commands = workflowData.steps.filter(step => step.type === 'command');
|
|
1130
|
+
const mcps = workflowData.steps.filter(step => step.type === 'mcp');
|
|
1131
|
+
|
|
1132
|
+
console.log(chalk.blue(`\n📦 Installing workflow components...`));
|
|
1133
|
+
console.log(chalk.gray(` Agents: ${agents.length}`));
|
|
1134
|
+
console.log(chalk.gray(` Commands: ${commands.length}`));
|
|
1135
|
+
console.log(chalk.gray(` MCPs: ${mcps.length}`));
|
|
1136
|
+
|
|
1137
|
+
// Install components from workflow data (not from GitHub)
|
|
1138
|
+
if (workflowData.components) {
|
|
1139
|
+
console.log(chalk.blue(`📦 Installing components from workflow package...`));
|
|
1140
|
+
|
|
1141
|
+
// Install agents
|
|
1142
|
+
if (workflowData.components.agent) {
|
|
1143
|
+
for (const agent of workflowData.components.agent) {
|
|
1144
|
+
console.log(chalk.gray(` Installing agent: ${agent.name}`));
|
|
1145
|
+
await installComponentFromWorkflow(agent, 'agent', targetDir, options);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Install commands
|
|
1150
|
+
if (workflowData.components.command) {
|
|
1151
|
+
for (const command of workflowData.components.command) {
|
|
1152
|
+
console.log(chalk.gray(` Installing command: ${command.name}`));
|
|
1153
|
+
await installComponentFromWorkflow(command, 'command', targetDir, options);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Install MCPs
|
|
1158
|
+
if (workflowData.components.mcp) {
|
|
1159
|
+
for (const mcp of workflowData.components.mcp) {
|
|
1160
|
+
console.log(chalk.gray(` Installing MCP: ${mcp.name}`));
|
|
1161
|
+
await installComponentFromWorkflow(mcp, 'mcp', targetDir, options);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
} else {
|
|
1165
|
+
// Fallback to old method for legacy workflows
|
|
1166
|
+
console.log(chalk.yellow(`⚠️ Using legacy component installation method...`));
|
|
1167
|
+
|
|
1168
|
+
// Install agents
|
|
1169
|
+
for (const agent of agents) {
|
|
1170
|
+
console.log(chalk.gray(` Installing agent: ${agent.name}`));
|
|
1171
|
+
await installIndividualAgent(agent.path, targetDir, { ...options, silent: true });
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Install commands
|
|
1175
|
+
for (const command of commands) {
|
|
1176
|
+
console.log(chalk.gray(` Installing command: ${command.name}`));
|
|
1177
|
+
await installIndividualCommand(command.path, targetDir, { ...options, silent: true });
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Install MCPs
|
|
1182
|
+
for (const mcp of mcps) {
|
|
1183
|
+
console.log(chalk.gray(` Installing MCP: ${mcp.name}`));
|
|
1184
|
+
await installIndividualMCP(mcp.path, targetDir, { ...options, silent: true });
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Generate and save workflow YAML
|
|
1188
|
+
let yamlContent;
|
|
1189
|
+
if (workflowData.yaml) {
|
|
1190
|
+
// Use YAML from workflow package
|
|
1191
|
+
yamlContent = workflowData.yaml;
|
|
1192
|
+
} else {
|
|
1193
|
+
// Generate YAML (legacy)
|
|
1194
|
+
yamlContent = generateWorkflowYAML(workflowData);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const workflowsDir = path.join(targetDir, '.claude', 'workflows');
|
|
1198
|
+
const workflowFile = path.join(workflowsDir, `${workflowData.name.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.yaml`);
|
|
1199
|
+
|
|
1200
|
+
// Ensure .claude/workflows directory exists
|
|
1201
|
+
await fs.ensureDir(workflowsDir);
|
|
1202
|
+
await fs.writeFile(workflowFile, yamlContent, 'utf8');
|
|
1203
|
+
|
|
1204
|
+
console.log(chalk.green(`\n✅ Workflow "${workflowData.name}" installed successfully!`));
|
|
1205
|
+
console.log(chalk.cyan(`📁 Components installed to: .claude/`));
|
|
1206
|
+
console.log(chalk.cyan(`📄 Workflow file: ${path.relative(targetDir, workflowFile)}`));
|
|
1207
|
+
console.log(chalk.cyan(`🚀 Use the workflow file with Claude Code to execute the complete workflow`));
|
|
1208
|
+
|
|
1209
|
+
// Track successful workflow installation
|
|
1210
|
+
trackingService.trackDownload('workflow', hash, {
|
|
1211
|
+
installation_type: 'workflow',
|
|
1212
|
+
workflow_name: workflowData.name,
|
|
1213
|
+
components_count: workflowData.steps.length,
|
|
1214
|
+
agents_count: agents.length,
|
|
1215
|
+
commands_count: commands.length,
|
|
1216
|
+
mcps_count: mcps.length,
|
|
1217
|
+
target_directory: path.relative(process.cwd(), targetDir)
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
// Handle prompt execution if provided
|
|
1221
|
+
if (options.prompt) {
|
|
1222
|
+
await handlePromptExecution(options.prompt, targetDir);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
console.log(chalk.red(`❌ Error installing workflow: ${error.message}`));
|
|
1227
|
+
|
|
1228
|
+
if (error.message.includes('not found')) {
|
|
1229
|
+
console.log(chalk.yellow('\n💡 Possible solutions:'));
|
|
1230
|
+
console.log(chalk.gray(' • Check that the workflow hash is correct'));
|
|
1231
|
+
console.log(chalk.gray(' • Verify the workflow was generated successfully'));
|
|
1232
|
+
console.log(chalk.gray(' • Try generating a new workflow from the builder'));
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Decompress string with Unicode support
|
|
1239
|
+
*/
|
|
1240
|
+
function decompressString(compressed) {
|
|
1241
|
+
try {
|
|
1242
|
+
// Simple Base64 decoding with Unicode support
|
|
1243
|
+
const decoded = Buffer.from(compressed, 'base64').toString('utf8');
|
|
1244
|
+
// Convert URI encoded characters back
|
|
1245
|
+
return decodeURIComponent(decoded.replace(/(.)/g, function(m, p) {
|
|
1246
|
+
let code = p.charCodeAt(0).toString(16).toUpperCase();
|
|
1247
|
+
if (code.length < 2) code = '0' + code;
|
|
1248
|
+
return '%' + code;
|
|
1249
|
+
}));
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
throw new Error(`Decompression failed: ${error.message}`);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Fetch workflow data from hash
|
|
1257
|
+
* In production, this would fetch from a remote workflow registry
|
|
1258
|
+
* For now, we'll simulate this functionality
|
|
1259
|
+
*/
|
|
1260
|
+
async function fetchWorkflowData(hash) {
|
|
1261
|
+
try {
|
|
1262
|
+
// Check if hash contains encoded data (new format: shortHash_encodedData)
|
|
1263
|
+
if (hash.includes('_')) {
|
|
1264
|
+
console.log(chalk.green('🔓 Decoding workflow from hash...'));
|
|
1265
|
+
|
|
1266
|
+
const [shortHash, encodedData] = hash.split('_', 2);
|
|
1267
|
+
|
|
1268
|
+
if (!encodedData) {
|
|
1269
|
+
throw new Error('Invalid hash format: missing encoded data');
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// Decode compressed data
|
|
1273
|
+
let decodedData;
|
|
1274
|
+
try {
|
|
1275
|
+
// First try to decompress the data (new compressed format)
|
|
1276
|
+
const decompressedString = decompressString(encodedData);
|
|
1277
|
+
decodedData = JSON.parse(decompressedString);
|
|
1278
|
+
} catch (decompressError) {
|
|
1279
|
+
// Fallback to old Base64 format for compatibility
|
|
1280
|
+
try {
|
|
1281
|
+
const decodedString = decodeURIComponent(escape(atob(encodedData)));
|
|
1282
|
+
decodedData = JSON.parse(decodedString);
|
|
1283
|
+
} catch (base64Error) {
|
|
1284
|
+
throw new Error('Failed to decode workflow data from hash');
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// Validate decoded data structure
|
|
1289
|
+
if (!decodedData.metadata || !decodedData.steps || !decodedData.components) {
|
|
1290
|
+
throw new Error('Invalid workflow data structure in hash');
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
console.log(chalk.green('✅ Workflow decoded successfully!'));
|
|
1294
|
+
console.log(chalk.gray(` Short hash: ${shortHash}`));
|
|
1295
|
+
console.log(chalk.gray(` Timestamp: ${decodedData.timestamp}`));
|
|
1296
|
+
console.log(chalk.gray(` Version: ${decodedData.version}`));
|
|
1297
|
+
|
|
1298
|
+
// Convert to expected format
|
|
1299
|
+
return {
|
|
1300
|
+
name: decodedData.metadata.name,
|
|
1301
|
+
description: decodedData.metadata.description,
|
|
1302
|
+
tags: decodedData.metadata.tags || [],
|
|
1303
|
+
version: decodedData.version,
|
|
1304
|
+
hash: shortHash,
|
|
1305
|
+
steps: decodedData.steps,
|
|
1306
|
+
components: decodedData.components,
|
|
1307
|
+
yaml: decodedData.yaml,
|
|
1308
|
+
timestamp: decodedData.timestamp
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// Legacy demo workflows for testing
|
|
1313
|
+
if (hash === 'demo123' || hash === 'abc123test') {
|
|
1314
|
+
console.log(chalk.green('🎯 Demo workflow found! Using sample configuration...'));
|
|
1315
|
+
return {
|
|
1316
|
+
name: 'Full Stack Development Workflow',
|
|
1317
|
+
description: 'Complete workflow for setting up a full-stack development environment with React frontend, Node.js backend, and security auditing',
|
|
1318
|
+
tags: ['development', 'fullstack', 'react', 'security'],
|
|
1319
|
+
version: '1.0.0',
|
|
1320
|
+
hash: hash,
|
|
1321
|
+
steps: [
|
|
1322
|
+
{
|
|
1323
|
+
type: 'agent',
|
|
1324
|
+
name: 'frontend-developer',
|
|
1325
|
+
path: 'development-team/frontend-developer',
|
|
1326
|
+
category: 'development-team',
|
|
1327
|
+
description: 'Setup React frontend development environment'
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
type: 'agent',
|
|
1331
|
+
name: 'backend-architect',
|
|
1332
|
+
path: 'development-team/backend-architect',
|
|
1333
|
+
category: 'development-team',
|
|
1334
|
+
description: 'Configure Node.js backend architecture'
|
|
1335
|
+
},
|
|
1336
|
+
{
|
|
1337
|
+
type: 'command',
|
|
1338
|
+
name: 'generate-tests',
|
|
1339
|
+
path: 'testing/generate-tests',
|
|
1340
|
+
category: 'testing',
|
|
1341
|
+
description: 'Generate comprehensive test suite'
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
type: 'agent',
|
|
1345
|
+
name: 'api-security-audit',
|
|
1346
|
+
path: 'security/api-security-audit',
|
|
1347
|
+
category: 'security',
|
|
1348
|
+
description: 'Perform security audit on APIs'
|
|
1349
|
+
},
|
|
1350
|
+
{
|
|
1351
|
+
type: 'mcp',
|
|
1352
|
+
name: 'github-integration',
|
|
1353
|
+
path: 'integration/github-integration',
|
|
1354
|
+
category: 'integration',
|
|
1355
|
+
description: 'Setup GitHub integration for repository management'
|
|
1356
|
+
}
|
|
1357
|
+
]
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
// This is where we would integrate with a workflow registry API
|
|
1362
|
+
// For now, return null to indicate workflow not found for other hashes
|
|
1363
|
+
console.log(chalk.yellow('\n⚠️ Workflow registry not yet implemented.'));
|
|
1364
|
+
console.log(chalk.gray('To test with demo workflow, use hash: demo123'));
|
|
1365
|
+
console.log(chalk.gray('Example: --workflow "#demo123"'));
|
|
1366
|
+
|
|
1367
|
+
return null;
|
|
1368
|
+
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
console.error(chalk.red(`❌ Error fetching workflow data: ${error.message}`));
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Install component from workflow package data
|
|
1377
|
+
*/
|
|
1378
|
+
async function installComponentFromWorkflow(componentData, type, targetDir, options) {
|
|
1379
|
+
try {
|
|
1380
|
+
let targetPath;
|
|
1381
|
+
let fileName = componentData.name;
|
|
1382
|
+
|
|
1383
|
+
if (type === 'agent') {
|
|
1384
|
+
// Create .claude/agents directory if it doesn't exist
|
|
1385
|
+
const agentsDir = path.join(targetDir, '.claude', 'agents');
|
|
1386
|
+
await fs.ensureDir(agentsDir);
|
|
1387
|
+
|
|
1388
|
+
// For agents, handle category subdirectories
|
|
1389
|
+
if (componentData.category && componentData.category !== 'general') {
|
|
1390
|
+
const categoryDir = path.join(agentsDir, componentData.category);
|
|
1391
|
+
await fs.ensureDir(categoryDir);
|
|
1392
|
+
targetPath = path.join(categoryDir, `${fileName}.md`);
|
|
1393
|
+
} else {
|
|
1394
|
+
targetPath = path.join(agentsDir, `${fileName}.md`);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
} else if (type === 'command') {
|
|
1398
|
+
// Create .claude/commands directory if it doesn't exist
|
|
1399
|
+
const commandsDir = path.join(targetDir, '.claude', 'commands');
|
|
1400
|
+
await fs.ensureDir(commandsDir);
|
|
1401
|
+
targetPath = path.join(commandsDir, `${fileName}.md`);
|
|
1402
|
+
|
|
1403
|
+
} else if (type === 'mcp') {
|
|
1404
|
+
// For MCPs, merge with existing .mcp.json
|
|
1405
|
+
const targetMcpFile = path.join(targetDir, '.mcp.json');
|
|
1406
|
+
let existingConfig = {};
|
|
1407
|
+
|
|
1408
|
+
if (await fs.pathExists(targetMcpFile)) {
|
|
1409
|
+
existingConfig = await fs.readJson(targetMcpFile);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// Parse MCP content and merge
|
|
1413
|
+
let mcpConfig;
|
|
1414
|
+
try {
|
|
1415
|
+
mcpConfig = JSON.parse(componentData.content);
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
throw new Error(`Failed to parse MCP content for ${componentData.name}: ${error.message}`);
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// Remove description field before merging (CLI processing)
|
|
1421
|
+
if (mcpConfig.mcpServers) {
|
|
1422
|
+
for (const serverName in mcpConfig.mcpServers) {
|
|
1423
|
+
if (mcpConfig.mcpServers[serverName] && typeof mcpConfig.mcpServers[serverName] === 'object') {
|
|
1424
|
+
delete mcpConfig.mcpServers[serverName].description;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// Merge configurations
|
|
1430
|
+
const mergedConfig = {
|
|
1431
|
+
...existingConfig,
|
|
1432
|
+
...mcpConfig
|
|
1433
|
+
};
|
|
1434
|
+
|
|
1435
|
+
// Deep merge mcpServers
|
|
1436
|
+
if (existingConfig.mcpServers && mcpConfig.mcpServers) {
|
|
1437
|
+
mergedConfig.mcpServers = {
|
|
1438
|
+
...existingConfig.mcpServers,
|
|
1439
|
+
...mcpConfig.mcpServers
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
await fs.writeJson(targetMcpFile, mergedConfig, { spaces: 2 });
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// Write content for agents and commands
|
|
1448
|
+
if (targetPath) {
|
|
1449
|
+
await fs.writeFile(targetPath, componentData.content, 'utf8');
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
} catch (error) {
|
|
1453
|
+
console.error(chalk.red(`❌ Error installing ${type} "${componentData.name}": ${error.message}`));
|
|
1454
|
+
throw error;
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Generate workflow YAML from workflow data
|
|
1460
|
+
*/
|
|
1461
|
+
function generateWorkflowYAML(workflowData) {
|
|
1462
|
+
const yaml = `#═══════════════════════════════════════════════════════════════════════════════
|
|
1463
|
+
# 🤖 CLAUDE CODE WORKFLOW CONFIGURATION
|
|
1464
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1465
|
+
#
|
|
1466
|
+
# WORKFLOW: ${workflowData.name}
|
|
1467
|
+
# DESCRIPTION: ${workflowData.description}
|
|
1468
|
+
# GENERATED: ${new Date().toISOString()}
|
|
1469
|
+
# HASH: ${workflowData.hash || 'generated'}
|
|
1470
|
+
#
|
|
1471
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1472
|
+
# 📋 INSTRUCTIONS FOR CLAUDE CODE
|
|
1473
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1474
|
+
#
|
|
1475
|
+
# This is a WORKFLOW EXECUTION FILE for Claude Code. When you encounter this file,
|
|
1476
|
+
# you should execute it as a complete automated workflow. Here's how:
|
|
1477
|
+
#
|
|
1478
|
+
# 🎯 EXECUTION BEHAVIOR:
|
|
1479
|
+
# 1. READ this entire YAML file to understand the workflow structure
|
|
1480
|
+
# 2. EXECUTE each step in the exact order listed below (sequential mode)
|
|
1481
|
+
# 3. For AGENTS: Use the agent specified to perform the task described
|
|
1482
|
+
# 4. For COMMANDS: Execute the Claude Code command as specified
|
|
1483
|
+
# 5. For MCPs: Ensure the MCP server is available and use it as needed
|
|
1484
|
+
# 6. STOP if any step fails (on_error: stop)
|
|
1485
|
+
# 7. PROVIDE progress updates after each step completion
|
|
1486
|
+
#
|
|
1487
|
+
# 🔧 STEP EXECUTION PATTERN:
|
|
1488
|
+
# - Announce: "Executing Step X: [step.name]"
|
|
1489
|
+
# - Action: Perform the task using the specified component
|
|
1490
|
+
# - Confirm: "✅ Step X completed successfully"
|
|
1491
|
+
# - Continue to next step
|
|
1492
|
+
#
|
|
1493
|
+
# 💡 IMPORTANT NOTES:
|
|
1494
|
+
# - All components are already installed in .claude/ directories
|
|
1495
|
+
# - Each step builds upon the previous ones
|
|
1496
|
+
# - Follow the descriptions exactly as specified
|
|
1497
|
+
# - Use confirmation_required: true for user approval before starting
|
|
1498
|
+
# - Maintain context and outputs between steps for workflow continuity
|
|
1499
|
+
#
|
|
1500
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1501
|
+
|
|
1502
|
+
name: "${workflowData.name}"
|
|
1503
|
+
description: "${workflowData.description}"
|
|
1504
|
+
tags: [${workflowData.tags.map(tag => `"${tag}"`).join(', ')}]
|
|
1505
|
+
version: "${workflowData.version || '1.0.0'}"
|
|
1506
|
+
|
|
1507
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1508
|
+
# 🚀 WORKFLOW STEPS - EXECUTE IN ORDER
|
|
1509
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1510
|
+
|
|
1511
|
+
steps:
|
|
1512
|
+
${workflowData.steps.map((step, index) => ` - step: ${index + 1}
|
|
1513
|
+
type: ${step.type}
|
|
1514
|
+
name: "${step.name}"
|
|
1515
|
+
path: "${step.path}"
|
|
1516
|
+
category: "${step.category}"
|
|
1517
|
+
description: "${step.description}"
|
|
1518
|
+
|
|
1519
|
+
# CLAUDE CODE INSTRUCTIONS FOR THIS STEP:
|
|
1520
|
+
claude_instructions: |
|
|
1521
|
+
Execute this step using the ${step.type} located at .claude/${step.type}s/${step.name}.${step.type === 'mcp' ? 'json' : 'md'}
|
|
1522
|
+
Task: ${step.description}
|
|
1523
|
+
${step.type === 'agent' ? 'Use this agent to perform the specified task with full context from previous steps.' : ''}
|
|
1524
|
+
${step.type === 'command' ? 'Execute this command with appropriate parameters based on workflow context.' : ''}
|
|
1525
|
+
${step.type === 'mcp' ? 'Ensure MCP server is running and utilize its capabilities for the task.' : ''}
|
|
1526
|
+
|
|
1527
|
+
action_template: |
|
|
1528
|
+
echo "🔄 Executing Step ${index + 1}: ${step.name}"
|
|
1529
|
+
echo "📝 Task: ${step.description}"
|
|
1530
|
+
echo "🎯 Using ${step.type}: ${step.path}"
|
|
1531
|
+
# [CLAUDE CODE WILL REPLACE THIS WITH ACTUAL EXECUTION]
|
|
1532
|
+
echo "✅ Step ${index + 1} completed successfully"
|
|
1533
|
+
`).join('\n')}
|
|
1534
|
+
|
|
1535
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1536
|
+
# ⚙️ EXECUTION CONFIGURATION
|
|
1537
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1538
|
+
|
|
1539
|
+
execution:
|
|
1540
|
+
mode: "sequential" # Execute steps one by one, in order
|
|
1541
|
+
on_error: "stop" # Stop workflow if any step fails
|
|
1542
|
+
timeout: 300 # Maximum time per step (5 minutes)
|
|
1543
|
+
continue_on_warning: true # Continue if warnings occur
|
|
1544
|
+
save_outputs: true # Save outputs between steps for context
|
|
1545
|
+
|
|
1546
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1547
|
+
# 📦 INSTALLED COMPONENTS REFERENCE
|
|
1548
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1549
|
+
|
|
1550
|
+
components:
|
|
1551
|
+
agents: [${workflowData.steps.filter(s => s.type === 'agent').map(s => `"${s.path}"`).join(', ')}]
|
|
1552
|
+
commands: [${workflowData.steps.filter(s => s.type === 'command').map(s => `"${s.path}"`).join(', ')}]
|
|
1553
|
+
mcps: [${workflowData.steps.filter(s => s.type === 'mcp').map(s => `"${s.path}"`).join(', ')}]
|
|
1554
|
+
|
|
1555
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1556
|
+
# 🤖 CLAUDE CODE INTEGRATION SETTINGS
|
|
1557
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1558
|
+
|
|
1559
|
+
claudecode:
|
|
1560
|
+
workflow_mode: true # Enable workflow execution mode
|
|
1561
|
+
auto_execute: false # Require user confirmation before starting
|
|
1562
|
+
confirmation_required: true # Ask user before each step
|
|
1563
|
+
show_progress: true # Display progress indicators
|
|
1564
|
+
save_context: true # Maintain context between steps
|
|
1565
|
+
|
|
1566
|
+
# WORKFLOW EXECUTION INSTRUCTIONS FOR CLAUDE:
|
|
1567
|
+
execution_instructions: |
|
|
1568
|
+
When executing this workflow:
|
|
1569
|
+
|
|
1570
|
+
1. 🎯 PREPARATION PHASE:
|
|
1571
|
+
- Confirm all components are installed in .claude/ directories
|
|
1572
|
+
- Verify user wants to execute this workflow
|
|
1573
|
+
- Explain what will happen in each step
|
|
1574
|
+
|
|
1575
|
+
2. 🚀 EXECUTION PHASE:
|
|
1576
|
+
- Execute each step sequentially
|
|
1577
|
+
- Use the exact agent/command/mcp specified for each step
|
|
1578
|
+
- Maintain outputs and context between steps
|
|
1579
|
+
- Provide clear progress updates
|
|
1580
|
+
|
|
1581
|
+
3. ✅ COMPLETION PHASE:
|
|
1582
|
+
- Summarize what was accomplished
|
|
1583
|
+
- Highlight any outputs or files created
|
|
1584
|
+
- Suggest next steps if applicable
|
|
1585
|
+
|
|
1586
|
+
4. ❌ ERROR HANDLING:
|
|
1587
|
+
- If a step fails, stop execution immediately
|
|
1588
|
+
- Provide clear error message and suggested fixes
|
|
1589
|
+
- Offer to retry the failed step after fixes
|
|
1590
|
+
|
|
1591
|
+
Remember: This workflow was designed to work as a complete automation.
|
|
1592
|
+
Each step builds upon the previous ones. Execute with confidence!
|
|
1593
|
+
|
|
1594
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1595
|
+
# 📋 WORKFLOW SUMMARY
|
|
1596
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1597
|
+
#
|
|
1598
|
+
# This workflow will execute ${workflowData.steps.length} steps in sequence:
|
|
1599
|
+
${workflowData.steps.map((step, index) => `# ${index + 1}. ${step.description} (${step.type}: ${step.name})`).join('\n')}
|
|
1600
|
+
#
|
|
1601
|
+
# Total estimated time: ${Math.ceil(workflowData.steps.length * 2)} minutes
|
|
1602
|
+
# Components required: ${workflowData.steps.filter(s => s.type === 'agent').length} agents, ${workflowData.steps.filter(s => s.type === 'command').length} commands, ${workflowData.steps.filter(s => s.type === 'mcp').length} MCPs
|
|
1603
|
+
#═══════════════════════════════════════════════════════════════════════════════
|
|
1604
|
+
`;
|
|
1605
|
+
|
|
1606
|
+
return yaml;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
/**
|
|
1610
|
+
* Handle prompt execution in Claude Code
|
|
1611
|
+
*/
|
|
1612
|
+
async function handlePromptExecution(prompt, targetDir) {
|
|
1613
|
+
console.log(chalk.blue('\n🎯 Prompt execution requested...'));
|
|
1614
|
+
|
|
1615
|
+
// Ask user if they want to execute the prompt in Claude Code
|
|
1616
|
+
const { shouldExecute } = await inquirer.prompt([{
|
|
1617
|
+
type: 'confirm',
|
|
1618
|
+
name: 'shouldExecute',
|
|
1619
|
+
message: `Do you want to execute this prompt in Claude Code?\n${chalk.cyan(`"${prompt}"`)}`,
|
|
1620
|
+
default: true
|
|
1621
|
+
}]);
|
|
1622
|
+
|
|
1623
|
+
if (!shouldExecute) {
|
|
1624
|
+
console.log(chalk.yellow('⏹️ Prompt execution skipped by user.'));
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
console.log(chalk.blue('🚀 Preparing to launch Claude Code with your prompt...'));
|
|
1629
|
+
|
|
1630
|
+
try {
|
|
1631
|
+
// Check if claude command is available in PATH
|
|
1632
|
+
const { spawn } = require('child_process');
|
|
1633
|
+
const open = require('open');
|
|
1634
|
+
|
|
1635
|
+
// First try to execute claude command directly
|
|
1636
|
+
const claudeProcess = spawn('claude', [prompt], {
|
|
1637
|
+
cwd: targetDir,
|
|
1638
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
1639
|
+
shell: true
|
|
1640
|
+
});
|
|
1641
|
+
|
|
1642
|
+
claudeProcess.on('error', async (error) => {
|
|
1643
|
+
if (error.code === 'ENOENT') {
|
|
1644
|
+
// Claude command not found, try alternative approaches
|
|
1645
|
+
console.log(chalk.yellow('⚠️ Claude Code CLI not found in PATH.'));
|
|
1646
|
+
console.log(chalk.blue('💡 Alternative ways to execute your prompt:'));
|
|
1647
|
+
console.log(chalk.gray(' 1. Install Claude Code CLI: https://claude.ai/code'));
|
|
1648
|
+
console.log(chalk.gray(' 2. Copy and paste this prompt in Claude Code interface:'));
|
|
1649
|
+
console.log(chalk.cyan(`\n "${prompt}"\n`));
|
|
1650
|
+
|
|
1651
|
+
// Ask if user wants to open Claude Code web interface
|
|
1652
|
+
const { openWeb } = await inquirer.prompt([{
|
|
1653
|
+
type: 'confirm',
|
|
1654
|
+
name: 'openWeb',
|
|
1655
|
+
message: 'Would you like to open Claude Code in your browser?',
|
|
1656
|
+
default: true
|
|
1657
|
+
}]);
|
|
1658
|
+
|
|
1659
|
+
if (openWeb) {
|
|
1660
|
+
await open('https://claude.ai/code');
|
|
1661
|
+
console.log(chalk.green('✅ Claude Code opened in your browser!'));
|
|
1662
|
+
console.log(chalk.cyan(`Don't forget to paste your prompt: "${prompt}"`));
|
|
1663
|
+
}
|
|
1664
|
+
} else {
|
|
1665
|
+
throw error;
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
claudeProcess.on('close', (code) => {
|
|
1670
|
+
if (code === 0) {
|
|
1671
|
+
console.log(chalk.green('✅ Claude Code executed successfully!'));
|
|
1672
|
+
} else if (code !== null) {
|
|
1673
|
+
console.log(chalk.yellow(`⚠️ Claude Code exited with code ${code}`));
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
console.log(chalk.red(`❌ Error executing prompt: ${error.message}`));
|
|
1679
|
+
console.log(chalk.blue('💡 You can manually execute this prompt in Claude Code:'));
|
|
1680
|
+
console.log(chalk.cyan(`"${prompt}"`));
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
475
1684
|
module.exports = { createClaudeConfig, showMainMenu };
|