claude-code-templates 1.26.0 → 1.26.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.26.0",
3
+ "version": "1.26.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": {
@@ -782,6 +782,15 @@
782
782
  stroke: rgba(251, 146, 60, 1);
783
783
  }
784
784
 
785
+ .action-btn.analytics-btn:hover {
786
+ background: rgba(139, 92, 246, 0.15);
787
+ color: rgba(139, 92, 246, 1);
788
+ }
789
+
790
+ .action-btn.analytics-btn:hover svg {
791
+ stroke: rgba(139, 92, 246, 1);
792
+ }
793
+
785
794
  /* Conversations list */
786
795
  .conversations-list {
787
796
  flex: 1;
@@ -2067,6 +2076,14 @@
2067
2076
  </svg>
2068
2077
  <span>Search</span>
2069
2078
  </button>
2079
+ <button class="action-btn analytics-btn" id="showAnalytics" onclick="showAnalyticsModal()">
2080
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2081
+ <line x1="18" y1="20" x2="18" y2="10"></line>
2082
+ <line x1="12" y1="20" x2="12" y2="4"></line>
2083
+ <line x1="6" y1="20" x2="6" y2="14"></line>
2084
+ </svg>
2085
+ <span>Analytics</span>
2086
+ </button>
2070
2087
  </div>
2071
2088
  </div>
2072
2089
  <div class="header-right">
@@ -2169,13 +2186,14 @@
2169
2186
  </p>
2170
2187
 
2171
2188
  <div style="margin-bottom: 10px;">
2172
- <p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 1: Direct reference</p>
2189
+ <p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 1: Start with context loaded</p>
2173
2190
  <code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap;">claude "read @context-file.md and continue"</code>
2174
2191
  </div>
2175
2192
 
2176
2193
  <div>
2177
- <p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 2: Using cat (pipe)</p>
2178
- <code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap;">cat context-file.md | claude</code>
2194
+ <p style="margin: 0 0 4px 0; font-size: 12px; font-weight: 600; color: #10b981;">Option 2: In interactive mode</p>
2195
+ <code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap;">claude</code>
2196
+ <code style="display: block; background: rgba(0,0,0,0.5); padding: 8px; border-radius: 4px; font-size: 12px; color: #e5e7eb; overflow-x: auto; white-space: nowrap; margin-top: 4px;">> @context-file.md continue with the task</code>
2179
2197
  </div>
2180
2198
  </div>
2181
2199
 
@@ -2195,6 +2213,176 @@
2195
2213
  </div>
2196
2214
  </div>
2197
2215
 
2216
+ <!-- Analytics Modal -->
2217
+ <div class="modal-overlay" id="analyticsModal">
2218
+ <div class="modal" style="max-width: 900px; max-height: 90vh; overflow-y: auto;">
2219
+ <div class="modal-header">
2220
+ <h3 class="modal-title">Session Analytics (Beta)</h3>
2221
+ </div>
2222
+ <div style="padding: 20px;">
2223
+ <!-- Loading State -->
2224
+ <div id="analyticsLoading" style="text-align: center; padding: 40px;">
2225
+ <p style="color: var(--text-secondary);">Loading analytics data...</p>
2226
+ </div>
2227
+
2228
+ <!-- Analytics Content (hidden initially) -->
2229
+ <div id="analyticsContent" style="display: none;">
2230
+ <!-- Overview Stats - 3 columns -->
2231
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 20px;">
2232
+ <div style="background: rgba(139, 92, 246, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #8b5cf6;">
2233
+ <div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Total Messages</div>
2234
+ <div style="font-size: 24px; font-weight: 700; color: #8b5cf6;" id="analyticsMessageCount">-</div>
2235
+ </div>
2236
+ <div style="background: rgba(59, 130, 246, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #3b82f6;">
2237
+ <div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Total Tokens</div>
2238
+ <div style="font-size: 24px; font-weight: 700; color: #3b82f6;" id="analyticsTotalTokens">-</div>
2239
+ </div>
2240
+ <div style="background: rgba(16, 185, 129, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #10b981;">
2241
+ <div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Tool Calls</div>
2242
+ <div style="font-size: 24px; font-weight: 700; color: #10b981;" id="analyticsToolCalls">-</div>
2243
+ </div>
2244
+ <div style="background: rgba(234, 88, 12, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #ea580c;">
2245
+ <div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Tools Used</div>
2246
+ <div style="font-size: 24px; font-weight: 700; color: #ea580c;" id="analyticsUniqueTools">-</div>
2247
+ </div>
2248
+ <div style="background: rgba(251, 146, 60, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #fb923c;">
2249
+ <div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Models Used</div>
2250
+ <div style="font-size: 24px; font-weight: 700; color: #fb923c;" id="analyticsUniqueModels">-</div>
2251
+ </div>
2252
+ <div style="background: rgba(168, 85, 247, 0.1); border-radius: 8px; padding: 12px; border-left: 3px solid #a855f7;">
2253
+ <div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Cache Efficiency</div>
2254
+ <div style="font-size: 24px; font-weight: 700; color: #a855f7;" id="analyticsCacheEfficiency">-</div>
2255
+ </div>
2256
+ </div>
2257
+
2258
+ <!-- Token Usage Breakdown -->
2259
+ <div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
2260
+ <h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
2261
+ Token Usage Breakdown
2262
+ </h4>
2263
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
2264
+ <div>
2265
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Input Tokens</div>
2266
+ <div style="font-size: 18px; font-weight: 600; color: #3b82f6;" id="analyticsInputTokens">-</div>
2267
+ </div>
2268
+ <div>
2269
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Output Tokens</div>
2270
+ <div style="font-size: 18px; font-weight: 600; color: #8b5cf6;" id="analyticsOutputTokens">-</div>
2271
+ </div>
2272
+ <div>
2273
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Cache Creation</div>
2274
+ <div style="font-size: 18px; font-weight: 600; color: #fb923c;" id="analyticsCacheCreation">-</div>
2275
+ </div>
2276
+ <div>
2277
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Cache Reads</div>
2278
+ <div style="font-size: 18px; font-weight: 600; color: #10b981;" id="analyticsCacheReads">-</div>
2279
+ </div>
2280
+ </div>
2281
+ <div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.1);">
2282
+ <div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">Cost Estimate (USD)</div>
2283
+ <div style="font-size: 16px; font-weight: 600; color: #10b981;" id="analyticsCostEstimate">$0.00</div>
2284
+ </div>
2285
+ </div>
2286
+
2287
+ <!-- Session Timeline -->
2288
+ <div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
2289
+ <h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
2290
+ Session Timeline
2291
+ </h4>
2292
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
2293
+ <div>
2294
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Started</div>
2295
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsStartTime">-</div>
2296
+ </div>
2297
+ <div>
2298
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Last Activity</div>
2299
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsLastActivity">-</div>
2300
+ </div>
2301
+ <div>
2302
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Duration</div>
2303
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsDuration">-</div>
2304
+ </div>
2305
+ <div>
2306
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Status</div>
2307
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsStatus">-</div>
2308
+ </div>
2309
+ </div>
2310
+ </div>
2311
+
2312
+ <!-- Time Breakdown -->
2313
+ <div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
2314
+ <h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
2315
+ Time Breakdown
2316
+ </h4>
2317
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 12px;">
2318
+ <div>
2319
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Claude Executing</div>
2320
+ <div style="font-size: 18px; font-weight: 600; color: #3b82f6;" id="analyticsWaitTime">-</div>
2321
+ <div style="font-size: 11px; color: var(--text-secondary); margin-top: 2px;" id="analyticsWaitPercent">-</div>
2322
+ </div>
2323
+ <div>
2324
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">User Conversing</div>
2325
+ <div style="font-size: 18px; font-weight: 600; color: #10b981;" id="analyticsUserTime">-</div>
2326
+ <div style="font-size: 11px; color: var(--text-secondary); margin-top: 2px;" id="analyticsUserPercent">-</div>
2327
+ </div>
2328
+ </div>
2329
+ <div style="height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; overflow: hidden; display: flex;">
2330
+ <div id="analyticsWaitBar" style="background: linear-gradient(90deg, #3b82f6, #2563eb); transition: width 0.3s ease;"></div>
2331
+ <div id="analyticsUserBar" style="background: linear-gradient(90deg, #10b981, #059669); transition: width 0.3s ease;"></div>
2332
+ </div>
2333
+ </div>
2334
+
2335
+ <!-- Model Information -->
2336
+ <div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
2337
+ <h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
2338
+ Model Information
2339
+ </h4>
2340
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
2341
+ <div>
2342
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Primary Model</div>
2343
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary); font-family: monospace;" id="analyticsPrimaryModel">-</div>
2344
+ </div>
2345
+ <div>
2346
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">Service Tier</div>
2347
+ <div style="font-size: 14px; font-weight: 600; color: var(--text-primary);" id="analyticsServiceTier">-</div>
2348
+ </div>
2349
+ </div>
2350
+ <div id="analyticsAllModels" style="display: none; margin-top: 12px;">
2351
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px;">All Models Used</div>
2352
+ <div id="analyticsModelsList" style="display: flex; flex-wrap: wrap; gap: 6px;">
2353
+ <!-- Models will be populated here -->
2354
+ </div>
2355
+ </div>
2356
+ </div>
2357
+
2358
+ <!-- Tool Usage -->
2359
+ <div style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px;">
2360
+ <h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
2361
+ Tool Usage
2362
+ </h4>
2363
+ <div id="analyticsToolsList" style="max-height: 200px; overflow-y: auto;">
2364
+ <!-- Tool usage will be populated here -->
2365
+ </div>
2366
+ </div>
2367
+
2368
+ <!-- Components Used -->
2369
+ <div id="analyticsComponentsSection" style="background: rgba(255, 255, 255, 0.03); border-radius: 8px; padding: 16px; margin-bottom: 16px; display: none;">
2370
+ <h4 style="margin: 0 0 12px 0; color: var(--text-primary); font-size: 14px;">
2371
+ Components Used
2372
+ </h4>
2373
+ <div id="analyticsComponentsContent">
2374
+ <!-- Components will be populated here -->
2375
+ </div>
2376
+ </div>
2377
+ </div>
2378
+ </div>
2379
+
2380
+ <div class="modal-actions">
2381
+ <button class="modal-btn secondary" onclick="closeAnalyticsModal()">Close</button>
2382
+ </div>
2383
+ </div>
2384
+ </div>
2385
+
2198
2386
  <!-- Import WebSocket and Data Services -->
