claude-code-templates 1.20.2 → 1.21.0
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/bin/create-claude-config.js +5 -1
- package/package.json +1 -1
- package/src/analytics/core/AgentAnalyzer.js +3 -3
- package/src/analytics/core/ConversationAnalyzer.js +2 -1
- package/src/analytics-web/components/ActivityHeatmap.js +782 -0
- package/src/analytics-web/components/DashboardPage.js +58 -0
- package/src/analytics-web/index.html +471 -1
- package/src/analytics-web/services/DataService.js +7 -0
- package/src/analytics.js +234 -0
- package/src/index.js +443 -6
- package/src/sandbox-server.js +555 -0
- package/src/test-activity-data.json +1094 -0
package/src/analytics.js
CHANGED
|
@@ -1191,6 +1191,90 @@ class ClaudeAnalytics {
|
|
|
1191
1191
|
}
|
|
1192
1192
|
});
|
|
1193
1193
|
|
|
1194
|
+
// Clear cache endpoint
|
|
1195
|
+
this.app.post('/api/clear-cache', async (req, res) => {
|
|
1196
|
+
try {
|
|
1197
|
+
console.log('🔥 Clear cache request received');
|
|
1198
|
+
|
|
1199
|
+
// Clear DataCache
|
|
1200
|
+
if (this.dataCache && typeof this.dataCache.clear === 'function') {
|
|
1201
|
+
this.dataCache.clear();
|
|
1202
|
+
console.log('🔥 Server DataCache cleared');
|
|
1203
|
+
} else {
|
|
1204
|
+
console.log('⚠️ DataCache not available or no clear method');
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
// Also clear ConversationAnalyzer cache if available
|
|
1208
|
+
if (this.conversationAnalyzer && typeof this.conversationAnalyzer.clearCache === 'function') {
|
|
1209
|
+
this.conversationAnalyzer.clearCache();
|
|
1210
|
+
console.log('🔥 ConversationAnalyzer cache cleared');
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
res.json({
|
|
1214
|
+
success: true,
|
|
1215
|
+
message: 'Cache cleared successfully',
|
|
1216
|
+
timestamp: new Date().toISOString()
|
|
1217
|
+
});
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
console.error('❌ Error clearing cache:', error);
|
|
1220
|
+
res.status(500).json({
|
|
1221
|
+
error: 'Failed to clear cache',
|
|
1222
|
+
details: error.message
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
|
|
1227
|
+
// Activity heatmap data endpoint - needs full conversation history
|
|
1228
|
+
this.app.get('/api/activity', async (req, res) => {
|
|
1229
|
+
try {
|
|
1230
|
+
// TEMPORARY: Use test data for demo/screenshots
|
|
1231
|
+
console.log(`🔥 /api/activity called - using test data for demo...`);
|
|
1232
|
+
const fs = require('fs');
|
|
1233
|
+
const path = require('path');
|
|
1234
|
+
const testDataPath = path.join(__dirname, 'test-activity-data.json');
|
|
1235
|
+
|
|
1236
|
+
if (fs.existsSync(testDataPath)) {
|
|
1237
|
+
const testData = JSON.parse(fs.readFileSync(testDataPath, 'utf8'));
|
|
1238
|
+
|
|
1239
|
+
// Calculate totals
|
|
1240
|
+
const totalContributions = testData.reduce((sum, day) => sum + day.conversations, 0);
|
|
1241
|
+
const totalTools = testData.reduce((sum, day) => sum + day.tools, 0);
|
|
1242
|
+
const totalMessages = testData.reduce((sum, day) => sum + day.messages, 0);
|
|
1243
|
+
const totalTokens = testData.reduce((sum, day) => sum + day.tokens, 0);
|
|
1244
|
+
|
|
1245
|
+
const activityData = {
|
|
1246
|
+
dailyActivity: testData,
|
|
1247
|
+
totalContributions,
|
|
1248
|
+
activeDays: testData.length,
|
|
1249
|
+
longestStreak: 7, // Sample streak
|
|
1250
|
+
currentStreak: 3, // Sample current streak
|
|
1251
|
+
totalTools,
|
|
1252
|
+
totalMessages,
|
|
1253
|
+
totalTokens,
|
|
1254
|
+
timestamp: new Date().toISOString()
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
return res.json(activityData);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Fallback to real data if test file doesn't exist
|
|
1261
|
+
console.log(`🔥 /api/activity called - loading all conversations...`);
|
|
1262
|
+
const allConversations = await this.conversationAnalyzer.loadConversations(this.stateCalculator);
|
|
1263
|
+
console.log(`🔥 Loaded ${allConversations.length} conversations from server`);
|
|
1264
|
+
|
|
1265
|
+
// Generate activity data using complete dataset
|
|
1266
|
+
const activityData = this.generateActivityDataFromConversations(allConversations);
|
|
1267
|
+
res.json({
|
|
1268
|
+
conversations: allConversations, // Also include conversations for the heatmap component
|
|
1269
|
+
...activityData,
|
|
1270
|
+
timestamp: new Date().toISOString()
|
|
1271
|
+
});
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
console.error('Error generating activity data:', error);
|
|
1274
|
+
res.status(500).json({ error: 'Failed to generate activity data' });
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1194
1278
|
// Main dashboard route
|
|
1195
1279
|
this.app.get('/', (req, res) => {
|
|
1196
1280
|
res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
|
|
@@ -2033,6 +2117,156 @@ class ClaudeAnalytics {
|
|
|
2033
2117
|
}
|
|
2034
2118
|
}
|
|
2035
2119
|
|
|
2120
|
+
/**
|
|
2121
|
+
* Generate activity data for the heatmap using complete conversation set
|
|
2122
|
+
* @param {Array} conversations - Complete conversation array (not limited)
|
|
2123
|
+
* @returns {Object} Activity data including daily contributions and stats
|
|
2124
|
+
*/
|
|
2125
|
+
generateActivityDataFromConversations(conversations) {
|
|
2126
|
+
const dailyActivity = new Map();
|
|
2127
|
+
const oneYearAgo = new Date();
|
|
2128
|
+
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
|
2129
|
+
oneYearAgo.setHours(0, 0, 0, 0);
|
|
2130
|
+
|
|
2131
|
+
const today = new Date();
|
|
2132
|
+
const todayEnd = new Date();
|
|
2133
|
+
todayEnd.setHours(23, 59, 59, 999);
|
|
2134
|
+
|
|
2135
|
+
console.log(`🔥 Generating activity data for ${conversations.length} conversations (FULL DATASET)...`);
|
|
2136
|
+
|
|
2137
|
+
// Process conversations for daily activity
|
|
2138
|
+
conversations.forEach(conversation => {
|
|
2139
|
+
if (!conversation.lastModified) return;
|
|
2140
|
+
|
|
2141
|
+
const date = new Date(conversation.lastModified);
|
|
2142
|
+
if (date < oneYearAgo || date > todayEnd) return;
|
|
2143
|
+
|
|
2144
|
+
const dateKey = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
2145
|
+
|
|
2146
|
+
const current = dailyActivity.get(dateKey) || {
|
|
2147
|
+
conversations: 0,
|
|
2148
|
+
tokens: 0,
|
|
2149
|
+
messages: 0,
|
|
2150
|
+
tools: 0,
|
|
2151
|
+
date: dateKey
|
|
2152
|
+
};
|
|
2153
|
+
|
|
2154
|
+
current.conversations += 1;
|
|
2155
|
+
current.tokens += conversation.tokens || 0;
|
|
2156
|
+
current.messages += conversation.messageCount || 0;
|
|
2157
|
+
current.tools += (conversation.toolUsage?.totalToolCalls || 0);
|
|
2158
|
+
|
|
2159
|
+
dailyActivity.set(dateKey, current);
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
// Convert to array and sort by date
|
|
2163
|
+
const activityArray = Array.from(dailyActivity.values())
|
|
2164
|
+
.sort((a, b) => a.date.localeCompare(b.date));
|
|
2165
|
+
|
|
2166
|
+
// Calculate stats
|
|
2167
|
+
const totalContributions = activityArray.reduce((sum, day) => sum + day.conversations, 0);
|
|
2168
|
+
const totalTools = activityArray.reduce((sum, day) => sum + day.tools, 0);
|
|
2169
|
+
const { longestStreak, currentStreak } = this.calculateStreaks(activityArray);
|
|
2170
|
+
|
|
2171
|
+
console.log(`🔥 Activity data generated: ${activityArray.length} active days, ${totalContributions} total contributions, ${totalTools} tool calls`);
|
|
2172
|
+
|
|
2173
|
+
return {
|
|
2174
|
+
dailyActivity: activityArray,
|
|
2175
|
+
totalContributions,
|
|
2176
|
+
totalTools,
|
|
2177
|
+
longestStreak,
|
|
2178
|
+
currentStreak,
|
|
2179
|
+
activeDays: activityArray.length,
|
|
2180
|
+
startDate: oneYearAgo.toISOString().split('T')[0],
|
|
2181
|
+
endDate: today.toISOString().split('T')[0]
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/**
|
|
2186
|
+
* Generate activity data for the heatmap (legacy method for backward compatibility)
|
|
2187
|
+
* @returns {Object} Activity data including daily contributions and stats
|
|
2188
|
+
*/
|
|
2189
|
+
generateActivityData() {
|
|
2190
|
+
if (!this.data || !this.data.conversations) {
|
|
2191
|
+
return {
|
|
2192
|
+
dailyActivity: [],
|
|
2193
|
+
totalContributions: 0,
|
|
2194
|
+
longestStreak: 0,
|
|
2195
|
+
currentStreak: 0
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
return this.generateActivityDataFromConversations(this.data.conversations);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
/**
|
|
2203
|
+
* Calculate contribution streaks
|
|
2204
|
+
* @param {Array} activityArray - Sorted array of daily activity
|
|
2205
|
+
* @returns {Object} Longest and current streak counts
|
|
2206
|
+
*/
|
|
2207
|
+
calculateStreaks(activityArray) {
|
|
2208
|
+
if (activityArray.length === 0) {
|
|
2209
|
+
return { longestStreak: 0, currentStreak: 0 };
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
let longestStreak = 0;
|
|
2213
|
+
let currentStreak = 0;
|
|
2214
|
+
let tempStreak = 0;
|
|
2215
|
+
|
|
2216
|
+
const today = new Date();
|
|
2217
|
+
today.setHours(0, 0, 0, 0);
|
|
2218
|
+
|
|
2219
|
+
// Create a map of active dates for quick lookup
|
|
2220
|
+
const activeDates = new Set(activityArray.map(day => day.date));
|
|
2221
|
+
|
|
2222
|
+
// Check streak going backwards from today
|
|
2223
|
+
let checkDate = new Date(today);
|
|
2224
|
+
let foundToday = false;
|
|
2225
|
+
|
|
2226
|
+
// First check if today or yesterday has activity
|
|
2227
|
+
for (let i = 0; i < 2; i++) {
|
|
2228
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
2229
|
+
if (activeDates.has(dateKey)) {
|
|
2230
|
+
foundToday = true;
|
|
2231
|
+
break;
|
|
2232
|
+
}
|
|
2233
|
+
checkDate.setDate(checkDate.getDate() - 1);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
if (foundToday) {
|
|
2237
|
+
// Calculate current streak
|
|
2238
|
+
checkDate = new Date(today);
|
|
2239
|
+
while (true) {
|
|
2240
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
2241
|
+
if (activeDates.has(dateKey)) {
|
|
2242
|
+
currentStreak++;
|
|
2243
|
+
checkDate.setDate(checkDate.getDate() - 1);
|
|
2244
|
+
} else {
|
|
2245
|
+
break;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
// Calculate longest streak by checking all consecutive days
|
|
2251
|
+
const oneYearAgo = new Date(today);
|
|
2252
|
+
oneYearAgo.setFullYear(today.getFullYear() - 1);
|
|
2253
|
+
|
|
2254
|
+
checkDate = new Date(oneYearAgo);
|
|
2255
|
+
while (checkDate <= today) {
|
|
2256
|
+
const dateKey = checkDate.toISOString().split('T')[0];
|
|
2257
|
+
|
|
2258
|
+
if (activeDates.has(dateKey)) {
|
|
2259
|
+
tempStreak++;
|
|
2260
|
+
longestStreak = Math.max(longestStreak, tempStreak);
|
|
2261
|
+
} else {
|
|
2262
|
+
tempStreak = 0;
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
checkDate.setDate(checkDate.getDate() + 1);
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
return { longestStreak, currentStreak };
|
|
2269
|
+
}
|
|
2036
2270
|
|
|
2037
2271
|
stop() {
|
|
2038
2272
|
// Stop file watchers
|