claude-code-templates 1.20.2 → 1.20.3

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/src/analytics.js CHANGED
@@ -1191,6 +1191,27 @@ class ClaudeAnalytics {
1191
1191
  }
1192
1192
  });
1193
1193
 
1194
+ // Activity heatmap data endpoint - needs full conversation history
1195
+ this.app.get('/api/activity', async (req, res) => {
1196
+ try {
1197
+ console.log(`🔥 /api/activity called - loading all conversations...`);
1198
+ // Get all conversations without the 150 limit for accurate heatmap data
1199
+ const allConversations = await this.conversationAnalyzer.loadConversations(this.stateCalculator);
1200
+ console.log(`🔥 Loaded ${allConversations.length} conversations from server`);
1201
+
1202
+ // Generate activity data using complete dataset
1203
+ const activityData = this.generateActivityDataFromConversations(allConversations);
1204
+ res.json({
1205
+ conversations: allConversations, // Also include conversations for the heatmap component
1206
+ ...activityData,
1207
+ timestamp: new Date().toISOString()
1208
+ });
1209
+ } catch (error) {
1210
+ console.error('Error generating activity data:', error);
1211
+ res.status(500).json({ error: 'Failed to generate activity data' });
1212
+ }
1213
+ });
1214
+
1194
1215
  // Main dashboard route
