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.
Files changed (101) hide show
  1. package/README.md +7 -7
  2. package/bin/create-claude-config.js +17 -8
  3. package/package.json +2 -3
  4. package/src/analytics/core/AgentAnalyzer.js +17 -3
  5. package/src/analytics/core/ProcessDetector.js +23 -7
  6. package/src/analytics/core/StateCalculator.js +102 -33
  7. package/src/analytics/data/DataCache.js +7 -7
  8. package/src/analytics-web/chats_mobile.html +2590 -0
  9. package/src/analytics-web/components/App.js +10 -10
  10. package/src/analytics-web/components/SessionTimer.js +1 -1
  11. package/src/analytics-web/components/Sidebar.js +5 -14
  12. package/src/analytics-web/index.html +932 -78
  13. package/src/analytics.js +263 -5
  14. package/src/chats-mobile.js +682 -0
  15. package/src/claude-api-proxy.js +460 -0
  16. package/src/file-operations.js +239 -36
  17. package/src/health-check.js +310 -0
  18. package/src/index.js +1245 -36
  19. package/src/tracking-service.js +31 -34
  20. package/components/agents/api-security-audit.md +0 -92
  21. package/components/agents/database-optimization.md +0 -94
  22. package/components/agents/react-performance-optimization.md +0 -64
  23. package/components/commands/check-file.md +0 -53
  24. package/components/commands/generate-tests.md +0 -68
  25. package/components/mcps/deepgraph-nextjs.json +0 -12
  26. package/components/mcps/deepgraph-react.json +0 -12
  27. package/components/mcps/deepgraph-typescript.json +0 -12
  28. package/components/mcps/deepgraph-vue.json +0 -12
  29. package/components/mcps/filesystem-access.json +0 -12
  30. package/components/mcps/github-integration.json +0 -11
  31. package/components/mcps/memory-integration.json +0 -8
  32. package/components/mcps/mysql-integration.json +0 -11
  33. package/components/mcps/postgresql-integration.json +0 -11
  34. package/components/mcps/web-fetch.json +0 -8
  35. package/src/analytics-web/components/AgentsPage.js +0 -4761
  36. package/templates/common/.claude/commands/git-workflow.md +0 -239
  37. package/templates/common/.claude/commands/project-setup.md +0 -316
  38. package/templates/common/.mcp.json +0 -41
  39. package/templates/common/CLAUDE.md +0 -109
  40. package/templates/common/README.md +0 -96
  41. package/templates/go/.mcp.json +0 -78
  42. package/templates/go/README.md +0 -25
  43. package/templates/javascript-typescript/.claude/commands/api-endpoint.md +0 -51
  44. package/templates/javascript-typescript/.claude/commands/debug.md +0 -52
  45. package/templates/javascript-typescript/.claude/commands/lint.md +0 -48
  46. package/templates/javascript-typescript/.claude/commands/npm-scripts.md +0 -48
  47. package/templates/javascript-typescript/.claude/commands/refactor.md +0 -55
  48. package/templates/javascript-typescript/.claude/commands/test.md +0 -61
  49. package/templates/javascript-typescript/.claude/commands/typescript-migrate.md +0 -51
  50. package/templates/javascript-typescript/.claude/settings.json +0 -142
  51. package/templates/javascript-typescript/.mcp.json +0 -80
  52. package/templates/javascript-typescript/CLAUDE.md +0 -185
  53. package/templates/javascript-typescript/README.md +0 -259
  54. package/templates/javascript-typescript/examples/angular-app/.claude/commands/components.md +0 -63
  55. package/templates/javascript-typescript/examples/angular-app/.claude/commands/services.md +0 -62
  56. package/templates/javascript-typescript/examples/node-api/.claude/commands/api-endpoint.md +0 -46
  57. package/templates/javascript-typescript/examples/node-api/.claude/commands/database.md +0 -56
  58. package/templates/javascript-typescript/examples/node-api/.claude/commands/middleware.md +0 -61
  59. package/templates/javascript-typescript/examples/node-api/.claude/commands/route.md +0 -57
  60. package/templates/javascript-typescript/examples/node-api/CLAUDE.md +0 -102
  61. package/templates/javascript-typescript/examples/react-app/.claude/commands/component.md +0 -29
  62. package/templates/javascript-typescript/examples/react-app/.claude/commands/hooks.md +0 -44
  63. package/templates/javascript-typescript/examples/react-app/.claude/commands/state-management.md +0 -45
  64. package/templates/javascript-typescript/examples/react-app/CLAUDE.md +0 -81
  65. package/templates/javascript-typescript/examples/react-app/agents/react-performance-optimization.md +0 -530
  66. package/templates/javascript-typescript/examples/react-app/agents/react-state-management.md +0 -295
  67. package/templates/javascript-typescript/examples/vue-app/.claude/commands/components.md +0 -46
  68. package/templates/javascript-typescript/examples/vue-app/.claude/commands/composables.md +0 -51
  69. package/templates/python/.claude/commands/lint.md +0 -111
  70. package/templates/python/.claude/commands/test.md +0 -73
  71. package/templates/python/.claude/settings.json +0 -153
  72. package/templates/python/.mcp.json +0 -78
  73. package/templates/python/CLAUDE.md +0 -276
  74. package/templates/python/examples/django-app/.claude/commands/admin.md +0 -264
  75. package/templates/python/examples/django-app/.claude/commands/django-model.md +0 -124
  76. package/templates/python/examples/django-app/.claude/commands/views.md +0 -222
  77. package/templates/python/examples/django-app/CLAUDE.md +0 -313
  78. package/templates/python/examples/django-app/agents/django-api-security.md +0 -642
  79. package/templates/python/examples/django-app/agents/django-database-optimization.md +0 -752
  80. package/templates/python/examples/fastapi-app/.claude/commands/api-endpoints.md +0 -513
  81. package/templates/python/examples/fastapi-app/.claude/commands/auth.md +0 -775
  82. package/templates/python/examples/fastapi-app/.claude/commands/database.md +0 -657
  83. package/templates/python/examples/fastapi-app/.claude/commands/deployment.md +0 -160
  84. package/templates/python/examples/fastapi-app/.claude/commands/testing.md +0 -927
  85. package/templates/python/examples/fastapi-app/CLAUDE.md +0 -229
  86. package/templates/python/examples/flask-app/.claude/commands/app-factory.md +0 -384
  87. package/templates/python/examples/flask-app/.claude/commands/blueprint.md +0 -243
  88. package/templates/python/examples/flask-app/.claude/commands/database.md +0 -410
  89. package/templates/python/examples/flask-app/.claude/commands/deployment.md +0 -620
  90. package/templates/python/examples/flask-app/.claude/commands/flask-route.md +0 -217
  91. package/templates/python/examples/flask-app/.claude/commands/testing.md +0 -559
  92. package/templates/python/examples/flask-app/CLAUDE.md +0 -391
  93. package/templates/ruby/.claude/commands/model.md +0 -360
  94. package/templates/ruby/.claude/commands/test.md +0 -480
  95. package/templates/ruby/.claude/settings.json +0 -146
  96. package/templates/ruby/.mcp.json +0 -83
  97. package/templates/ruby/CLAUDE.md +0 -284
  98. package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +0 -490
  99. package/templates/ruby/examples/rails-app/CLAUDE.md +0 -376
  100. package/templates/rust/.mcp.json +0 -78
  101. package/templates/rust/README.md +0 -26
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 Dashboard - View and analyze your Claude conversations',
34
+ name: '💬 Chats Mobile - AI-first mobile interface for conversations',
34
35
  value: 'chats',
