claude-code-templates 1.5.0 → 1.5.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/analytics.js +132 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/analytics.js CHANGED
@@ -70,12 +70,33 @@ class ClaudeAnalytics {
70
70
  const conversations = [];
71
71
 
72
72
  try {
73
- const files = await fs.readdir(this.claudeDir);
74
- const jsonlFiles = files.filter(file => file.endsWith('.jsonl'));
73
+ // Search for .jsonl files recursively in all subdirectories
74
+ const findJsonlFiles = async (dir) => {
75
+ const files = [];
76
+ const items = await fs.readdir(dir);
77
+
78
+ for (const item of items) {
79
+ const itemPath = path.join(dir, item);
80
+ const stats = await fs.stat(itemPath);
81
+
82
+ if (stats.isDirectory()) {
83
+ // Recursively search subdirectories
84
+ const subFiles = await findJsonlFiles(itemPath);
85
+ files.push(...subFiles);
86
+ } else if (item.endsWith('.jsonl')) {
87
+ files.push(itemPath);
88
+ }
89
+ }
90
+
91
+ return files;
92
+ };
75
93
 
76
- for (const file of jsonlFiles) {
77
- const filePath = path.join(this.claudeDir, file);
94
+ const jsonlFiles = await findJsonlFiles(this.claudeDir);
95
+ console.log(chalk.blue(`Found ${jsonlFiles.length} conversation files`));
96
+
97
+ for (const filePath of jsonlFiles) {
78
98
  const stats = await fs.stat(filePath);
99
+ const filename = path.basename(filePath);
79
100
 
80
101
  try {
81
102
  const content = await fs.readFile(filePath, 'utf8');
@@ -88,21 +109,25 @@ class ClaudeAnalytics {
88
109
  }
89
110
  }).filter(Boolean);
90
111
 
112
+ // Extract project name from path
113
+ const projectFromPath = this.extractProjectFromPath(filePath);
114
+
91
115
  const conversation = {
92
- id: file.replace('.jsonl', ''),
93
- filename: file,
116
+ id: filename.replace('.jsonl', ''),
117
+ filename: filename,
118
+ filePath: filePath,
94
119
  messageCount: messages.length,
95
120
  fileSize: stats.size,
96
121
  lastModified: stats.mtime,
97
122
  created: stats.birthtime,
98
123
  tokens: this.estimateTokens(content),
99
- project: this.extractProjectFromConversation(messages),
124
+ project: projectFromPath || this.extractProjectFromConversation(messages),
100
125
  status: this.determineConversationStatus(messages, stats.mtime)
101
126
  };
102
127
 
103
128
  conversations.push(conversation);
104
129
  } catch (error) {
105
- console.warn(chalk.yellow(`Warning: Could not parse ${file}:`, error.message));
130
+ console.warn(chalk.yellow(`Warning: Could not parse ${filename}:`, error.message));
106
131
  }
107
132
  }
108
133
 
@@ -160,6 +185,27 @@ class ClaudeAnalytics {
160
185
  return Math.ceil(text.length / 4);
161
186
  }
162
187
 
188
+ extractProjectFromPath(filePath) {
189
+ // Extract project name from file path like:
190
+ // /Users/user/.claude/projects/-Users-user-Projects-MyProject/conversation.jsonl
191
+ const pathParts = filePath.split('/');
192
+ const projectIndex = pathParts.findIndex(part => part === 'projects');
193
+
194
+ if (projectIndex !== -1 && projectIndex + 1 < pathParts.length) {
195
+ const projectDir = pathParts[projectIndex + 1];
196
+ // Clean up the project directory name
197
+ const cleanName = projectDir
198
+ .replace(/^-/, '')
199
+ .replace(/-/g, '/')
200
+ .split('/')
201
+ .pop() || 'Unknown';
202
+
203
+ return cleanName;
204
+ }
205
+
206
+ return null;
207
+ }
208
+
163
209
  extractProjectFromConversation(messages) {
164
210
  // Try to extract project information from conversation
165
211
  for (const message of messages.slice(0, 5)) {
@@ -233,20 +279,24 @@ class ClaudeAnalytics {
233
279
  setupFileWatchers() {
234
280
  console.log(chalk.blue('👀 Setting up file watchers for real-time updates...'));
235
281
 
236
- // Watch conversation files
237
- const conversationWatcher = chokidar.watch(path.join(this.claudeDir, '*.jsonl'), {
282
+ // Watch conversation files recursively in all subdirectories
283
+ const conversationWatcher = chokidar.watch([
284
+ path.join(this.claudeDir, '**/*.jsonl')
285
+ ], {
238
286
  persistent: true,
239
287
  ignoreInitial: true
240
288
  });
241
289
 
242
290
  conversationWatcher.on('change', async () => {
291
+ console.log(chalk.yellow('🔄 Conversation file changed, updating data...'));
243
292
  await this.loadInitialData();
244
- console.log(chalk.green('🔄 Conversation data updated'));
293
+ console.log(chalk.green(' Data updated'));
245
294
  });
246
295
 
247
296
  conversationWatcher.on('add', async () => {
297
+ console.log(chalk.yellow('📝 New conversation file detected...'));
248
298
  await this.loadInitialData();
249
- console.log(chalk.green('📝 New conversation detected'));
299
+ console.log(chalk.green(' Data updated'));
250
300
  });
251
301
 
252
302
  this.watchers.push(conversationWatcher);
@@ -255,15 +305,28 @@ class ClaudeAnalytics {
255
305
  const projectWatcher = chokidar.watch(this.claudeDir, {
256
306
  persistent: true,
257
307
  ignoreInitial: true,
258
- depth: 1
308
+ depth: 2 // Increased depth to catch subdirectories
259
309
  });
260
310
 
261
311
  projectWatcher.on('addDir', async () => {
312
+ console.log(chalk.yellow('📁 New project directory detected...'));
313
+ await this.loadInitialData();
314
+ console.log(chalk.green('✅ Data updated'));
315
+ });
316
+
317
+ projectWatcher.on('change', async () => {
318
+ console.log(chalk.yellow('📁 Project directory changed...'));
262
319
  await this.loadInitialData();
263
- console.log(chalk.green('📁 New project detected'));
320
+ console.log(chalk.green(' Data updated'));
264
321
  });
265
322
 
266
323
  this.watchers.push(projectWatcher);
324
+
325
+ // Also set up periodic refresh to catch any missed changes
326
+ setInterval(async () => {
327
+ console.log(chalk.blue('⏱️ Periodic data refresh...'));
328
+ await this.loadInitialData();
329
+ }, 30000); // Every 30 seconds
267
330
  }
268
331
 
269
332
  setupWebServer() {
@@ -271,12 +334,34 @@ class ClaudeAnalytics {
271
334
  this.app.use(express.static(path.join(__dirname, 'analytics-web')));
272
335
 
273
336
  // API endpoints
274
- this.app.get('/api/data', (req, res) => {
275
- res.json(this.data);
337
+ this.app.get('/api/data', async (req, res) => {
338
+ // Add timestamp to verify data freshness
339
+ const dataWithTimestamp = {
340
+ ...this.data,
341
+ timestamp: new Date().toISOString(),
342
+ lastUpdate: new Date().toLocaleString()
343
+ };
344
+ res.json(dataWithTimestamp);
345
+ });
346
+
347
+ this.app.get('/api/realtime', async (req, res) => {
348
+ const realtimeWithTimestamp = {
349
+ ...this.data.realtimeStats,
350
+ timestamp: new Date().toISOString(),
351
+ lastUpdate: new Date().toLocaleString()
352
+ };
353
+ res.json(realtimeWithTimestamp);
276
354
  });
277
355
 
278
- this.app.get('/api/realtime', (req, res) => {
279
- res.json(this.data.realtimeStats);
356
+ // Force refresh endpoint
357
+ this.app.get('/api/refresh', async (req, res) => {
358
+ console.log(chalk.blue('🔄 Manual refresh requested...'));
359
+ await this.loadInitialData();
360
+ res.json({
361
+ success: true,
362
+ message: 'Data refreshed',
363
+ timestamp: new Date().toISOString()
364
+ });
280
365
  });
281
366
 
282
367
  // Main dashboard route
@@ -543,6 +628,7 @@ async function createWebDashboard() {
543
628
  Claude Code Analytics
544
629
  </h1>
545
630
  <p>Real-time monitoring of your Claude Code usage</p>
631
+ <p id="lastUpdate" style="font-size: 0.75rem; opacity: 0.8;"></p>
546
632
  </div>
547
633
 
548
634
  <div id="loading" class="loading">
@@ -601,9 +687,15 @@ async function createWebDashboard() {
601
687
  const response = await fetch('/api/data');
602
688
  const data = await response.json();
603
689
 
690
+ console.log('Data loaded:', data.timestamp);
691
+
604
692
  document.getElementById('loading').style.display = 'none';
605
693
  document.getElementById('dashboard').style.display = 'block';
606
694
 
695
+ // Update timestamp
696
+ document.getElementById('lastUpdate').textContent =
697
+ \`Last updated: \${data.lastUpdate}\`;
698
+
607
699
  updateStats(data.summary);
608
700
  updateConversations(data.conversations);
609
701
  updateProjects(data.activeProjects);
@@ -660,11 +752,31 @@ async function createWebDashboard() {
660
752
  \`).join('');
661
753
  }
662
754
 
755
+ // Manual refresh function
756
+ async function forceRefresh() {
757
+ try {
758
+ const response = await fetch('/api/refresh');
759
+ const result = await response.json();
760
+ console.log('Manual refresh:', result);
761
+ await loadData();
762
+ } catch (error) {
763
+ console.error('Failed to refresh:', error);
764
+ }
765
+ }
766
+
663
767
  // Load initial data
664
768
  loadData();
665
769
 
666
- // Refresh data every 10 seconds
667
- setInterval(loadData, 10000);
770
+ // Refresh data every 5 seconds
771
+ setInterval(loadData, 5000);
772
+
773
+ // Add keyboard shortcut for refresh (F5 or Ctrl+R)
774
+ document.addEventListener('keydown', function(e) {
775
+ if (e.key === 'F5' || (e.ctrlKey && e.key === 'r')) {
776
+ e.preventDefault();
777
+ forceRefresh();
778
+ }
779
+ });
668
780
  </script>
669
781
  </body>
670
782
  </html>`;