2199
2387
  <script src="services/WebSocketService.js"></script>
2200
2388
  <script src="services/DataService.js"></script>
@@ -2600,8 +2788,10 @@
2600
2788
 
2601
2789
  const resumeBtn = document.getElementById('resumeConversation');
2602
2790
  const downloadBtn = document.getElementById('downloadConversation');
2791
+ const analyticsBtn = document.getElementById('showAnalytics');
2603
2792
  resumeBtn.setAttribute('data-conversation-id', conversationId);
2604
2793
  downloadBtn.setAttribute('data-conversation-id', conversationId);
2794
+ analyticsBtn.setAttribute('data-conversation-id', conversationId);
2605
2795
 
2606
2796
  // Load messages (placeholder for now)
2607
2797
  this.loadChatMessages(conversationId);
@@ -4761,6 +4951,288 @@
4761
4951
  }
4762
4952
  }
4763
4953
 
4954
+ // Analytics Modal Functions
4955
+ async function showAnalyticsModal() {
4956
+ const analyticsBtn = document.getElementById('showAnalytics');
4957
+ const conversationId = analyticsBtn.getAttribute('data-conversation-id');
4958
+
4959
+ if (!conversationId) {
4960
+ console.error('No conversation ID found');
4961
+ return;
4962
+ }
4963
+
4964
+ console.log('📊 Opening analytics modal for conversation:', conversationId);
4965
+
4966
+ // Show analytics modal
4967
+ const analyticsModal = document.getElementById('analyticsModal');
4968
+ analyticsModal.classList.add('show');
4969
+
4970
+ // Reset to loading state
4971
+ document.getElementById('analyticsLoading').style.display = 'block';
4972
+ document.getElementById('analyticsContent').style.display = 'none';
4973
+
4974
+ // Close modal when clicking outside
4975
+ analyticsModal.addEventListener('click', (e) => {
4976
+ if (e.target === analyticsModal) {
4977
+ closeAnalyticsModal();
4978
+ }
4979
+ });
4980
+
4981
+ // Fetch analytics data
4982
+ try {
4983
+ const response = await fetch(`/api/conversations/${conversationId}/analytics`);
4984
+
4985
+ if (!response.ok) {
4986
+ throw new Error(`Failed to fetch analytics: ${response.statusText}`);
4987
+ }
4988
+
4989
+ const data = await response.json();
4990
+ console.log('✅ Analytics fetched:', data);
4991
+
4992
+ // Populate modal with analytics data
4993
+ populateAnalyticsModal(data.analytics);
4994
+
4995
+ // Hide loading, show content
4996
+ document.getElementById('analyticsLoading').style.display = 'none';
4997
+ document.getElementById('analyticsContent').style.display = 'block';
4998
+
4999
+ } catch (error) {
5000
+ console.error('❌ Failed to fetch analytics:', error);
5001
+ document.getElementById('analyticsLoading').innerHTML =
5002
+ `<p style="color: #ef4444;">Failed to load analytics: ${error.message}</p>`;
5003
+ }
5004
+ }
5005
+
5006
+ function closeAnalyticsModal() {
5007
+ const analyticsModal = document.getElementById('analyticsModal');
5008
+ analyticsModal.classList.remove('show');
5009
+ }
5010
+
5011
+ function populateAnalyticsModal(analytics) {
5012
+ console.log('📊 Populating analytics modal with data:', analytics);
5013
+
5014
+ // Overview stats - with safe fallbacks
5015
+ document.getElementById('analyticsMessageCount').textContent = (analytics.messageCount || 0).toLocaleString();
5016
+ document.getElementById('analyticsTotalTokens').textContent = (analytics.totalTokens || 0).toLocaleString();
5017
+ document.getElementById('analyticsToolCalls').textContent = (analytics.toolCalls || 0).toLocaleString();
5018
+ document.getElementById('analyticsCacheEfficiency').textContent = analytics.cacheEfficiency || '0%';
5019
+
5020
+ // New stats: unique tools and models
5021
+ const uniqueTools = analytics.toolUsage?.uniqueTools || 0;
5022
+ const uniqueModels = (analytics.modelInfo?.modelUsage && Array.isArray(analytics.modelInfo.modelUsage))
5023
+ ? analytics.modelInfo.modelUsage.length
5024
+ : 0;
5025
+
5026
+ console.log('📊 Unique tools:', uniqueTools, 'Unique models:', uniqueModels);
5027
+ document.getElementById('analyticsUniqueTools').textContent = uniqueTools;
5028
+ document.getElementById('analyticsUniqueModels').textContent = uniqueModels;
5029
+
5030
+ // Token breakdown
5031
+ document.getElementById('analyticsInputTokens').textContent = analytics.tokenUsage.inputTokens.toLocaleString();
5032
+ document.getElementById('analyticsOutputTokens').textContent = analytics.tokenUsage.outputTokens.toLocaleString();
5033
+ document.getElementById('analyticsCacheCreation').textContent = analytics.tokenUsage.cacheCreationTokens.toLocaleString();
5034
+ document.getElementById('analyticsCacheReads').textContent = analytics.tokenUsage.cacheReadTokens.toLocaleString();
5035
+
5036
+ // Cost estimate
5037
+ document.getElementById('analyticsCostEstimate').textContent = `$${analytics.costEstimate.total}`;
5038
+
5039
+ // Model info
5040
+ document.getElementById('analyticsPrimaryModel').textContent = analytics.modelInfo.primaryModel;
5041
+ document.getElementById('analyticsServiceTier').textContent = analytics.modelInfo.serviceTier;
5042
+
5043
+ // Show all models if multiple were used with usage percentages
5044
+ const allModelsSection = document.getElementById('analyticsAllModels');
5045
+ if (analytics.modelInfo.hasMultipleModels && analytics.modelInfo.modelUsage && analytics.modelInfo.modelUsage.length > 1) {
5046
+ const modelsListHTML = analytics.modelInfo.modelUsage
5047
+ .map(usage => `
5048
+ <span style="
5049
+ display: inline-flex;
5050
+ align-items: center;
5051
+ gap: 6px;
5052
+ padding: 4px 10px;
5053
+ background: rgba(139, 92, 246, 0.15);
5054
+ border: 1px solid rgba(139, 92, 246, 0.3);
5055
+ border-radius: 4px;
5056
+ font-size: 12px;
5057
+ font-family: monospace;
5058
+ color: var(--text-primary);
5059
+ ">
5060
+ <span>${usage.model}</span>
5061
+ <span style="
5062
+ padding: 2px 6px;
5063
+ background: rgba(139, 92, 246, 0.3);
5064
+ border-radius: 3px;
5065
+ font-size: 10px;
5066
+ font-weight: 600;
5067
+ color: #e9d5ff;
5068
+ ">${usage.count} msgs (${usage.percentage}%)</span>
5069
+ </span>
5070
+ `)
5071
+ .join('');
5072
+ document.getElementById('analyticsModelsList').innerHTML = modelsListHTML;
5073
+ allModelsSection.style.display = 'block';
5074
+ } else {
5075
+ allModelsSection.style.display = 'none';
5076
+ }
5077
+
5078
+ // Tool usage breakdown
5079
+ const toolsList = document.getElementById('analyticsToolsList');
5080
+ if (Object.keys(analytics.toolUsage.breakdown).length > 0) {
5081
+ const toolsHTML = Object.entries(analytics.toolUsage.breakdown)
5082
+ .sort((a, b) => b[1] - a[1]) // Sort by usage count
5083
+ .map(([tool, count]) => {
5084
+ const percentage = Math.round((count / analytics.toolUsage.totalCalls) * 100);
5085
+ return `
5086
+ <div style="margin-bottom: 10px; padding: 8px; background: rgba(255,255,255,0.03); border-radius: 6px;">
5087
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
5088
+ <span style="font-size: 13px; font-weight: 600; color: var(--text-primary);">${tool}</span>
5089
+ <span style="font-size: 12px; color: var(--text-secondary);">${count} calls (${percentage}%)</span>
5090
+ </div>
5091
+ <div style="height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; overflow: hidden;">
5092
+ <div style="height: 100%; width: ${percentage}%; background: linear-gradient(90deg, #10b981, #059669); transition: width 0.3s ease;"></div>
5093
+ </div>
5094
+ </div>
5095
+ `;
5096
+ })
5097
+ .join('');
5098
+ toolsList.innerHTML = toolsHTML;
5099
+ } else {
5100
+ toolsList.innerHTML = '<p style="color: var(--text-secondary); font-size: 13px; text-align: center; padding: 20px;">No tool usage detected in this session</p>';
5101
+ }
5102
+
5103
+ // Timeline
5104
+ const formatDate = (isoString) => {
5105
+ if (!isoString) return 'N/A';
5106
+ const date = new Date(isoString);
5107
+ return date.toLocaleString('en-US', {
5108
+ month: 'short',
5109
+ day: 'numeric',
5110
+ hour: '2-digit',
5111
+ minute: '2-digit'
5112
+ });
5113
+ };
5114
+
5115
+ document.getElementById('analyticsStartTime').textContent = formatDate(analytics.timeline.startTime);
5116
+ document.getElementById('analyticsLastActivity').textContent = formatDate(analytics.timeline.endTime);
5117
+ document.getElementById('analyticsDuration').textContent = analytics.timeline.duration;
5118
+ document.getElementById('analyticsStatus').textContent = analytics.timeline.status.charAt(0).toUpperCase() + analytics.timeline.status.slice(1);
5119
+
5120
+ // Time breakdown
5121
+ if (analytics.timeBreakdown && analytics.timeBreakdown.totalWaitTime) {
5122
+ document.getElementById('analyticsWaitTime').textContent = analytics.timeBreakdown.totalWaitTime || '0s';
5123
+ document.getElementById('analyticsUserTime').textContent = analytics.timeBreakdown.totalUserTime || '0s';
5124
+ document.getElementById('analyticsWaitPercent').textContent = `${analytics.timeBreakdown.waitTimePercent || 0}% of iteration time`;
5125
+ document.getElementById('analyticsUserPercent').textContent = `${analytics.timeBreakdown.userTimePercent || 0}% of iteration time`;
5126
+
5127
+ // Update progress bars
5128
+ document.getElementById('analyticsWaitBar').style.width = `${analytics.timeBreakdown.waitTimePercent || 0}%`;
5129
+ document.getElementById('analyticsUserBar').style.width = `${analytics.timeBreakdown.userTimePercent || 0}%`;
5130
+ } else {
5131
+ // No timing data available
5132
+ document.getElementById('analyticsWaitTime').textContent = 'N/A';
5133
+ document.getElementById('analyticsUserTime').textContent = 'N/A';
5134
+ document.getElementById('analyticsWaitPercent').textContent = 'No data';
5135
+ document.getElementById('analyticsUserPercent').textContent = 'No data';
5136
+ document.getElementById('analyticsWaitBar').style.width = '0%';
5137
+ document.getElementById('analyticsUserBar').style.width = '0%';
5138
+ }
5139
+
5140
+ // Components used (agents, commands, skills)
5141
+ if (analytics.componentsUsed) {
5142
+ const hasComponents = analytics.componentsUsed.totalAgents > 0 ||
5143
+ analytics.componentsUsed.totalCommands > 0 ||
5144
+ analytics.componentsUsed.totalSkills > 0;
5145
+
5146
+ if (hasComponents) {
5147
+ let componentsHTML = '';
5148
+
5149
+ // Agents
5150
+ if (analytics.componentsUsed.agents.length > 0) {
5151
+ componentsHTML += '<div style="margin-bottom: 12px;">';
5152
+ componentsHTML += '<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600;">Agents</div>';
5153
+ componentsHTML += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
5154
+ analytics.componentsUsed.agents.forEach(agent => {
5155
+ componentsHTML += `
5156
+ <span style="
5157
+ display: inline-flex;
5158
+ align-items: center;
5159
+ gap: 4px;
5160
+ padding: 4px 10px;
5161
+ background: rgba(139, 92, 246, 0.15);
5162
+ border: 1px solid rgba(139, 92, 246, 0.3);
5163
+ border-radius: 4px;
5164
+ font-size: 12px;
5165
+ color: var(--text-primary);
5166
+ ">
5167
+ <span>${agent.type}</span>
5168
+ <span style="color: var(--text-secondary); font-size: 11px;">×${agent.count}</span>
5169
+ </span>
5170
+ `;
5171
+ });
5172
+ componentsHTML += '</div></div>';
5173
+ }
5174
+
5175
+ // Slash Commands
5176
+ if (analytics.componentsUsed.slashCommands.length > 0) {
5177
+ componentsHTML += '<div style="margin-bottom: 12px;">';
5178
+ componentsHTML += '<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600;">Slash Commands</div>';
5179
+ componentsHTML += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
5180
+ analytics.componentsUsed.slashCommands.forEach(cmd => {
5181
+ componentsHTML += `
5182
+ <span style="
5183
+ display: inline-flex;
5184
+ align-items: center;
5185
+ gap: 4px;
5186
+ padding: 4px 10px;
5187
+ background: rgba(59, 130, 246, 0.15);
5188
+ border: 1px solid rgba(59, 130, 246, 0.3);
5189
+ border-radius: 4px;
5190
+ font-size: 12px;
5191
+ font-family: monospace;
5192
+ color: var(--text-primary);
5193
+ ">
5194
+ <span>${cmd.name}</span>
5195
+ <span style="color: var(--text-secondary); font-size: 11px;">×${cmd.count}</span>
5196
+ </span>
5197
+ `;
5198
+ });
5199
+ componentsHTML += '</div></div>';
5200
+ }
5201
+
5202
+ // Skills
5203
+ if (analytics.componentsUsed.skills.length > 0) {
5204
+ componentsHTML += '<div>';
5205
+ componentsHTML += '<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; font-weight: 600;">Skills</div>';
5206
+ componentsHTML += '<div style="display: flex; flex-wrap: wrap; gap: 6px;">';
5207
+ analytics.componentsUsed.skills.forEach(skill => {
5208
+ componentsHTML += `
5209
+ <span style="
5210
+ display: inline-flex;
5211
+ align-items: center;
5212
+ gap: 4px;
5213
+ padding: 4px 10px;
5214
+ background: rgba(16, 185, 129, 0.15);
5215
+ border: 1px solid rgba(16, 185, 129, 0.3);
5216
+ border-radius: 4px;
5217
+ font-size: 12px;
5218
+ color: var(--text-primary);
5219
+ ">
5220
+ <span>${skill.name}</span>
5221
+ <span style="color: var(--text-secondary); font-size: 11px;">×${skill.count}</span>
5222
+ </span>
5223
+ `;
5224
+ });
5225
+ componentsHTML += '</div></div>';
5226
+ }
5227
+
5228
+ document.getElementById('analyticsComponentsContent').innerHTML = componentsHTML;
5229
+ document.getElementById('analyticsComponentsSection').style.display = 'block';
5230
+ } else {
5231
+ document.getElementById('analyticsComponentsSection').style.display = 'none';
5232
+ }
5233
+ }
5234
+ }
5235
+
4764
5236
  // Initialize the app