35
- short: 'Chats Dashboard'
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 Dashboard...'));
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
- // Handle individual component installation
94
- if (options.agent) {
95
- await installIndividualAgent(options.agent, targetDir, options);
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
- if (options.command) {
100
- await installIndividualCommand(options.command, targetDir, options);
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
- if (options.mcp) {
105
- await installIndividualMCP(options.mcp, targetDir, options);
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/agents dashboard
135
- if (options.chats || options.agents) {
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/docu/'));
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
- // Download agent directly from GitHub
309
- const githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/agents/${agentName}.md`;
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
- console.log(chalk.yellow('Available agents: api-security-audit, database-optimization, react-performance-optimization'));
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
- const targetFile = path.join(agentsDir, `${agentName}.md`);
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
- console.log(chalk.green(`✅ Agent "${agentName}" installed successfully!`));
333
- console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
334
- console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
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
- // Download command directly from GitHub
353
- const githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/commands/${commandName}.md`;
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
- const targetFile = path.join(commandsDir, `${commandName}.md`);
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
- console.log(chalk.green(`✅ Command "${commandName}" installed successfully!`));
377
- console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
378
- console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
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
- // Download MCP directly from GitHub
397
- const githubUrl = `https://raw.githubusercontent.com/davila7/claude-code-templates/main/cli-tool/components/mcps/${mcpName}.json`;
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
- console.log(chalk.green(`✅ MCP "${mcpName}" installed successfully!`));
432
- console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetMcpFile)}`));
433
- console.log(chalk.cyan(`📦 Downloaded from: ${githubUrl}`));
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 };