1195
1216
  this.app.get('/', (req, res) => {
1196
1217
  res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
@@ -2033,6 +2054,172 @@ class ClaudeAnalytics {
2033
2054
  }
2034
2055
  }
2035
2056
 
2057
+ /**
2058
+ * Generate activity data for the heatmap using complete conversation set
2059
+ * @param {Array} conversations - Complete conversation array (not limited)
2060
+ * @returns {Object} Activity data including daily contributions and stats
2061
+ */
2062
+ generateActivityDataFromConversations(conversations) {
2063
+ const dailyActivity = new Map();
2064
+ const oneYearAgo = new Date();
2065
+ oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
2066
+ oneYearAgo.setHours(0, 0, 0, 0);
2067
+
2068
+ const today = new Date();
2069
+ today.setHours(23, 59, 59, 999);
2070
+
2071
+ console.log(`🔥 Generating activity data for ${conversations.length} conversations (FULL DATASET)...`);
2072
+
2073
+ // Show date range of conversations
2074
+ if (conversations.length > 0) {
2075
+ const dates = conversations.map(c => c.lastModified ? new Date(c.lastModified) : null).filter(Boolean);
2076
+ dates.sort((a, b) => a - b);
2077
+ console.log(`🔥 Server conversation date range: ${dates[0]?.toLocaleDateString()} to ${dates[dates.length - 1]?.toLocaleDateString()}`);
2078
+
2079
+ // Show some sample conversations with dates
2080
+ console.log(`🔥 First 3 conversations by date:`);
2081
+ const sortedConversations = conversations
2082
+ .filter(c => c.lastModified)
2083
+ .sort((a, b) => new Date(a.lastModified) - new Date(b.lastModified))
2084
+ .slice(0, 3);
2085
+ sortedConversations.forEach((conv, i) => {
2086
+ console.log(` ${i+1}: ${conv.filename} - ${new Date(conv.lastModified).toLocaleDateString()}`);
2087
+ });
2088
+ }
2089
+
2090
+ // Process conversations for daily activity
2091
+ conversations.forEach(conversation => {
2092
+ if (!conversation.lastModified) return;
2093
+
2094
+ const date = new Date(conversation.lastModified);
2095
+ if (date < oneYearAgo || date > today) return;
2096
+
2097
+ const dateKey = date.toISOString().split('T')[0]; // YYYY-MM-DD
2098
+
2099
+ const current = dailyActivity.get(dateKey) || {
2100
+ conversations: 0,
2101
+ tokens: 0,
2102
+ messages: 0,
2103
+ tools: 0,
2104
+ date: dateKey
2105
+ };
2106
+
2107
+ current.conversations += 1;
2108
+ current.tokens += conversation.tokens || 0;
2109
+ current.messages += conversation.messageCount || 0;
2110
+ current.tools += (conversation.toolUsage?.totalToolCalls || 0);
2111
+
2112
+ dailyActivity.set(dateKey, current);
2113
+ });
2114
+
2115
+ // Convert to array and sort by date
2116
+ const activityArray = Array.from(dailyActivity.values())
2117
+ .sort((a, b) => a.date.localeCompare(b.date));
2118
+
2119
+ // Calculate stats
2120
+ const totalContributions = activityArray.reduce((sum, day) => sum + day.conversations, 0);
2121
+ const totalTools = activityArray.reduce((sum, day) => sum + day.tools, 0);
2122
+ const { longestStreak, currentStreak } = this.calculateStreaks(activityArray);
2123
+
2124
+ console.log(`🔥 Activity data generated: ${activityArray.length} active days, ${totalContributions} total contributions, ${totalTools} tool calls`);
2125
+
2126
+ return {
2127
+ dailyActivity: activityArray,
2128
+ totalContributions,
2129
+ totalTools,
2130
+ longestStreak,
2131
+ currentStreak,
2132
+ activeDays: activityArray.length,
2133
+ startDate: oneYearAgo.toISOString().split('T')[0],
2134
+ endDate: today.toISOString().split('T')[0]
2135
+ };
2136
+ }
2137
+
2138
+ /**
2139
+ * Generate activity data for the heatmap (legacy method for backward compatibility)
2140
+ * @returns {Object} Activity data including daily contributions and stats
2141
+ */
2142
+ generateActivityData() {
2143
+ if (!this.data || !this.data.conversations) {
2144
+ return {
2145
+ dailyActivity: [],
2146
+ totalContributions: 0,
2147
+ longestStreak: 0,
2148
+ currentStreak: 0
2149
+ };
2150
+ }
2151
+
2152
+ return this.generateActivityDataFromConversations(this.data.conversations);
2153
+ }
2154
+
2155
+ /**
2156
+ * Calculate contribution streaks
2157
+ * @param {Array} activityArray - Sorted array of daily activity
2158
+ * @returns {Object} Longest and current streak counts
2159
+ */
2160
+ calculateStreaks(activityArray) {
2161
+ if (activityArray.length === 0) {
2162
+ return { longestStreak: 0, currentStreak: 0 };
2163
+ }
2164
+
2165
+ let longestStreak = 0;
2166
+ let currentStreak = 0;
2167
+ let tempStreak = 0;
2168
+
2169
+ const today = new Date();
2170
+ today.setHours(0, 0, 0, 0);
2171
+
2172
+ // Create a map of active dates for quick lookup
2173
+ const activeDates = new Set(activityArray.map(day => day.date));
2174
+
2175
+ // Check streak going backwards from today
2176
+ let checkDate = new Date(today);
2177
+ let foundToday = false;
2178
+
2179
+ // First check if today or yesterday has activity
2180
+ for (let i = 0; i < 2; i++) {
2181
+ const dateKey = checkDate.toISOString().split('T')[0];
2182
+ if (activeDates.has(dateKey)) {
2183
+ foundToday = true;
2184
+ break;
2185
+ }
2186
+ checkDate.setDate(checkDate.getDate() - 1);
2187
+ }
2188
+
2189
+ if (foundToday) {
2190
+ // Calculate current streak
2191
+ checkDate = new Date(today);
2192
+ while (true) {
2193
+ const dateKey = checkDate.toISOString().split('T')[0];
2194
+ if (activeDates.has(dateKey)) {
2195
+ currentStreak++;
2196
+ checkDate.setDate(checkDate.getDate() - 1);
2197
+ } else {
2198
+ break;
2199
+ }
2200
+ }
2201
+ }
2202
+
2203
+ // Calculate longest streak by checking all consecutive days
2204
+ const oneYearAgo = new Date(today);
2205
+ oneYearAgo.setFullYear(today.getFullYear() - 1);
2206
+
2207
+ checkDate = new Date(oneYearAgo);
2208
+ while (checkDate <= today) {
2209
+ const dateKey = checkDate.toISOString().split('T')[0];
2210
+
2211
+ if (activeDates.has(dateKey)) {
2212
+ tempStreak++;
2213
+ longestStreak = Math.max(longestStreak, tempStreak);
2214
+ } else {
2215
+ tempStreak = 0;
2216
+ }
2217
+
2218
+ checkDate.setDate(checkDate.getDate() + 1);
2219
+ }
2220
+
2221
+ return { longestStreak, currentStreak };
2222
+ }
2036
2223
 
2037
2224
  stop() {
2038
2225
  // Stop file watchers