claude-code-templates 1.12.2 → 1.13.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.
@@ -54,6 +54,9 @@ program
54
54
  .option('--analytics', 'launch real-time Claude Code analytics dashboard')
55
55
  .option('--chats, --agents', 'launch Claude Code chats/agents dashboard (opens directly to conversations)')
56
56
  .option('--health-check, --health, --check, --verify', 'run comprehensive health check to verify Claude Code setup')
57
+ .option('--agent <agent>', 'install specific agent component')
58
+ .option('--command <command>', 'install specific command component')
59
+ .option('--mcp <mcp>', 'install specific MCP component')
57
60
  .action(async (options) => {
58
61
  try {
59
62
  await createClaudeConfig(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.12.2",
3
+ "version": "1.13.1",
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": {
@@ -0,0 +1,341 @@
1
+ /**
2
+ * AgentAnalyzer - Analyzes Claude Code specialized agent usage patterns
3
+ * Extracts agent invocation data, usage frequency, and workflow patterns
4
+ */
5
+ const chalk = require('chalk');
6
+ const fs = require('fs-extra');
7
+ const path = require('path');
8
+
9
+ class AgentAnalyzer {
10
+ constructor() {
11
+ // Known Claude Code specialized agents
12
+ this.AGENT_TYPES = {
13
+ 'general-purpose': {
14
+ name: 'General Purpose',
15
+ description: 'Multi-step tasks and research',
16
+ color: '#3fb950',
17
+ icon: '🔧'
18
+ },
19
+ 'claude-code-best-practices': {
20
+ name: 'Claude Code Best Practices',
21
+ description: 'Workflow optimization and setup guidance',
22
+ color: '#f97316',
23
+ icon: '⚡'
24
+ },
25
+ 'docusaurus-expert': {
26
+ name: 'Docusaurus Expert',
27
+ description: 'Documentation site management',
28
+ color: '#0969da',
29
+ icon: '📚'
30
+ }
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Analyze agent usage across all conversations
36
+ * @param {Array} conversations - Array of conversation objects with parsed messages
37
+ * @param {Object} dateRange - Optional date range filter
38
+ * @returns {Object} Agent usage analysis
39
+ */
40
+ async analyzeAgentUsage(conversations, dateRange = null) {
41
+ const agentStats = {};
42
+ const agentTimeline = [];
43
+ const agentWorkflows = {};
44
+ let totalAgentInvocations = 0;
45
+
46
+ for (const conversation of conversations) {
47
+ // Parse messages from JSONL file if not already parsed
48
+ let messages = conversation.parsedMessages;
49
+ if (!messages && conversation.filePath) {
50
+ messages = await this.parseJsonlFile(conversation.filePath);
51
+ }
52
+
53
+ if (!messages) continue;
54
+
55
+ messages.forEach(message => {
56
+ // Skip if outside date range
57
+ if (dateRange && !this.isWithinDateRange(message.timestamp, dateRange)) {
58
+ return;
59
+ }
60
+
61
+ // Look for Task tool usage with subagent_type
62
+ // Handle both direct message structure and nested message structure
63
+ const messageContent = message.message ? message.message.content : message.content;
64
+ const messageRole = message.message ? message.message.role : message.role;
65
+
66
+ if (messageRole === 'assistant' &&
67
+ messageContent &&
68
+ Array.isArray(messageContent)) {
69
+
70
+ messageContent.forEach(content => {
71
+ if (content.type === 'tool_use' &&
72
+ content.name === 'Task' &&
73
+ content.input &&
74
+ content.input.subagent_type) {
75
+
76
+ const agentType = content.input.subagent_type;
77
+ const timestamp = new Date(message.timestamp);
78
+ const prompt = content.input.prompt || content.input.description || 'No description';
79
+
80
+ // Initialize agent stats
81
+ if (!agentStats[agentType]) {
82
+ agentStats[agentType] = {
83
+ type: agentType,
84
+ name: this.AGENT_TYPES[agentType]?.name || agentType,
85
+ description: this.AGENT_TYPES[agentType]?.description || 'Custom agent',
86
+ color: this.AGENT_TYPES[agentType]?.color || '#8b5cf6',
87
+ icon: this.AGENT_TYPES[agentType]?.icon || '🤖',
88
+ totalInvocations: 0,
89
+ uniqueConversations: new Set(),
90
+ firstUsed: timestamp,
91
+ lastUsed: timestamp,
92
+ prompts: [],
93
+ hourlyDistribution: new Array(24).fill(0),
94
+ dailyUsage: {}
95
+ };
96
+ }
97
+
98
+ const stats = agentStats[agentType];
99
+
100
+ // Update stats
101
+ stats.totalInvocations++;
102
+ stats.uniqueConversations.add(conversation.id);
103
+ stats.lastUsed = new Date(Math.max(stats.lastUsed, timestamp));
104
+ stats.firstUsed = new Date(Math.min(stats.firstUsed, timestamp));
105
+
106
+ // Store prompt for analysis
107
+ stats.prompts.push({
108
+ text: prompt,
109
+ timestamp: timestamp,
110
+ conversationId: conversation.id
111
+ });
112
+
113
+ // Track hourly distribution
114
+ const hour = timestamp.getHours();
115
+ stats.hourlyDistribution[hour]++;
116
+
117
+ // Track daily usage
118
+ const dateKey = timestamp.toISOString().split('T')[0];
119
+ stats.dailyUsage[dateKey] = (stats.dailyUsage[dateKey] || 0) + 1;
120
+
121
+ // Add to timeline
122
+ agentTimeline.push({
123
+ timestamp: timestamp,
124
+ agentType: agentType,
125
+ agentName: stats.name,
126
+ prompt: prompt.substring(0, 100) + (prompt.length > 100 ? '...' : ''),
127
+ conversationId: conversation.id,
128
+ color: stats.color,
129
+ icon: stats.icon
130
+ });
131
+
132
+ totalAgentInvocations++;
133
+ }
134
+ });
135
+ }
136
+ });
137
+ }
138
+
139
+ // Convert Sets to counts and finalize stats
140
+ Object.keys(agentStats).forEach(agentType => {
141
+ const stats = agentStats[agentType];
142
+ stats.uniqueConversations = stats.uniqueConversations.size;
143
+ stats.averageUsagePerConversation = stats.uniqueConversations > 0 ?
144
+ (stats.totalInvocations / stats.uniqueConversations).toFixed(1) : 0;
145
+ });
146
+
147
+ // Sort timeline by timestamp
148
+ agentTimeline.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
149
+
150
+ // Calculate agent workflows (sequences of agent usage)
151
+ const workflowPatterns = this.analyzeAgentWorkflows(agentTimeline);
152
+
153
+ return {
154
+ totalAgentInvocations,
155
+ totalAgentTypes: Object.keys(agentStats).length,
156
+ agentStats: Object.values(agentStats).sort((a, b) => b.totalInvocations - a.totalInvocations),
157
+ agentTimeline,
158
+ workflowPatterns,
159
+ popularHours: this.calculatePopularHours(agentStats),
160
+ usageByDay: this.calculateDailyUsage(agentStats),
161
+ efficiency: this.calculateAgentEfficiency(agentStats)
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Analyze agent workflow patterns
167
+ * @param {Array} timeline - Chronological agent invocations
168
+ * @returns {Object} Workflow patterns
169
+ */
170
+ analyzeAgentWorkflows(timeline) {
171
+ const workflows = {};
172
+ const SESSION_GAP_MINUTES = 30; // Minutes between workflow sessions
173
+
174
+ let currentWorkflow = [];
175
+ let lastTimestamp = null;
176
+
177
+ timeline.forEach(event => {
178
+ const currentTime = new Date(event.timestamp);
179
+
180
+ // Start new workflow if gap is too large or first event
181
+ if (!lastTimestamp ||
182
+ (currentTime - lastTimestamp) > (SESSION_GAP_MINUTES * 60 * 1000)) {
183
+
184
+ // Save previous workflow if it had multiple agents
185
+ if (currentWorkflow.length > 1) {
186
+ const workflowKey = currentWorkflow.map(e => e.agentType).join(' → ');
187
+ workflows[workflowKey] = (workflows[workflowKey] || 0) + 1;
188
+ }
189
+
190
+ currentWorkflow = [event];
191
+ } else {
192
+ currentWorkflow.push(event);
193
+ }
194
+
195
+ lastTimestamp = currentTime;
196
+ });
197
+
198
+ // Don't forget the last workflow
199
+ if (currentWorkflow.length > 1) {
200
+ const workflowKey = currentWorkflow.map(e => e.agentType).join(' → ');
201
+ workflows[workflowKey] = (workflows[workflowKey] || 0) + 1;
202
+ }
203
+
204
+ // Convert to sorted array
205
+ return Object.entries(workflows)
206
+ .map(([pattern, count]) => ({ pattern, count }))
207
+ .sort((a, b) => b.count - a.count)
208
+ .slice(0, 10); // Top 10 workflows
209
+ }
210
+
211
+ /**
212
+ * Calculate popular usage hours across all agents
213
+ * @param {Object} agentStats - Agent statistics
214
+ * @returns {Array} Hour usage data
215
+ */
216
+ calculatePopularHours(agentStats) {
217
+ const hourlyTotals = new Array(24).fill(0);
218
+
219
+ Object.values(agentStats).forEach(stats => {
220
+ stats.hourlyDistribution.forEach((count, hour) => {
221
+ hourlyTotals[hour] += count;
222
+ });
223
+ });
224
+
225
+ return hourlyTotals.map((count, hour) => ({
226
+ hour,
227
+ count,
228
+ label: `${hour.toString().padStart(2, '0')}:00`
229
+ }));
230
+ }
231
+
232
+ /**
233
+ * Calculate daily usage across all agents
234
+ * @param {Object} agentStats - Agent statistics
235
+ * @returns {Array} Daily usage data
236
+ */
237
+ calculateDailyUsage(agentStats) {
238
+ const dailyTotals = {};
239
+
240
+ Object.values(agentStats).forEach(stats => {
241
+ Object.entries(stats.dailyUsage).forEach(([date, count]) => {
242
+ dailyTotals[date] = (dailyTotals[date] || 0) + count;
243
+ });
244
+ });
245
+
246
+ return Object.entries(dailyTotals)
247
+ .map(([date, count]) => ({
248
+ date,
249
+ count,
250
+ timestamp: new Date(date)
251
+ }))
252
+ .sort((a, b) => a.timestamp - b.timestamp);
253
+ }
254
+
255
+ /**
256
+ * Calculate agent efficiency metrics
257
+ * @param {Object} agentStats - Agent statistics
258
+ * @returns {Object} Efficiency metrics
259
+ */
260
+ calculateAgentEfficiency(agentStats) {
261
+ const agents = Object.values(agentStats);
262
+ if (agents.length === 0) return {};
263
+
264
+ const totalInvocations = agents.reduce((sum, agent) => sum + agent.totalInvocations, 0);
265
+ const totalConversations = agents.reduce((sum, agent) => sum + agent.uniqueConversations, 0);
266
+
267
+ return {
268
+ averageInvocationsPerAgent: (totalInvocations / agents.length).toFixed(1),
269
+ averageConversationsPerAgent: (totalConversations / agents.length).toFixed(1),
270
+ mostUsedAgent: agents[0],
271
+ agentDiversity: agents.length,
272
+ adoptionRate: (agents.filter(a => a.totalInvocations > 1).length / agents.length * 100).toFixed(1)
273
+ };
274
+ }
275
+
276
+ /**
277
+ * Parse JSONL file to extract messages
278
+ * @param {string} filePath - Path to the JSONL file
279
+ * @returns {Array} Array of parsed messages
280
+ */
281
+ async parseJsonlFile(filePath) {
282
+ try {
283
+ if (!await fs.pathExists(filePath)) {
284
+ return null;
285
+ }
286
+
287
+ const content = await fs.readFile(filePath, 'utf8');
288
+ const lines = content.trim().split('\n').filter(line => line.trim());
289
+
290
+ return lines.map(line => {
291
+ try {
292
+ return JSON.parse(line);
293
+ } catch (error) {
294
+ console.warn(`Error parsing JSONL line in ${filePath}:`, error.message);
295
+ return null;
296
+ }
297
+ }).filter(Boolean);
298
+ } catch (error) {
299
+ console.error(`Error reading JSONL file ${filePath}:`, error);
300
+ return null;
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Check if timestamp is within date range
306
+ * @param {string|Date} timestamp - Message timestamp
307
+ * @param {Object} dateRange - Date range with startDate and endDate
308
+ * @returns {boolean} Whether timestamp is in range
309
+ */
310
+ isWithinDateRange(timestamp, dateRange) {
311
+ if (!dateRange || (!dateRange.startDate && !dateRange.endDate)) return true;
312
+
313
+ const messageDate = new Date(timestamp);
314
+ const startDate = dateRange.startDate ? new Date(dateRange.startDate) : new Date(0);
315
+ const endDate = dateRange.endDate ? new Date(dateRange.endDate) : new Date();
316
+
317
+ return messageDate >= startDate && messageDate <= endDate;
318
+ }
319
+
320
+ /**
321
+ * Generate agent usage summary for display
322
+ * @param {Object} analysisResult - Result from analyzeAgentUsage
323
+ * @returns {Object} Summary data
324
+ */
325
+ generateSummary(analysisResult) {
326
+ const { totalAgentInvocations, totalAgentTypes, agentStats, efficiency } = analysisResult;
327
+
328
+ return {
329
+ totalInvocations: totalAgentInvocations,
330
+ totalAgentTypes,
331
+ topAgent: agentStats[0] || null,
332
+ averageUsage: efficiency.averageInvocationsPerAgent,
333
+ adoptionRate: efficiency.adoptionRate,
334
+ summary: totalAgentInvocations > 0 ?
335
+ `${totalAgentInvocations} agent invocations across ${totalAgentTypes} different agents` :
336
+ 'No agent usage detected'
337
+ };
338
+ }
339
+ }
340
+
341
+ module.exports = AgentAnalyzer;
@@ -6,34 +6,40 @@ const chalk = require('chalk');
6
6
 
7
7
  class SessionAnalyzer {
8
8
  constructor() {
9
- this.SESSION_DURATION = 5 * 60 * 60 * 1000; // 5 hours in milliseconds
9
+ // CORRECTED: Sessions don't have fixed duration - they reset at scheduled times
10
+ // Reset hours: 1am, 7am, 1pm, 7pm local time
11
+ this.RESET_HOURS = [1, 7, 13, 19];
10
12
  this.MONTHLY_SESSION_LIMIT = 50;
11
13
 
12
- // Plan-specific message limits (conservative estimates)
14
+ // Plan-specific usage information (Claude uses complexity-based limits, not fixed message counts)
13
15
  this.PLAN_LIMITS = {
14
16
  'free': {
15
17
  name: 'Free Plan',
16
- messagesPerSession: null,
18
+ estimatedMessagesPerSession: null,
17
19
  monthlyPrice: 0,
18
- hasSessionLimits: false
20
+ hasSessionLimits: false,
21
+ description: 'Daily usage limits apply'
19
22
  },
20
23
  'standard': {
21
24
  name: 'Pro Plan',
22
- messagesPerSession: 45,
25
+ estimatedMessagesPerSession: 45, // Rough estimate for ~200 sentence messages
23
26
  monthlyPrice: 20,
24
- hasSessionLimits: true
27
+ hasSessionLimits: true,
28
+ description: 'Usage based on message complexity, conversation length, and current capacity. Limits reset every 5 hours.'
25
29
  },
26
30
  'max': {
27
31
  name: 'Max Plan (5x)',
28
- messagesPerSession: 225,
32
+ estimatedMessagesPerSession: null, // 5x more than Pro, but still complexity-based
29
33
  monthlyPrice: 100,
30
- hasSessionLimits: true
34
+ hasSessionLimits: true,
35
+ description: '5x the usage of Pro plan. Complexity-based limits.'
31
36
  },
32
37
  'premium': {
33
- name: 'Max Plan (5x)',
34
- messagesPerSession: 900,
38
+ name: 'Max Plan (20x)',
39
+ estimatedMessagesPerSession: null, // 20x more than Pro
35
40
  monthlyPrice: 200,
36
- hasSessionLimits: true
41
+ hasSessionLimits: true,
42
+ description: '20x the usage of Pro plan. Complexity-based limits.'
37
43
  }
38
44
  };
39
45
  }
@@ -325,7 +331,7 @@ class SessionAnalyzer {
325
331
 
326
332
  // Create current session based on Claude's actual session window
327
333
  const sessionStartTime = new Date(claudeSessionInfo.startTime);
328
- const sessionEndTime = new Date(claudeSessionInfo.startTime + claudeSessionInfo.sessionLimit.ms);
334
+ const sessionEndTime = new Date(claudeSessionInfo.sessionLimit.nextResetTime);
329
335
  const now = new Date();
330
336
 
331
337
  // Find the first user message that occurred AT OR AFTER the Claude session started
@@ -346,8 +352,50 @@ class SessionAnalyzer {
346
352
  return msgTime >= effectiveSessionStart && msgTime < sessionEndTime;
347
353
  });
348
354
 
355
+ // If no estimated messages found in session window, check for active conversations by lastModified
349
356
  if (currentSessionMessages.length === 0) {
350
- return [];
357
+ const RECENT_ACTIVITY_THRESHOLD = 10 * 60 * 1000; // 10 minutes
358
+ const now = new Date();
359
+
360
+ // Find conversations with recent activity (lastModified within session timeframe)
361
+ const activeConversations = conversations.filter(conversation => {
362
+ if (!conversation.lastModified) return false;
363
+
364
+ const lastModified = new Date(conversation.lastModified);
365
+ const timeSinceModified = now - lastModified;
366
+
367
+ // Consider conversation active if:
368
+ // 1. Modified after session start, AND
369
+ // 2. Recently modified (within threshold)
370
+ return lastModified >= sessionStartTime && timeSinceModified < RECENT_ACTIVITY_THRESHOLD;
371
+ });
372
+
373
+ if (activeConversations.length === 0) {
374
+ return [];
375
+ }
376
+
377
+ // Create messages for active conversations based on their real message count
378
+ activeConversations.forEach(conversation => {
379
+ const lastModified = new Date(conversation.lastModified);
380
+ const messageCount = conversation.messageCount || 0;
381
+
382
+ // Create messages based on real message count, distributing them over the session
383
+ const sessionDuration = now - sessionStartTime;
384
+ const timePerMessage = sessionDuration / messageCount;
385
+
386
+ for (let i = 0; i < messageCount; i++) {
387
+ // Distribute messages over the session timeline, alternating user/assistant
388
+ const messageTime = new Date(sessionStartTime.getTime() + (i * timePerMessage));
389
+ const role = i % 2 === 0 ? 'user' : 'assistant';
390
+
391
+ currentSessionMessages.push({
392
+ timestamp: messageTime,
393
+ role: role,
394
+ conversationId: conversation.id,
395
+ usage: conversation.tokenUsage || null
396
+ });
397
+ }
398
+ });
351
399
  }
352
400
 
353
401
  // Create the current session object
@@ -409,12 +457,19 @@ class SessionAnalyzer {
409
457
  getCurrentActiveSessionFromClaudeInfo(sessions, claudeSessionInfo) {
410
458
  if (sessions.length === 0) return null;
411
459
 
412
- // If Claude session is expired, return null
413
- if (claudeSessionInfo.estimatedTimeRemaining.isExpired) {
414
- return null;
460
+ const now = Date.now();
461
+ const RECENT_ACTIVITY_THRESHOLD = 5 * 60 * 1000; // 5 minutes
462
+
463
+ // Check if there's recent activity - sessions can be renewed at reset time
464
+ const timeSinceLastUpdate = now - claudeSessionInfo.lastUpdate;
465
+ const hasRecentActivity = timeSinceLastUpdate < RECENT_ACTIVITY_THRESHOLD;
466
+
467
+ // Session is active if not expired OR has recent activity (session was renewed)
468
+ if (!claudeSessionInfo.estimatedTimeRemaining.isExpired || hasRecentActivity) {
469
+ return sessions[0];
415
470
  }
416
471
 
417
- return sessions[0]; // Since we only create one session based on Claude's current session
472
+ return null;
418
473
  }
419
474
 
420
475
  /**
@@ -516,38 +571,33 @@ class SessionAnalyzer {
516
571
  const warnings = [];
517
572
  const planLimits = this.PLAN_LIMITS[userPlan.planType] || this.PLAN_LIMITS['standard'];
518
573
 
519
- // Session-level warnings
574
+ // Session-level warnings - only for time remaining and token usage
520
575
  if (currentSession) {
521
- const sessionProgress = currentSession.messageCount / planLimits.messagesPerSession;
522
-
523
- if (sessionProgress >= 0.9) {
524
- warnings.push({
525
- type: 'session_limit_critical',
526
- level: 'error',
527
- message: `You're near your session message limit (${currentSession.messageCount}/${planLimits.messagesPerSession})`,
528
- timeRemaining: currentSession.timeRemaining
529
- });
530
- } else if (sessionProgress >= 0.75) {
576
+ // Time remaining warning (30 minutes before reset)
577
+ if (currentSession.timeRemaining < 30 * 60 * 1000) { // 30 minutes
531
578
  warnings.push({
532
- type: 'session_limit_warning',
533
- level: 'warning',
534
- message: `75% of session messages used (${currentSession.messageCount}/${planLimits.messagesPerSession})`,
579
+ type: 'session_time_warning',
580
+ level: 'info',
581
+ message: `Session resets in ${Math.round(currentSession.timeRemaining / 60000)} minutes`,
535
582
  timeRemaining: currentSession.timeRemaining
536
583
  });
537
584
  }
538
585
 
539
- // Time remaining warning
540
- if (currentSession.timeRemaining < 30 * 60 * 1000) { // 30 minutes
586
+ // High token usage warning (if we have token data and it's exceptionally high)
587
+ if (currentSession.tokenUsage && currentSession.tokenUsage.total > 1000000) { // 1M tokens
541
588
  warnings.push({
542
- type: 'session_time_warning',
589
+ type: 'high_token_usage',
543
590
  level: 'info',
544
- message: `Session expires in ${Math.round(currentSession.timeRemaining / 60000)} minutes`,
545
- timeRemaining: currentSession.timeRemaining
591
+ message: `High token usage in this session (${Math.round(currentSession.tokenUsage.total / 1000)}K tokens)`,
592
+ tokenUsage: currentSession.tokenUsage.total
546
593
  });
547
594
  }
595
+
596
+ // Note: We don't warn about message counts since Claude uses complexity-based limits
597
+ // that can't be accurately predicted from simple message counts
548
598
  }
549
599
 
550
- // Monthly warnings
600
+ // Monthly warnings (these limits are more predictable)
551
601
  const monthlyProgress = monthlyUsage.sessionCount / this.MONTHLY_SESSION_LIMIT;
552
602
 
553
603
  if (monthlyProgress >= 0.9) {
@@ -605,25 +655,30 @@ class SessionAnalyzer {
605
655
  // Ensure limits exist, fallback to standard plan
606
656
  const planLimits = limits || this.PLAN_LIMITS['standard'];
607
657
 
608
- // Use weighted message calculation for more accurate progress
609
- const weightedProgress = (currentSession.messageWeight / planLimits.messagesPerSession) * 100;
658
+ // Calculate only user messages (Claude only counts prompts toward limits)
659
+ const userMessages = currentSession.messages ? currentSession.messages.filter(msg => msg.role === 'user') : [];
660
+ const userMessageCount = userMessages.length;
610
661
 
611
662
  return {
612
663
  hasActiveSession: true,
613
664
  timeRemaining: currentSession.timeRemaining,
614
665
  timeRemainingFormatted: this.formatTimeRemaining(currentSession.timeRemaining),
615
- messagesUsed: currentSession.messageCount,
616
- messagesLimit: planLimits.messagesPerSession,
617
- messageWeight: currentSession.messageWeight,
618
- usageDetails: currentSession.usageDetails,
666
+ messagesUsed: userMessageCount,
667
+ messagesEstimate: planLimits.estimatedMessagesPerSession, // Show as estimate, not limit
619
668
  tokensUsed: currentSession.tokenUsage.total,
620
- sessionProgress: weightedProgress,
621
- sessionProgressSimple: (currentSession.messageCount / planLimits.messagesPerSession) * 100,
622
669
  planName: planLimits.name,
670
+ planDescription: planLimits.description,
623
671
  monthlySessionsUsed: monthlyUsage.sessionCount,
624
672
  monthlySessionsLimit: this.MONTHLY_SESSION_LIMIT,
625
673
  warnings: warnings.filter(w => w.type.includes('session')),
626
- willResetAt: currentSession.endTime
674
+ willResetAt: currentSession.endTime,
675
+ // Usage insights
676
+ usageInsights: {
677
+ tokensPerMessage: userMessageCount > 0 ? Math.round(currentSession.tokenUsage.total / userMessageCount) : 0,
678
+ averageMessageComplexity: userMessageCount > 0 ? currentSession.messageWeight / userMessageCount : 0,
679
+ conversationLength: currentSession.messages ? currentSession.messages.length : 0,
680
+ sessionDuration: Date.now() - currentSession.startTime
681
+ }
627
682
  };
628
683
  }
629
684
  }