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.
- package/bin/create-claude-config.js +3 -0
- package/package.json +1 -1
- package/src/analytics/core/AgentAnalyzer.js +341 -0
- package/src/analytics/core/SessionAnalyzer.js +101 -46
- package/src/analytics-web/components/AgentAnalytics.js +710 -0
- package/src/analytics-web/components/DashboardPage.js +810 -39
- package/src/analytics-web/components/SessionTimer.js +14 -11
- package/src/analytics-web/index.html +666 -65
- package/src/analytics.js +112 -10
- package/src/index.js +183 -0
- package/src/analytics-web/components/Dashboard.js.deprecated +0 -589
package/src/analytics.js
CHANGED
|
@@ -10,6 +10,7 @@ const ProcessDetector = require('./analytics/core/ProcessDetector');
|
|
|
10
10
|
const ConversationAnalyzer = require('./analytics/core/ConversationAnalyzer');
|
|
11
11
|
const FileWatcher = require('./analytics/core/FileWatcher');
|
|
12
12
|
const SessionAnalyzer = require('./analytics/core/SessionAnalyzer');
|
|
13
|
+
const AgentAnalyzer = require('./analytics/core/AgentAnalyzer');
|
|
13
14
|
const DataCache = require('./analytics/data/DataCache');
|
|
14
15
|
const WebSocketServer = require('./analytics/notifications/WebSocketServer');
|
|
15
16
|
const NotificationManager = require('./analytics/notifications/NotificationManager');
|
|
@@ -24,6 +25,7 @@ class ClaudeAnalytics {
|
|
|
24
25
|
this.processDetector = new ProcessDetector();
|
|
25
26
|
this.fileWatcher = new FileWatcher();
|
|
26
27
|
this.sessionAnalyzer = new SessionAnalyzer();
|
|
28
|
+
this.agentAnalyzer = new AgentAnalyzer();
|
|
27
29
|
this.dataCache = new DataCache();
|
|
28
30
|
this.performanceMonitor = new PerformanceMonitor({
|
|
29
31
|
enabled: true,
|
|
@@ -667,6 +669,28 @@ class ClaudeAnalytics {
|
|
|
667
669
|
}
|
|
668
670
|
});
|
|
669
671
|
|
|
672
|
+
// Agent usage analytics endpoint
|
|
673
|
+
this.app.get('/api/agents', async (req, res) => {
|
|
674
|
+
try {
|
|
675
|
+
const startDate = req.query.startDate;
|
|
676
|
+
const endDate = req.query.endDate;
|
|
677
|
+
const dateRange = (startDate || endDate) ? { startDate, endDate } : null;
|
|
678
|
+
|
|
679
|
+
const agentAnalysis = await this.agentAnalyzer.analyzeAgentUsage(this.data.conversations, dateRange);
|
|
680
|
+
const agentSummary = this.agentAnalyzer.generateSummary(agentAnalysis);
|
|
681
|
+
|
|
682
|
+
res.json({
|
|
683
|
+
...agentAnalysis,
|
|
684
|
+
summary: agentSummary,
|
|
685
|
+
dateRange,
|
|
686
|
+
timestamp: new Date().toISOString()
|
|
687
|
+
});
|
|
688
|
+
} catch (error) {
|
|
689
|
+
console.error('Error getting agent analytics:', error);
|
|
690
|
+
res.status(500).json({ error: 'Failed to get agent analytics' });
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
|
|
670
694
|
this.app.get('/api/realtime', async (req, res) => {
|
|
671
695
|
const realtimeWithTimestamp = {
|
|
672
696
|
...this.data.realtimeStats,
|
|
@@ -1661,12 +1685,88 @@ class ClaudeAnalytics {
|
|
|
1661
1685
|
const timeSinceLastUpdate = now - lastUpdate;
|
|
1662
1686
|
const timeSinceLastUpdateMinutes = Math.floor(timeSinceLastUpdate / (1000 * 60));
|
|
1663
1687
|
|
|
1664
|
-
//
|
|
1665
|
-
|
|
1666
|
-
const
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
1688
|
+
// CORRECTED: Calculate next reset time based on scheduled reset hours
|
|
1689
|
+
// Claude sessions reset at specific times, not fixed durations
|
|
1690
|
+
const resetHours = [1, 7, 13, 19]; // 1am, 7am, 1pm, 7pm local time
|
|
1691
|
+
const sessionStartDate = new Date(startTime);
|
|
1692
|
+
|
|
1693
|
+
// Find next reset time after session start
|
|
1694
|
+
let nextResetTime = new Date(sessionStartDate);
|
|
1695
|
+
nextResetTime.setMinutes(0, 0, 0); // Set to exact hour
|
|
1696
|
+
|
|
1697
|
+
// Find the next reset hour
|
|
1698
|
+
let foundReset = false;
|
|
1699
|
+
for (let i = 0; i < resetHours.length * 2; i++) { // Check up to 2 full days
|
|
1700
|
+
const currentDay = Math.floor(i / resetHours.length);
|
|
1701
|
+
const hourIndex = i % resetHours.length;
|
|
1702
|
+
const resetHour = resetHours[hourIndex];
|
|
1703
|
+
|
|
1704
|
+
const testResetTime = new Date(sessionStartDate);
|
|
1705
|
+
testResetTime.setDate(testResetTime.getDate() + currentDay);
|
|
1706
|
+
testResetTime.setHours(resetHour, 0, 0, 0);
|
|
1707
|
+
|
|
1708
|
+
if (testResetTime > sessionStartDate) {
|
|
1709
|
+
nextResetTime = testResetTime;
|
|
1710
|
+
foundReset = true;
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (!foundReset) {
|
|
1716
|
+
// Fallback: assume next day 7am if no pattern found
|
|
1717
|
+
nextResetTime.setDate(nextResetTime.getDate() + 1);
|
|
1718
|
+
nextResetTime.setHours(7, 0, 0, 0);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
const sessionLimitMs = nextResetTime.getTime() - startTime;
|
|
1722
|
+
let timeRemaining = nextResetTime.getTime() - now;
|
|
1723
|
+
let timeRemainingMinutes = Math.floor(timeRemaining / (1000 * 60));
|
|
1724
|
+
let timeRemainingHours = Math.floor(timeRemainingMinutes / 60);
|
|
1725
|
+
let remainingMinutesDisplay = timeRemainingMinutes % 60;
|
|
1726
|
+
|
|
1727
|
+
// If session is expired but has recent activity, calculate next reset time
|
|
1728
|
+
if (timeRemaining <= 0) {
|
|
1729
|
+
const RECENT_ACTIVITY_THRESHOLD = 5 * 60 * 1000; // 5 minutes
|
|
1730
|
+
const timeSinceLastUpdate = now - lastUpdate;
|
|
1731
|
+
|
|
1732
|
+
if (timeSinceLastUpdate < RECENT_ACTIVITY_THRESHOLD) {
|
|
1733
|
+
// Session was renewed, find the NEXT reset time
|
|
1734
|
+
let nextNextResetTime = new Date(nextResetTime);
|
|
1735
|
+
let foundNextReset = false;
|
|
1736
|
+
|
|
1737
|
+
for (let i = 0; i < resetHours.length; i++) {
|
|
1738
|
+
const resetHour = resetHours[i];
|
|
1739
|
+
const testResetTime = new Date(nextResetTime);
|
|
1740
|
+
|
|
1741
|
+
if (resetHour > nextResetTime.getHours()) {
|
|
1742
|
+
// Same day, later hour
|
|
1743
|
+
testResetTime.setHours(resetHour, 0, 0, 0);
|
|
1744
|
+
} else {
|
|
1745
|
+
// Next day
|
|
1746
|
+
testResetTime.setDate(testResetTime.getDate() + 1);
|
|
1747
|
+
testResetTime.setHours(resetHour, 0, 0, 0);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
if (testResetTime > nextResetTime) {
|
|
1751
|
+
nextNextResetTime = testResetTime;
|
|
1752
|
+
foundNextReset = true;
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
if (!foundNextReset) {
|
|
1758
|
+
// Default to next day 1am
|
|
1759
|
+
nextNextResetTime.setDate(nextNextResetTime.getDate() + 1);
|
|
1760
|
+
nextNextResetTime.setHours(1, 0, 0, 0);
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
timeRemaining = nextNextResetTime.getTime() - now;
|
|
1764
|
+
timeRemainingMinutes = Math.floor(timeRemaining / (1000 * 60));
|
|
1765
|
+
timeRemainingHours = Math.floor(timeRemainingMinutes / 60);
|
|
1766
|
+
remainingMinutesDisplay = timeRemainingMinutes % 60;
|
|
1767
|
+
nextResetTime = nextNextResetTime;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1670
1770
|
|
|
1671
1771
|
return {
|
|
1672
1772
|
hasSession: true,
|
|
@@ -1691,13 +1791,15 @@ class ClaudeAnalytics {
|
|
|
1691
1791
|
hours: timeRemainingHours,
|
|
1692
1792
|
remainingMinutes: remainingMinutesDisplay,
|
|
1693
1793
|
formatted: timeRemaining > 0 ? `${timeRemainingHours}h ${remainingMinutesDisplay}m` : 'Session expired',
|
|
1694
|
-
isExpired: timeRemaining <= 0
|
|
1794
|
+
isExpired: timeRemaining <= 0 && timeSinceLastUpdate >= (5 * 60 * 1000) // Expired only if past reset time AND no recent activity
|
|
1695
1795
|
},
|
|
1696
1796
|
sessionLimit: {
|
|
1697
1797
|
ms: sessionLimitMs,
|
|
1698
|
-
hours:
|
|
1699
|
-
minutes:
|
|
1700
|
-
formatted:
|
|
1798
|
+
hours: Math.floor(sessionLimitMs / (1000 * 60 * 60)),
|
|
1799
|
+
minutes: Math.floor((sessionLimitMs % (1000 * 60 * 60)) / (1000 * 60)),
|
|
1800
|
+
formatted: `${Math.floor(sessionLimitMs / (1000 * 60 * 60))}h ${Math.floor((sessionLimitMs % (1000 * 60 * 60)) / (1000 * 60))}m`,
|
|
1801
|
+
nextResetTime: nextResetTime.toISOString(),
|
|
1802
|
+
resetHour: nextResetTime.getHours()
|
|
1701
1803
|
}
|
|
1702
1804
|
};
|
|
1703
1805
|
} catch (error) {
|
package/src/index.js
CHANGED
|
@@ -80,6 +80,22 @@ async function showMainMenu() {
|
|
|
80
80
|
async function createClaudeConfig(options = {}) {
|
|
81
81
|
const targetDir = options.directory || process.cwd();
|
|
82
82
|
|
|
83
|
+
// Handle individual component installation
|
|
84
|
+
if (options.agent) {
|
|
85
|
+
await installIndividualAgent(options.agent, targetDir, options);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (options.command) {
|
|
90
|
+
await installIndividualCommand(options.command, targetDir, options);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.mcp) {
|
|
95
|
+
await installIndividualMCP(options.mcp, targetDir, options);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
83
99
|
// Handle command stats analysis (both singular and plural)
|
|
84
100
|
if (options.commandStats || options.commandsStats) {
|
|
85
101
|
await runCommandStats(options);
|
|
@@ -253,4 +269,171 @@ async function createClaudeConfig(options = {}) {
|
|
|
253
269
|
}
|
|
254
270
|
}
|
|
255
271
|
|
|
272
|
+
// Individual component installation functions
|
|
273
|
+
async function installIndividualAgent(agentName, targetDir, options) {
|
|
274
|
+
console.log(chalk.blue(`🤖 Installing agent: ${agentName}`));
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
// Check if components directory exists
|
|
278
|
+
const componentsPath = path.join(__dirname, '..', 'components', 'agents');
|
|
279
|
+
const agentFile = path.join(componentsPath, `${agentName}.md`);
|
|
280
|
+
|
|
281
|
+
if (!await fs.pathExists(agentFile)) {
|
|
282
|
+
console.log(chalk.red(`❌ Agent "${agentName}" not found`));
|
|
283
|
+
console.log(chalk.yellow('Available agents:'));
|
|
284
|
+
|
|
285
|
+
// List available agents
|
|
286
|
+
if (await fs.pathExists(componentsPath)) {
|
|
287
|
+
const agents = await fs.readdir(componentsPath);
|
|
288
|
+
agents.filter(f => f.endsWith('.md')).forEach(agent => {
|
|
289
|
+
console.log(chalk.cyan(` - ${agent.replace('.md', '')}`));
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// For agents, they are typically part of templates, so we need to determine
|
|
296
|
+
// the appropriate language/framework and install the complete template
|
|
297
|
+
const agentContent = await fs.readFile(agentFile, 'utf8');
|
|
298
|
+
const language = extractLanguageFromAgent(agentContent, agentName);
|
|
299
|
+
const framework = extractFrameworkFromAgent(agentContent, agentName);
|
|
300
|
+
|
|
301
|
+
console.log(chalk.yellow(`📝 Agent "${agentName}" is part of ${language}/${framework} template`));
|
|
302
|
+
console.log(chalk.blue('🚀 Installing complete template with this agent...'));
|
|
303
|
+
|
|
304
|
+
// Install the template that contains this agent (avoid recursion)
|
|
305
|
+
const setupOptions = {
|
|
306
|
+
...options,
|
|
307
|
+
language,
|
|
308
|
+
framework,
|
|
309
|
+
yes: true,
|
|
310
|
+
targetDirectory: targetDir,
|
|
311
|
+
agent: null // Remove agent to avoid recursion
|
|
312
|
+
};
|
|
313
|
+
delete setupOptions.agent;
|
|
314
|
+
await createClaudeConfig(setupOptions);
|
|
315
|
+
|
|
316
|
+
console.log(chalk.green(`✅ Agent "${agentName}" installed successfully!`));
|
|
317
|
+
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.log(chalk.red(`❌ Error installing agent: ${error.message}`));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function installIndividualCommand(commandName, targetDir, options) {
|
|
324
|
+
console.log(chalk.blue(`⚡ Installing command: ${commandName}`));
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
// Check if components directory exists
|
|
328
|
+
const componentsPath = path.join(__dirname, '..', 'components', 'commands');
|
|
329
|
+
const commandFile = path.join(componentsPath, `${commandName}.md`);
|
|
330
|
+
|
|
331
|
+
if (!await fs.pathExists(commandFile)) {
|
|
332
|
+
console.log(chalk.red(`❌ Command "${commandName}" not found`));
|
|
333
|
+
console.log(chalk.yellow('Available commands:'));
|
|
334
|
+
|
|
335
|
+
// List available commands
|
|
336
|
+
if (await fs.pathExists(componentsPath)) {
|
|
337
|
+
const commands = await fs.readdir(componentsPath);
|
|
338
|
+
commands.filter(f => f.endsWith('.md')).forEach(command => {
|
|
339
|
+
console.log(chalk.cyan(` - ${command.replace('.md', '')}`));
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Create .claude/commands directory if it doesn't exist
|
|
346
|
+
const commandsDir = path.join(targetDir, '.claude', 'commands');
|
|
347
|
+
await fs.ensureDir(commandsDir);
|
|
348
|
+
|
|
349
|
+
// Copy the command file
|
|
350
|
+
const targetFile = path.join(commandsDir, `${commandName}.md`);
|
|
351
|
+
await fs.copy(commandFile, targetFile);
|
|
352
|
+
|
|
353
|
+
console.log(chalk.green(`✅ Command "${commandName}" installed successfully!`));
|
|
354
|
+
console.log(chalk.cyan(`📁 Installed to: ${path.relative(targetDir, targetFile)}`));
|
|
355
|
+
|
|
356
|
+
} catch (error) {
|
|
357
|
+
console.log(chalk.red(`❌ Error installing command: ${error.message}`));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function installIndividualMCP(mcpName, targetDir, options) {
|
|
362
|
+
console.log(chalk.blue(`🔌 Installing MCP: ${mcpName}`));
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
// Check if components directory exists
|
|
366
|
+
const componentsPath = path.join(__dirname, '..', 'components', 'mcps');
|
|
367
|
+
const mcpFile = path.join(componentsPath, `${mcpName}.json`);
|
|
368
|
+
|
|
369
|
+
if (!await fs.pathExists(mcpFile)) {
|
|
370
|
+
console.log(chalk.red(`❌ MCP "${mcpName}" not found`));
|
|
371
|
+
console.log(chalk.yellow('Available MCPs:'));
|
|
372
|
+
|
|
373
|
+
// List available MCPs
|
|
374
|
+
if (await fs.pathExists(componentsPath)) {
|
|
375
|
+
const mcps = await fs.readdir(componentsPath);
|
|
376
|
+
mcps.filter(f => f.endsWith('.json')).forEach(mcp => {
|
|
377
|
+
console.log(chalk.cyan(` - ${mcp.replace('.json', '')}`));
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Read the MCP configuration
|
|
384
|
+
const mcpConfig = await fs.readJson(mcpFile);
|
|
385
|
+
|
|
386
|
+
// Check if .mcp.json exists in target directory
|
|
387
|
+
const targetMcpFile = path.join(targetDir, '.mcp.json');
|
|
388
|
+
let existingConfig = {};
|
|
389
|
+
|
|
390
|
+
if (await fs.pathExists(targetMcpFile)) {
|
|
391
|
+
existingConfig = await fs.readJson(targetMcpFile);
|
|
392
|
+
console.log(chalk.yellow('📝 Existing .mcp.json found, merging configurations...'));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Merge configurations
|
|
396
|
+
const mergedConfig = {
|
|
397
|
+
...existingConfig,
|
|
398
|
+
...mcpConfig
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// Write the merged configuration
|
|
402
|
+
await fs.writeJson(targetMcpFile, mergedConfig, { spaces: 2 });
|
|
403
|
+
|
|
404
|
+
console.log(chalk.green(`✅ MCP "${mcpName}" installed successfully!`));
|
|
405
|
+
console.log(chalk.cyan(`📁 Configuration merged into: ${path.relative(targetDir, targetMcpFile)}`));
|
|
406
|
+
|
|
407
|
+
} catch (error) {
|
|
408
|
+
console.log(chalk.red(`❌ Error installing MCP: ${error.message}`));
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Helper functions to extract language/framework from agent content
|
|
413
|
+
function extractLanguageFromAgent(content, agentName) {
|
|
414
|
+
// Try to determine language from agent content or filename
|
|
415
|
+
if (agentName.includes('react') || content.includes('React')) return 'javascript-typescript';
|
|
416
|
+
if (agentName.includes('django') || content.includes('Django')) return 'python';
|
|
417
|
+
if (agentName.includes('fastapi') || content.includes('FastAPI')) return 'python';
|
|
418
|
+
if (agentName.includes('flask') || content.includes('Flask')) return 'python';
|
|
419
|
+
if (agentName.includes('rails') || content.includes('Rails')) return 'ruby';
|
|
420
|
+
if (agentName.includes('api-security') || content.includes('API security')) return 'javascript-typescript';
|
|
421
|
+
if (agentName.includes('database') || content.includes('database')) return 'javascript-typescript';
|
|
422
|
+
|
|
423
|
+
// Default to javascript-typescript for general agents
|
|
424
|
+
return 'javascript-typescript';
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function extractFrameworkFromAgent(content, agentName) {
|
|
428
|
+
// Try to determine framework from agent content or filename
|
|
429
|
+
if (agentName.includes('react') || content.includes('React')) return 'react';
|
|
430
|
+
if (agentName.includes('django') || content.includes('Django')) return 'django';
|
|
431
|
+
if (agentName.includes('fastapi') || content.includes('FastAPI')) return 'fastapi';
|
|
432
|
+
if (agentName.includes('flask') || content.includes('Flask')) return 'flask';
|
|
433
|
+
if (agentName.includes('rails') || content.includes('Rails')) return 'rails';
|
|
434
|
+
|
|
435
|
+
// For general agents, return none to install the base template
|
|
436
|
+
return 'none';
|
|
437
|
+
}
|
|
438
|
+
|
|
256
439
|
module.exports = { createClaudeConfig, showMainMenu };
|