4765
5237
  document.addEventListener('DOMContentLoaded', () => {
4766
5238
  new ChatsMobileApp();
@@ -9,6 +9,7 @@ const ConversationAnalyzer = require('./analytics/core/ConversationAnalyzer');
9
9
  const StateCalculator = require('./analytics/core/StateCalculator');
10
10
  const FileWatcher = require('./analytics/core/FileWatcher');
11
11
  const DataCache = require('./analytics/data/DataCache');
12
+ const AgentAnalyzer = require('./analytics/core/AgentAnalyzer');
12
13
  const WebSocketServer = require('./analytics/notifications/WebSocketServer');
13
14
  const SessionSharing = require('./session-sharing');
14
15
 
@@ -512,6 +513,289 @@ class ChatsMobile {
512
513
  }
513
514
  });
514
515
 
516
+ // API to get detailed analytics for a conversation
517
+ this.app.get('/api/conversations/:id/analytics', async (req, res) => {
518
+ try {
519
+ const conversationId = req.params.id;
520
+ const conversation = this.data.conversations.find(conv => conv.id === conversationId);
521
+
522
+ if (!conversation) {
523
+ return res.status(404).json({ error: 'Conversation not found' });
524
+ }
525
+
526
+ console.log(chalk.cyan(`📊 Fetching analytics for conversation ${conversationId}...`));
527
+
528
+ // Get parsed messages for this conversation
529
+ const messages = await this.conversationAnalyzer.getParsedConversation(conversation.filePath);
530
+
531
+ // Calculate session duration and timing breakdown
532
+ const startTime = messages.length > 0 ? new Date(messages[0].timestamp) : null;
533
+ const endTime = messages.length > 0 ? new Date(messages[messages.length - 1].timestamp) : null;
534
+ const durationMs = startTime && endTime ? endTime - startTime : 0;
535
+ const durationHours = Math.floor(durationMs / (1000 * 60 * 60));
536
+ const durationMinutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
537
+
538
+ // Calculate time conversing vs executing (time between messages)
539
+ let totalWaitTime = 0; // Time waiting for Claude (thinking + executing)
540
+ let totalUserTime = 0; // Time user takes to respond
541
+
542
+ // Find user and assistant messages only (ignore tool results and other message types)
543
+ const conversationMessages = messages.filter(msg => msg.role === 'user' || msg.role === 'assistant');
544
+
545
+ let lastUserTime = null;
546
+ let lastAssistantTime = null;
547
+
548
+ conversationMessages.forEach(msg => {
549
+ if (msg.role === 'user') {
550
+ // If there was a previous assistant message, calculate user thinking time
551
+ if (lastAssistantTime) {
552
+ const thinkingTime = new Date(msg.timestamp) - lastAssistantTime;
553
+ // Only count gaps less than 1 hour to avoid counting long breaks
554
+ if (thinkingTime > 0 && thinkingTime < 60 * 60 * 1000) {
555
+ totalUserTime += thinkingTime;
556
+ }
557
+ }
558
+ lastUserTime = new Date(msg.timestamp);
559
+ } else if (msg.role === 'assistant') {
560
+ // If there was a previous user message, calculate Claude execution time
561
+ if (lastUserTime) {
562
+ const executionTime = new Date(msg.timestamp) - lastUserTime;
563
+ // Only count gaps less than 10 minutes (typical execution time)
564
+ if (executionTime > 0 && executionTime < 10 * 60 * 1000) {
565
+ totalWaitTime += executionTime;
566
+ }
567
+ }
568
+ lastAssistantTime = new Date(msg.timestamp);
569
+ }
570
+ });
571
+
572
+ const totalIterationTime = totalWaitTime + totalUserTime;
573
+ const waitTimePercent = totalIterationTime > 0 ? Math.round((totalWaitTime / totalIterationTime) * 100) : 0;
574
+ const userTimePercent = totalIterationTime > 0 ? Math.round((totalUserTime / totalIterationTime) * 100) : 0;
575
+
576
+ // Calculate cache efficiency
577
+ const cacheTotal = (conversation.tokenUsage?.cacheCreationTokens || 0) + (conversation.tokenUsage?.cacheReadTokens || 0);
578
+ const cacheEfficiency = cacheTotal > 0
579
+ ? Math.round((conversation.tokenUsage?.cacheReadTokens || 0) / cacheTotal * 100)
580
+ : 0;
581
+
582
+ // Estimate cost (approximate Claude API pricing)
583
+ // Sonnet 4.5: $3/1M input, $15/1M output
584
+ // Cache write: $3.75/1M, Cache read: $0.30/1M
585
+ const inputCost = (conversation.tokenUsage?.inputTokens || 0) / 1000000 * 3;
586
+ const outputCost = (conversation.tokenUsage?.outputTokens || 0) / 1000000 * 15;
587
+ const cacheWriteCost = (conversation.tokenUsage?.cacheCreationTokens || 0) / 1000000 * 3.75;
588
+ const cacheReadCost = (conversation.tokenUsage?.cacheReadTokens || 0) / 1000000 * 0.30;
589
+ const totalCost = inputCost + outputCost + cacheWriteCost + cacheReadCost;
590
+
591
+ // Detect agents, hooks, and components used
592
+ const agentAnalyzer = new AgentAnalyzer();
593
+ const componentsUsed = {
594
+ agents: [],
595
+ slashCommands: [],
596
+ skills: []
597
+ };
598
+
599
+ messages.forEach(message => {
600
+ const messageContent = message.content;
601
+ const messageRole = message.role;
602
+
603
+ if (messageRole === 'assistant' && messageContent && Array.isArray(messageContent)) {
604
+ messageContent.forEach(content => {
605
+ // Detect Task tool with subagent_type (agents)
606
+ if (content.type === 'tool_use' && content.name === 'Task' && content.input?.subagent_type) {
607
+ const agentType = content.input.subagent_type;
608
+ if (!componentsUsed.agents.find(a => a.type === agentType)) {
609
+ componentsUsed.agents.push({
610
+ type: agentType,
611
+ count: 1
612
+ });
613
+ } else {
614
+ componentsUsed.agents.find(a => a.type === agentType).count++;
615
+ }
616
+ }
617
+
618
+ // Detect SlashCommand tool (commands)
619
+ if (content.type === 'tool_use' && content.name === 'SlashCommand' && content.input?.command) {
620
+ const command = content.input.command;
621
+ if (!componentsUsed.slashCommands.find(c => c.name === command)) {
622
+ componentsUsed.slashCommands.push({
623
+ name: command,
624
+ count: 1
625
+ });
626
+ } else {
627
+ componentsUsed.slashCommands.find(c => c.name === command).count++;
628
+ }
629
+ }
630
+
631
+ // Detect Skill tool (skills)
632
+ if (content.type === 'tool_use' && content.name === 'Skill' && content.input?.command) {
633
+ const skill = content.input.command;
634
+ if (!componentsUsed.skills.find(s => s.name === skill)) {
635
+ componentsUsed.skills.push({
636
+ name: skill,
637
+ count: 1
638
+ });
639
+ } else {
640
+ componentsUsed.skills.find(s => s.name === skill).count++;
641
+ }
642
+ }
643
+ });
644
+ }
645
+ });
646
+
647
+ // Format time durations
648
+ const formatDuration = (ms) => {
649
+ const hours = Math.floor(ms / (1000 * 60 * 60));
650
+ const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
651
+ const seconds = Math.floor((ms % (1000 * 60)) / 1000);
652
+
653
+ if (hours > 0) return `${hours}h ${minutes}m`;
654
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
655
+ return `${seconds}s`;
656
+ };
657
+
658
+ // Generate optimization tips based on analytics
659
+ const optimizationTips = [];
660
+
661
+ if (cacheEfficiency < 20 && cacheTotal > 0) {
662
+ optimizationTips.push('• Low cache efficiency detected. Consider restructuring prompts to maximize cache reuse.');
663
+ }
664
+ if (conversation.toolUsage?.totalToolCalls > 50) {
665
+ optimizationTips.push('• High tool usage detected. Review if all tool calls are necessary.');
666
+ }
667
+ if (conversation.tokenUsage?.outputTokens > conversation.tokenUsage?.inputTokens * 2) {
668
+ optimizationTips.push('• Output tokens significantly exceed input. Consider more concise prompts.');
669
+ }
670
+ if (messages.length > 100) {
671
+ optimizationTips.push('• Long conversation detected. Consider starting fresh sessions for new topics to optimize context.');
672
+ }
673
+ if (conversation.modelInfo?.hasMultipleModels) {
674
+ optimizationTips.push('• Multiple models used in this session. Stick to one model for consistency.');
675
+ }
676
+ if (waitTimePercent > 70) {
677
+ optimizationTips.push('• High execution time detected. Consider breaking down complex tasks or optimizing tool usage.');
678
+ }
679
+ if (optimizationTips.length === 0) {
680
+ optimizationTips.push('• Great! Your conversation shows efficient usage patterns.');
681
+ }
682
+
683
+ // Prepare detailed analytics response
684
+ const analytics = {
685
+ // Overview
686
+ messageCount: messages.length,
687
+ totalTokens: conversation.tokenUsage?.total || 0,
688
+ toolCalls: conversation.toolUsage?.totalToolCalls || 0,
689
+ cacheEfficiency: `${cacheEfficiency}%`,
690
+
691
+ // Token breakdown
692
+ tokenUsage: {
693
+ inputTokens: conversation.tokenUsage?.inputTokens || 0,
694
+ outputTokens: conversation.tokenUsage?.outputTokens || 0,
695
+ cacheCreationTokens: conversation.tokenUsage?.cacheCreationTokens || 0,
696
+ cacheReadTokens: conversation.tokenUsage?.cacheReadTokens || 0,
697
+ total: conversation.tokenUsage?.total || 0
698
+ },
699
+
700
+ // Cost estimate
701
+ costEstimate: {
702
+ total: totalCost.toFixed(4),
703
+ breakdown: {
704
+ input: inputCost.toFixed(4),
705
+ output: outputCost.toFixed(4),
706
+ cacheWrite: cacheWriteCost.toFixed(4),
707
+ cacheRead: cacheReadCost.toFixed(4)
708
+ }
709
+ },
710
+
711
+ // Model info with usage percentages
712
+ modelInfo: {
713
+ primaryModel: conversation.modelInfo?.primaryModel || 'Unknown',
714
+ serviceTier: conversation.modelInfo?.currentServiceTier || 'Unknown',
715
+ hasMultipleModels: conversation.modelInfo?.hasMultipleModels || false,
716
+ allModels: conversation.modelInfo?.models || [],
717
+ modelUsage: (() => {
718
+ // Calculate model usage percentages
719
+ const modelCounts = {};
720
+ let totalMessages = 0;
721
+
722
+ messages.forEach(msg => {
723
+ if (msg.model && msg.model !== '<synthetic>') {
724
+ modelCounts[msg.model] = (modelCounts[msg.model] || 0) + 1;
725
+ totalMessages++;
726
+ }
727
+ });
728
+
729
+ return Object.entries(modelCounts).map(([model, count]) => ({
730
+ model,
731
+ count,
732
+ percentage: totalMessages > 0 ? ((count / totalMessages) * 100).toFixed(1) : '0.0'
733
+ })).sort((a, b) => b.count - a.count);
734
+ })()
735
+ },
736
+
737
+ // Tool usage
738
+ toolUsage: {
739
+ totalCalls: conversation.toolUsage?.totalToolCalls || 0,
740
+ uniqueTools: conversation.toolUsage?.uniqueTools || 0,
741
+ breakdown: conversation.toolUsage?.toolStats || {},
742
+ timeline: conversation.toolUsage?.toolTimeline || []
743
+ },
744
+
745
+ // Session timeline
746
+ timeline: {
747
+ startTime: startTime ? startTime.toISOString() : null,
748
+ endTime: endTime ? endTime.toISOString() : null,
749
+ duration: durationHours > 0
750
+ ? `${durationHours}h ${durationMinutes}m`
751
+ : `${durationMinutes}m`,
752
+ durationMs: durationMs,
753
+ status: conversation.status || 'unknown'
754
+ },
755
+
756
+ // Time breakdown (conversing vs executing)
757
+ timeBreakdown: {
758
+ totalWaitTime: formatDuration(totalWaitTime),
759
+ totalUserTime: formatDuration(totalUserTime),
760
+ waitTimePercent: waitTimePercent,
761
+ userTimePercent: userTimePercent,
762
+ waitTimeMs: totalWaitTime,
763
+ userTimeMs: totalUserTime,
764
+ totalIterationTime: formatDuration(totalIterationTime)
765
+ },
766
+
767
+ // Components used (agents, commands, skills)
768
+ componentsUsed: {
769
+ agents: componentsUsed.agents.sort((a, b) => b.count - a.count),
770
+ slashCommands: componentsUsed.slashCommands.sort((a, b) => b.count - a.count),
771
+ skills: componentsUsed.skills.sort((a, b) => b.count - a.count),
772
+ totalAgents: componentsUsed.agents.length,
773
+ totalCommands: componentsUsed.slashCommands.length,
774
+ totalSkills: componentsUsed.skills.length
775
+ },
776
+
777
+ // Optimization tips
778
+ optimizationTips: optimizationTips,
779
+
780
+ // Metadata
781
+ conversationId: conversationId,
782
+ project: conversation.project || 'Unknown',
783
+ timestamp: new Date().toISOString()
784
+ };
785
+
786
+ res.json({
787
+ success: true,
788
+ analytics: analytics
789
+ });
790
+ } catch (error) {
791
+ console.error('Error fetching conversation analytics:', error);
792
+ res.status(500).json({
793
+ error: 'Failed to fetch analytics',
794
+ message: error.message
795
+ });
796
+ }
797
+ });
798
+
515
799
  // Serve the mobile chats page as default
516
800
  this.app.get('/', (req, res) => {
517
801
  res.sendFile(path.join(__dirname, 'analytics-web', 'chats_mobile.html'));