claude-code-templates 1.13.2 → 1.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,307 @@
1
+ /**
2
+ * HeaderComponent - Unified header for all analytics pages
3
+ * Provides consistent branding, navigation, and controls across the application
4
+ */
5
+ class HeaderComponent {
6
+ constructor(container, options = {}) {
7
+ this.container = container;
8
+ this.options = {
9
+ title: options.title || 'Claude Code Analytics Dashboard',
10
+ subtitle: options.subtitle || 'Real-time monitoring and analytics for Claude Code sessions',
11
+ showVersionBadge: options.showVersionBadge !== false,
12
+ showLastUpdate: options.showLastUpdate !== false,
13
+ showThemeSwitch: options.showThemeSwitch !== false,
14
+ showGitHubLink: options.showGitHubLink !== false,
15
+ version: options.version || 'v1.13.2', // Default fallback
16
+ customActions: options.customActions || [],
17
+ dataService: options.dataService || null, // DataService for dynamic version loading
18
+ ...options
19
+ };
20
+
21
+ this.lastUpdateTime = null;
22
+ this.updateInterval = null;
23
+ this.actualVersion = this.options.version; // Will be updated dynamically
24
+ }
25
+
26
+ /**
27
+ * Render the header component
28
+ */
29
+ render() {
30
+ const headerHTML = `
31
+ <div class="page-header">
32
+ <div class="header-content">
33
+ <div class="header-left">
34
+ <div class="status-header">
35
+ <span class="session-timer-status-dot active" id="session-status-dot"></span>
36
+ <h1 class="page-title">
37
+ ${this.options.title}
38
+ ${this.options.showVersionBadge ? `<span class="version-badge" id="version-badge">${this.actualVersion}</span>` : ''}
39
+ </h1>
40
+ </div>
41
+ <div class="page-subtitle">
42
+ ${this.options.subtitle}
43
+ </div>
44
+ ${this.options.showLastUpdate ? `
45
+ <div class="last-update-header">
46
+ <span class="last-update-label">last update:</span>
47
+ <span id="last-update-header-text">Never</span>
48
+ </div>
49
+ ` : ''}
50
+ </div>
51
+ <div class="header-right">
52
+ ${this.options.showThemeSwitch ? `
53
+ <div class="theme-switch-container" title="Toggle light/dark theme">
54
+ <div class="theme-switch" id="header-theme-switch">
55
+ <div class="theme-switch-track">
56
+ <div class="theme-switch-thumb" id="header-theme-switch-thumb">
57
+ <span class="theme-switch-icon">🌙</span>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ ` : ''}
63
+ ${this.options.showGitHubLink ? `
64
+ <a href="https://github.com/davila7/claude-code-templates" target="_blank" class="github-link" title="Star on GitHub">
65
+ <span class="github-icon">⭐</span>
66
+ Star on GitHub
67
+ </a>
68
+ ` : ''}
69
+ ${this.renderCustomActions()}
70
+ </div>
71
+ </div>
72
+ </div>
73
+ `;
74
+
75
+ this.container.innerHTML = headerHTML;
76
+ this.bindEvents();
77
+ this.initializeTheme();
78
+
79
+ if (this.options.showLastUpdate) {
80
+ this.updateLastUpdateTime();
81
+ this.startUpdateInterval();
82
+ }
83
+
84
+ // Load version dynamically if DataService is available
85
+ if (this.options.dataService) {
86
+ this.loadVersion();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Render custom actions in header
92
+ */
93
+ renderCustomActions() {
94
+ if (!this.options.customActions || this.options.customActions.length === 0) {
95
+ return '';
96
+ }
97
+
98
+ return this.options.customActions.map(action => `
99
+ <button class="header-action-btn" id="${action.id}" title="${action.title || action.label}">
100
+ ${action.icon ? `<span class="btn-icon">${action.icon}</span>` : ''}
101
+ ${action.label}
102
+ </button>
103
+ `).join('');
104
+ }
105
+
106
+ /**
107
+ * Bind event listeners
108
+ */
109
+ bindEvents() {
110
+ // Theme toggle
111
+ if (this.options.showThemeSwitch) {
112
+ const themeSwitch = this.container.querySelector('#header-theme-switch');
113
+ if (themeSwitch) {
114
+ themeSwitch.addEventListener('click', () => this.toggleTheme());
115
+ }
116
+ }
117
+
118
+ // Custom action handlers
119
+ this.options.customActions.forEach(action => {
120
+ const btn = this.container.querySelector(`#${action.id}`);
121
+ if (btn && action.handler) {
122
+ btn.addEventListener('click', action.handler);
123
+ }
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Initialize theme from localStorage
129
+ */
130
+ initializeTheme() {
131
+ if (!this.options.showThemeSwitch) return;
132
+
133
+ const savedTheme = localStorage.getItem('claude-analytics-theme') || 'dark';
134
+ const body = document.body;
135
+ const headerThumb = this.container.querySelector('#header-theme-switch-thumb');
136
+ const headerIcon = headerThumb?.querySelector('.theme-switch-icon');
137
+
138
+ body.setAttribute('data-theme', savedTheme);
139
+ if (headerThumb && headerIcon) {
140
+ if (savedTheme === 'light') {
141
+ headerThumb.classList.add('light');
142
+ headerIcon.textContent = '☀️';
143
+ } else {
144
+ headerThumb.classList.remove('light');
145
+ headerIcon.textContent = '🌙';
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Toggle theme between light and dark
152
+ */
153
+ toggleTheme() {
154
+ const body = document.body;
155
+ const headerThumb = this.container.querySelector('#header-theme-switch-thumb');
156
+ const headerIcon = headerThumb?.querySelector('.theme-switch-icon');
157
+
158
+ // Also sync with global theme switch if exists
159
+ const globalThumb = document.getElementById('themeSwitchThumb');
160
+ const globalIcon = globalThumb?.querySelector('.theme-switch-icon');
161
+
162
+ const isLight = body.getAttribute('data-theme') === 'light';
163
+ const newTheme = isLight ? 'dark' : 'light';
164
+
165
+ body.setAttribute('data-theme', newTheme);
166
+
167
+ // Update header theme switch
168
+ if (headerThumb && headerIcon) {
169
+ headerThumb.classList.toggle('light', newTheme === 'light');
170
+ headerIcon.textContent = newTheme === 'light' ? '☀️' : '🌙';
171
+ }
172
+
173
+ // Sync with global theme switch
174
+ if (globalThumb && globalIcon) {
175
+ globalThumb.classList.toggle('light', newTheme === 'light');
176
+ globalIcon.textContent = newTheme === 'light' ? '☀️' : '🌙';
177
+ }
178
+
179
+ localStorage.setItem('claude-analytics-theme', newTheme);
180
+ }
181
+
182
+ /**
183
+ * Update last update time
184
+ */
185
+ updateLastUpdateTime() {
186
+ if (!this.options.showLastUpdate) return;
187
+
188
+ const currentTime = new Date().toLocaleTimeString();
189
+ const lastUpdateText = this.container.querySelector('#last-update-header-text');
190
+
191
+ if (lastUpdateText) {
192
+ lastUpdateText.textContent = currentTime;
193
+ }
194
+
195
+ this.lastUpdateTime = currentTime;
196
+ }
197
+
198
+ /**
199
+ * Start periodic update of timestamp
200
+ */
201
+ startUpdateInterval() {
202
+ if (this.updateInterval) {
203
+ clearInterval(this.updateInterval);
204
+ }
205
+
206
+ // Update every 30 seconds
207
+ this.updateInterval = setInterval(() => {
208
+ this.updateLastUpdateTime();
209
+ }, 30000);
210
+ }
211
+
212
+ /**
213
+ * Stop update interval
214
+ */
215
+ stopUpdateInterval() {
216
+ if (this.updateInterval) {
217
+ clearInterval(this.updateInterval);
218
+ this.updateInterval = null;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Load version dynamically from backend
224
+ */
225
+ async loadVersion() {
226
+ if (!this.options.dataService) return;
227
+
228
+ try {
229
+ const versionData = await this.options.dataService.getVersion();
230
+ if (versionData && versionData.version) {
231
+ this.actualVersion = `v${versionData.version}`;
232
+ this.updateVersionBadge();
233
+ }
234
+ } catch (error) {
235
+ console.error('Error loading version:', error);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Update version badge in the DOM
241
+ */
242
+ updateVersionBadge() {
243
+ const versionBadge = this.container.querySelector('#version-badge');
244
+ if (versionBadge) {
245
+ versionBadge.textContent = this.actualVersion;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Update header title
251
+ * @param {string} title - New title
252
+ */
253
+ updateTitle(title) {
254
+ this.options.title = title;
255
+ const titleElement = this.container.querySelector('.page-title');
256
+ if (titleElement) {
257
+ titleElement.innerHTML = `
258
+ ${title}
259
+ ${this.options.showVersionBadge ? `<span class="version-badge" id="version-badge">${this.actualVersion}</span>` : ''}
260
+ `;
261
+ }
262
+ }
263
+
264
+ /**
265
+ * Update header subtitle
266
+ * @param {string} subtitle - New subtitle
267
+ */
268
+ updateSubtitle(subtitle) {
269
+ this.options.subtitle = subtitle;
270
+ const subtitleElement = this.container.querySelector('.page-subtitle');
271
+ if (subtitleElement) {
272
+ subtitleElement.textContent = subtitle;
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Update status dot state
278
+ * @param {boolean} active - Whether status should be active
279
+ */
280
+ updateStatusDot(active) {
281
+ const statusDot = this.container.querySelector('#session-status-dot');
282
+ if (statusDot) {
283
+ statusDot.classList.toggle('active', active);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Get current theme
289
+ * @returns {string} Current theme ('light' or 'dark')
290
+ */
291
+ getCurrentTheme() {
292
+ return document.body.getAttribute('data-theme') || 'dark';
293
+ }
294
+
295
+ /**
296
+ * Destroy header component
297
+ */
298
+ destroy() {
299
+ this.stopUpdateInterval();
300
+ this.container.innerHTML = '';
301
+ }
302
+ }
303
+
304
+ // Export for module use
305
+ if (typeof module !== 'undefined' && module.exports) {
306
+ module.exports = HeaderComponent;
307
+ }
@@ -12,6 +12,7 @@
12
12
  <script src="services/StateService.js"></script>
13
13
 
14
14
  <!-- Component Scripts -->
15
+ <script src="components/HeaderComponent.js"></script>
15
16
  <script src="components/Charts.js"></script>
16
17
  <!-- Dashboard.js removed - deprecated architecture -->
17
18
  <script src="components/SessionTimer.js"></script>
@@ -2644,6 +2645,7 @@
2644
2645
  flex: 1;
2645
2646
  display: flex;
2646
2647
  flex-direction: column;
2648
+ margin-left: 56px;
2647
2649
  transition: margin-left 0.3s ease;
2648
2650
  }
2649
2651
 
@@ -2662,7 +2664,9 @@
2662
2664
  display: flex;
2663
2665
  flex-direction: column;
2664
2666
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2665
- position: relative;
2667
+ position: fixed;
2668
+ top: 0;
2669
+ left: 0;
2666
2670
  z-index: 1000;
2667
2671
  box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
2668
2672
  overflow: visible;
@@ -2673,6 +2677,10 @@
2673
2677
  background: var(--bg-secondary);
2674
2678
  border-right: 1px solid var(--text-accent);
2675
2679
  box-shadow: 4px 0 16px rgba(213, 116, 85, 0.1);
2680
+ position: fixed;
2681
+ top: 0;
2682
+ left: 0;
2683
+ height: 100vh;
2676
2684
  }
2677
2685
 
2678
2686
  .sidebar-header {
@@ -4616,32 +4624,7 @@
4616
4624
  margin: 0 auto;
4617
4625
  }
4618
4626
 
4619
- /* App Layout Styles (App.js + Sidebar.js) */
4620
- .app {
4621
- display: flex;
4622
- min-height: 100vh;
4623
- background: var(--bg-primary);
4624
- }
4625
-
4626
- .app-sidebar {
4627
- width: 60px;
4628
- background: var(--bg-secondary);
4629
- border-right: 1px solid var(--border-primary);
4630
- transition: width 0.3s ease;
4631
- }
4632
-
4633
- .app-main {
4634
- flex: 1;
4635
- display: flex;
4636
- flex-direction: column;
4637
- min-width: 0;
4638
- }
4639
-
4640
- .app-content {
4641
- flex: 1;
4642
- padding: 20px;
4643
- overflow-y: auto;
4644
- }
4627
+ /* App Layout Styles (App.js + Sidebar.js) - Duplicates removed, using definitions above */
4645
4628
 
4646
4629
  /* Metrics Section */
4647
4630
  .metrics-section {
@@ -409,6 +409,19 @@ class DataService {
409
409
  this.setupWebSocketIntegration();
410
410
  }
411
411
 
412
+ /**
413
+ * Get application version from backend
414
+ * @returns {Promise<Object>} Version information
415
+ */
416
+ async getVersion() {
417
+ try {
418
+ return await this.cachedFetch('/api/version', 300000); // Cache for 5 minutes
419
+ } catch (error) {
420
+ console.error('Error fetching version:', error);
421
+ return { version: '1.13.2', name: 'claude-code-templates' }; // Fallback
422
+ }
423
+ }
424
+
412
425
  /**
413
426
  * Get cache statistics
414
427
  * @returns {Object} Cache statistics
@@ -394,58 +394,62 @@ class HealthChecker {
394
394
  }
395
395
 
396
396
  checkAuthentication() {
397
+ const homeDir = os.homedir();
398
+
399
+ // Check for Claude Code OAuth authentication in ~/.claude.json
400
+ const claudeJsonPath = path.join(homeDir, '.claude.json');
401
+
397
402
  try {
398
- // Try to run claude command to check authentication status
399
- const output = execSync('claude auth status 2>&1', {
400
- encoding: 'utf8',
401
- stdio: 'pipe',
402
- timeout: 5000
403
- });
403
+ if (fs.existsSync(claudeJsonPath)) {
404
+ const claudeConfig = JSON.parse(fs.readFileSync(claudeJsonPath, 'utf8'));
405
+
406
+ // Check for OAuth authentication
407
+ if (claudeConfig.oauthAccount && claudeConfig.oauthAccount.accountUuid) {
408
+ const email = claudeConfig.oauthAccount.emailAddress || 'OAuth user';
409
+ return {
410
+ status: 'pass',
411
+ message: `Authenticated via OAuth (${email})`
412
+ };
413
+ }
414
+
415
+ // Check for API key authentication
416
+ if (claudeConfig.apiKey) {
417
+ return {
418
+ status: 'pass',
419
+ message: 'Authenticated via API key'
420
+ };
421
+ }
422
+ }
404
423
 
405
- if (output.includes('authenticated') || output.includes('logged in') || output.includes('active')) {
424
+ // Check for environment variable API key
425
+ if (process.env.ANTHROPIC_API_KEY) {
406
426
  return {
407
427
  status: 'pass',
408
- message: 'Authenticated and active'
428
+ message: 'Authenticated via ANTHROPIC_API_KEY environment variable'
409
429
  };
410
430
  }
411
431
 
412
- // If command runs but no clear authentication status
413
- return {
414
- status: 'warn',
415
- message: 'Authentication status unclear'
416
- };
417
-
418
- } catch (error) {
419
- // Try alternative method - check for session/config files
420
- const homeDir = os.homedir();
421
- const possibleAuthPaths = [
422
- path.join(homeDir, '.claude', 'session'),
423
- path.join(homeDir, '.claude', 'config'),
424
- path.join(homeDir, '.config', 'claude', 'session'),
425
- path.join(homeDir, '.config', 'claude', 'config'),
426
- path.join(homeDir, '.anthropic', 'session'),
427
- path.join(homeDir, '.anthropic', 'config')
428
- ];
429
-
430
- for (const authPath of possibleAuthPaths) {
431
- if (fs.existsSync(authPath)) {
432
- try {
433
- const stats = fs.statSync(authPath);
434
- // Check if file was modified recently (within last 30 days)
435
- const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
436
- if (stats.mtime > thirtyDaysAgo) {
437
- return {
438
- status: 'pass',
439
- message: 'Authentication session found'
440
- };
441
- }
442
- } catch (statError) {
443
- // Continue to next path
444
- }
445
- }
432
+ // Try to check if we can make a simple claude command
433
+ try {
434
+ execSync('claude --version', {
435
+ encoding: 'utf8',
436
+ stdio: 'pipe',
437
+ timeout: 3000
438
+ });
439
+
440
+ return {
441
+ status: 'warn',
442
+ message: 'Claude CLI available but authentication not configured'
443
+ };
444
+ } catch (cliError) {
445
+ return {
446
+ status: 'fail',
447
+ message: 'Claude CLI not available or not authenticated'
448
+ };
446
449
  }
447
450
 
448
- // Try to check if we can make a simple claude command
451
+ } catch (error) {
452
+ // If we can't read the config file, check if CLI is at least installed
449
453
  try {
450
454
  execSync('claude --version', {
451
455
  encoding: 'utf8',
@@ -460,7 +464,7 @@ class HealthChecker {
460
464
  } catch (cliError) {
461
465
  return {
462
466
  status: 'fail',
463
- message: 'Claude CLI not available or not authenticated'
467
+ message: 'Claude CLI not available or authentication check failed'
464
468
  };
465
469
  }
466
470
  }
package/src/index.js CHANGED
@@ -14,6 +14,7 @@ const { runHookStats } = require('./hook-stats');
14
14
  const { runMCPStats } = require('./mcp-stats');
15
15
  const { runAnalytics } = require('./analytics');
16
16
  const { runHealthCheck } = require('./health-check');
17
+ const { trackingService } = require('./tracking-service');
17
18
 
18
19
  async function showMainMenu() {
19
20
  console.log('');
@@ -49,12 +50,14 @@ async function showMainMenu() {
49
50
 
50
51
  if (initialChoice.action === 'analytics') {
51
52
  console.log(chalk.blue('📊 Launching Claude Code Analytics Dashboard...'));
53
+ trackingService.trackAnalyticsDashboard({ page: 'dashboard', source: 'interactive_menu' });
52
54
  await runAnalytics({});
53
55
  return;
54
56
  }
55
57
 
56
58
  if (initialChoice.action === 'chats') {
57
59
  console.log(chalk.blue('💬 Launching Claude Code Chats Dashboard...'));
60
+ trackingService.trackAnalyticsDashboard({ page: 'agents', source: 'interactive_menu' });
58
61
  await runAnalytics({ openTo: 'agents' });
59
62
  return;
60
63
  }
@@ -62,6 +65,13 @@ async function showMainMenu() {
62
65
  if (initialChoice.action === 'health') {
63
66
  console.log(chalk.blue('🔍 Running Health Check...'));
64
67
  const healthResult = await runHealthCheck();
68
+
69
+ // Track health check usage
70
+ trackingService.trackHealthCheck({
71
+ setup_recommended: healthResult.runSetup,
72
+ issues_found: healthResult.issues || 0
73
+ });
74
+
65
75
  if (healthResult.runSetup) {
66
76
  console.log(chalk.blue('⚙️ Starting Project Setup...'));
67
77
  // Continue with setup flow
@@ -116,12 +126,14 @@ async function createClaudeConfig(options = {}) {
116
126
 
117
127
  // Handle analytics dashboard
118
128
  if (options.analytics) {
129
+ trackingService.trackAnalyticsDashboard({ page: 'dashboard', source: 'command_line' });
119
130
  await runAnalytics(options);
120
131
  return;
121
132
  }
122
133
 
123
134
  // Handle chats/agents dashboard
124
135
  if (options.chats || options.agents) {
136
+ trackingService.trackAnalyticsDashboard({ page: 'agents', source: 'command_line' });
125
137
  await runAnalytics({ ...options, openTo: 'agents' });
126
138
  return;
127
139
  }
@@ -130,6 +142,14 @@ async function createClaudeConfig(options = {}) {
130
142
  let shouldRunSetup = false;
131
143
  if (options.healthCheck || options.health || options.check || options.verify) {
132
144
  const healthResult = await runHealthCheck();
145
+
146
+ // Track health check usage
147
+ trackingService.trackHealthCheck({
148
+ setup_recommended: healthResult.runSetup,
149
+ issues_found: healthResult.issues || 0,
150
+ source: 'command_line'
151
+ });
152
+
133
153
  if (healthResult.runSetup) {
134
154
  console.log(chalk.blue('⚙️ Starting Project Setup...'));
135
155
  shouldRunSetup = true;
@@ -244,8 +264,8 @@ async function createClaudeConfig(options = {}) {
244
264
  console.log(chalk.white(' 2. Customize the configuration for your project'));
245
265
  console.log(chalk.white(' 3. Start using Claude Code with: claude'));
246
266
  console.log('');
247
- console.log(chalk.blue('🌐 View all available templates at: https://davila7.github.io/claude-code-templates/'));
248
- console.log(chalk.blue('📖 Read the complete documentation at: https://davila7.github.io/claude-code-templates/docu/'));
267
+ 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/'));
249
269
 
250
270
  if (config.language !== 'common') {
251
271
  console.log(chalk.yellow(`💡 Language-specific features for ${config.language} have been configured`));
@@ -262,6 +282,17 @@ async function createClaudeConfig(options = {}) {
262
282
  if (config.mcps && config.mcps.length > 0) {
263
283
  console.log(chalk.blue(`🔧 ${config.mcps.length} MCP servers have been configured`));
264
284
  }
285
+
286
+ // Track successful template installation
287
+ if (!options.agent && !options.command && !options.mcp) {
288
+ trackingService.trackTemplateInstallation(config.language, config.framework, {
289
+ installation_method: options.setupFromMenu ? 'interactive_menu' : 'command_line',
290
+ dry_run: options.dryRun || false,
291
+ hooks_count: config.hooks ? config.hooks.length : 0,
292
+ mcps_count: config.mcps ? config.mcps.length : 0,
293
+ project_detected: !!options.detectedProject
294
+ });
295
+ }
265
296
 
266
297
  // Run post-installation validation
267
298
  if (!options.dryRun) {
@@ -315,6 +346,14 @@ async function installIndividualAgent(agentName, targetDir, options) {
315
346
 
316
347
  console.log(chalk.green(`✅ Agent "${agentName}" installed successfully!`));
317
348
 
349
+ // Track successful agent installation
350
+ trackingService.trackDownload('agent', agentName, {
351
+ language: language,
352
+ framework: framework,
353
+ installation_type: 'individual_agent',
354
+ template_installed: true
355
+ });
356
+
318
357
  } catch (error) {
319
358
  console.log(chalk.red(`❌ Error installing agent: ${error.message}`));
320
359
  }
@@ -353,6 +392,12 @@ async function installIndividualCommand(commandName, targetDir, options) {
353
392
  console.log(chalk.green(`✅ Command "${commandName}" installed successfully!`));
354
393
  console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
355
394
 
395
+ // Track successful command installation
396
+ trackingService.trackDownload('command', commandName, {
397
+ installation_type: 'individual_command',
398
+ target_directory: path.relative(process.cwd(), targetDir)
399
+ });
400
+
356
401
  } catch (error) {
357
402
  console.log(chalk.red(`❌ Error installing command: ${error.message}`));
358
403
  }
@@ -404,6 +449,13 @@ async function installIndividualMCP(mcpName, targetDir, options) {
404
449
  console.log(chalk.green(`✅ MCP "${mcpName}" installed successfully!`));
405
450
  console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetMcpFile)}`));
406
451
 
452
+ // Track successful MCP installation
453
+ trackingService.trackDownload('mcp', mcpName, {
454
+ installation_type: 'individual_mcp',
455
+ merged_with_existing: existingConfig !== null,
456
+ servers_count: Object.keys(mergedConfig.mcpServers || {}).length
457
+ });
458
+
407
459
  } catch (error) {
408
460
  console.log(chalk.red(`❌ Error installing MCP: ${error.message}`));
409
461
  }