claude-code-templates 1.28.2 → 1.28.4
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 +1 -0
- package/package.json +1 -1
- package/src/analytics/core/YearInReview2025.js +928 -0
- package/src/analytics-web/2025.html +2100 -0
- package/src/analytics.js +39 -3
- package/src/index.js +11 -2
- package/src/plugin-dashboard.js +11 -3
- package/src/skill-dashboard-web/script.js +5 -5
- package/src/skill-dashboard.js +93 -7
- package/src/validation/README.md +1 -1
package/src/analytics.js
CHANGED
|
@@ -14,6 +14,7 @@ const ConversationAnalyzer = require('./analytics/core/ConversationAnalyzer');
|
|
|
14
14
|
const FileWatcher = require('./analytics/core/FileWatcher');
|
|
15
15
|
const SessionAnalyzer = require('./analytics/core/SessionAnalyzer');
|
|
16
16
|
const AgentAnalyzer = require('./analytics/core/AgentAnalyzer');
|
|
17
|
+
const YearInReview2025 = require('./analytics/core/YearInReview2025');
|
|
17
18
|
const DataCache = require('./analytics/data/DataCache');
|
|
18
19
|
const WebSocketServer = require('./analytics/notifications/WebSocketServer');
|
|
19
20
|
const NotificationManager = require('./analytics/notifications/NotificationManager');
|
|
@@ -32,6 +33,7 @@ class ClaudeAnalytics {
|
|
|
32
33
|
this.fileWatcher = new FileWatcher();
|
|
33
34
|
this.sessionAnalyzer = new SessionAnalyzer();
|
|
34
35
|
this.agentAnalyzer = new AgentAnalyzer();
|
|
36
|
+
this.yearInReview2025 = new YearInReview2025();
|
|
35
37
|
this.dataCache = new DataCache();
|
|
36
38
|
this.performanceMonitor = new PerformanceMonitor({
|
|
37
39
|
enabled: true,
|
|
@@ -1244,6 +1246,37 @@ class ClaudeAnalytics {
|
|
|
1244
1246
|
}
|
|
1245
1247
|
});
|
|
1246
1248
|
|
|
1249
|
+
// Year in Review 2025 API endpoint
|
|
1250
|
+
this.app.get('/api/2025', async (req, res) => {
|
|
1251
|
+
try {
|
|
1252
|
+
console.log('📅 Generating 2025 Year in Review...');
|
|
1253
|
+
|
|
1254
|
+
// Load all conversations for analysis
|
|
1255
|
+
const allConversations = await this.conversationAnalyzer.loadConversations(this.stateCalculator);
|
|
1256
|
+
|
|
1257
|
+
// Generate year in review statistics
|
|
1258
|
+
const yearInReview = await this.yearInReview2025.generateYearInReview(allConversations, this.claudeDir);
|
|
1259
|
+
|
|
1260
|
+
console.log(`✅ 2025 Year in Review generated with ${yearInReview.totalConversations} conversations`);
|
|
1261
|
+
|
|
1262
|
+
res.json({
|
|
1263
|
+
...yearInReview,
|
|
1264
|
+
timestamp: new Date().toISOString()
|
|
1265
|
+
});
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
console.error('Error generating 2025 year in review:', error);
|
|
1268
|
+
res.status(500).json({
|
|
1269
|
+
error: 'Failed to generate year in review',
|
|
1270
|
+
message: error.message
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
// Year in Review 2025 page route
|
|
1276
|
+
this.app.get('/2025', (req, res) => {
|
|
1277
|
+
res.sendFile(path.join(__dirname, 'analytics-web', '2025.html'));
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1247
1280
|
// Main dashboard route
|
|
1248
1281
|
this.app.get('/', (req, res) => {
|
|
1249
1282
|
res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
|
|
@@ -1268,15 +1301,18 @@ class ClaudeAnalytics {
|
|
|
1268
1301
|
async openBrowser(openTo = null) {
|
|
1269
1302
|
const baseUrl = this.publicUrl || `http://localhost:${this.port}`;
|
|
1270
1303
|
let fullUrl = baseUrl;
|
|
1271
|
-
|
|
1272
|
-
// Add fragment/hash for specific page
|
|
1304
|
+
|
|
1305
|
+
// Add fragment/hash or path for specific page
|
|
1273
1306
|
if (openTo === 'agents') {
|
|
1274
1307
|
fullUrl = `${baseUrl}/#agents`;
|
|
1275
1308
|
console.log(chalk.blue('🌐 Opening browser to Claude Code Chats...'));
|
|
1309
|
+
} else if (openTo === '2025') {
|
|
1310
|
+
fullUrl = `${baseUrl}/2025`;
|
|
1311
|
+
console.log(chalk.blue('🎉 Opening browser to 2025 Year in Review...'));
|
|
1276
1312
|
} else {
|
|
1277
1313
|
console.log(chalk.blue('🌐 Opening browser to Claude Code Analytics...'));
|
|
1278
1314
|
}
|
|
1279
|
-
|
|
1315
|
+
|
|
1280
1316
|
try {
|
|
1281
1317
|
await open(fullUrl);
|
|
1282
1318
|
} catch (error) {
|
package/src/index.js
CHANGED
|
@@ -146,11 +146,12 @@ async function createClaudeConfig(options = {}) {
|
|
|
146
146
|
const targetDir = options.directory || process.cwd();
|
|
147
147
|
|
|
148
148
|
// Validate --tunnel usage
|
|
149
|
-
if (options.tunnel && !options.analytics && !options.chats && !options.agents && !options.chatsMobile) {
|
|
150
|
-
console.log(chalk.red('❌ Error: --tunnel can only be used with --analytics, --chats, or --chats-mobile'));
|
|
149
|
+
if (options.tunnel && !options.analytics && !options.chats && !options.agents && !options.chatsMobile && !options['2025']) {
|
|
150
|
+
console.log(chalk.red('❌ Error: --tunnel can only be used with --analytics, --chats, --2025, or --chats-mobile'));
|
|
151
151
|
console.log(chalk.yellow('💡 Examples:'));
|
|
152
152
|
console.log(chalk.gray(' cct --analytics --tunnel'));
|
|
153
153
|
console.log(chalk.gray(' cct --chats --tunnel'));
|
|
154
|
+
console.log(chalk.gray(' cct --2025 --tunnel'));
|
|
154
155
|
console.log(chalk.gray(' cct --chats-mobile'));
|
|
155
156
|
return;
|
|
156
157
|
}
|
|
@@ -242,6 +243,14 @@ async function createClaudeConfig(options = {}) {
|
|
|
242
243
|
return;
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
// Handle 2025 Year in Review dashboard
|
|
247
|
+
if (options['2025']) {
|
|
248
|
+
trackingService.trackCommandExecution('2025-year-in-review');
|
|
249
|
+
trackingService.trackAnalyticsDashboard({ page: '2025', source: 'command_line' });
|
|
250
|
+
await runAnalytics({ ...options, openTo: '2025' });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
245
254
|
// Handle plugin dashboard
|
|
246
255
|
if (options.plugins) {
|
|
247
256
|
trackingService.trackCommandExecution('plugins');
|
package/src/plugin-dashboard.js
CHANGED
|
@@ -151,7 +151,10 @@ class PluginDashboard {
|
|
|
151
151
|
|
|
152
152
|
if (source.source === 'github') return 'GitHub';
|
|
153
153
|
if (source.source === 'git') return 'Git';
|
|
154
|
-
|
|
154
|
+
// Support both 'local' and 'directory' for filesystem-based marketplaces
|
|
155
|
+
// 'directory' is used by Claude Code, 'local' for legacy compatibility
|
|
156
|
+
if (source.source === 'local' || source.source === 'directory') return 'Local';
|
|
157
|
+
if (source.source === 'url') return 'URL';
|
|
155
158
|
return 'Unknown';
|
|
156
159
|
}
|
|
157
160
|
|
|
@@ -249,7 +252,12 @@ class PluginDashboard {
|
|
|
249
252
|
mcps: 0
|
|
250
253
|
};
|
|
251
254
|
|
|
252
|
-
|
|
255
|
+
// Handle both string paths and object-based sources
|
|
256
|
+
// Marketplace aggregators use object format {source: "url", url: "..."} to reference external repos
|
|
257
|
+
// Individual plugins use string paths like "./" or "./plugins/name"
|
|
258
|
+
const pluginSourcePath = typeof pluginDef.source === 'string'
|
|
259
|
+
? path.join(marketplacePath, pluginDef.source)
|
|
260
|
+
: marketplacePath;
|
|
253
261
|
|
|
254
262
|
// Check if plugin has inline component definitions (claude-code-templates style)
|
|
255
263
|
if (pluginDef.agents || pluginDef.commands || pluginDef.mcpServers) {
|
|
@@ -261,7 +269,7 @@ class PluginDashboard {
|
|
|
261
269
|
};
|
|
262
270
|
}
|
|
263
271
|
// Otherwise, try to count from source directory (claude-code-plugins style)
|
|
264
|
-
else if (pluginDef.source) {
|
|
272
|
+
else if (typeof pluginDef.source === 'string') {
|
|
265
273
|
if (await fs.pathExists(pluginSourcePath)) {
|
|
266
274
|
components = await this.countPluginComponents(pluginSourcePath);
|
|
267
275
|
}
|
|
@@ -362,11 +362,11 @@ function updateStats() {
|
|
|
362
362
|
document.getElementById('sidebarTotalSkills').textContent = total;
|
|
363
363
|
document.getElementById('sidebarPersonalSkills').textContent = personal;
|
|
364
364
|
|
|
365
|
-
// Filter counts
|
|
366
|
-
document.getElementById('countAll').textContent =
|
|
367
|
-
document.getElementById('countPersonal').textContent =
|
|
368
|
-
document.getElementById('countProject').textContent =
|
|
369
|
-
document.getElementById('countPlugin').textContent =
|
|
365
|
+
// Filter counts - always show totals, not filtered counts
|
|
366
|
+
document.getElementById('countAll').textContent = total;
|
|
367
|
+
document.getElementById('countPersonal').textContent = personal;
|
|
368
|
+
document.getElementById('countProject').textContent = project;
|
|
369
|
+
document.getElementById('countPlugin').textContent = plugin;
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
// Update Empty State
|
package/src/skill-dashboard.js
CHANGED
|
@@ -26,14 +26,24 @@ class SkillDashboard {
|
|
|
26
26
|
async loadSkillsData() {
|
|
27
27
|
try {
|
|
28
28
|
// Load personal skills
|
|
29
|
+
console.log(chalk.gray(`📂 Scanning personal skills: ${this.personalSkillsDir}`));
|
|
29
30
|
this.personalSkills = await this.loadSkillsFromDirectory(this.personalSkillsDir, 'Personal');
|
|
31
|
+
console.log(chalk.gray(`✓ Found ${this.personalSkills.length} personal skill(s)`));
|
|
30
32
|
|
|
31
33
|
// Load project skills (if in a project directory)
|
|
32
34
|
const projectSkillsDir = path.join(process.cwd(), '.claude', 'skills');
|
|
35
|
+
console.log(chalk.gray(`📂 Scanning project skills: ${projectSkillsDir}`));
|
|
33
36
|
this.projectSkills = await this.loadSkillsFromDirectory(projectSkillsDir, 'Project');
|
|
37
|
+
console.log(chalk.gray(`✓ Found ${this.projectSkills.length} project skill(s)`));
|
|
38
|
+
|
|
39
|
+
// Load plugin skills from marketplaces
|
|
40
|
+
console.log(chalk.gray(`📂 Scanning plugin skills from marketplaces`));
|
|
41
|
+
this.pluginSkills = await this.loadPluginSkills();
|
|
42
|
+
console.log(chalk.gray(`✓ Found ${this.pluginSkills.length} plugin skill(s)`));
|
|
34
43
|
|
|
35
44
|
// Combine all skills
|
|
36
|
-
this.skills = [...this.personalSkills, ...this.projectSkills];
|
|
45
|
+
this.skills = [...this.personalSkills, ...this.projectSkills, ...this.pluginSkills];
|
|
46
|
+
console.log(chalk.green(`✅ Total skills loaded: ${this.skills.length}`));
|
|
37
47
|
|
|
38
48
|
} catch (error) {
|
|
39
49
|
console.error(chalk.red('Error loading skills data:'), error.message);
|
|
@@ -46,38 +56,114 @@ class SkillDashboard {
|
|
|
46
56
|
|
|
47
57
|
try {
|
|
48
58
|
if (!(await fs.pathExists(skillsDir))) {
|
|
49
|
-
|
|
50
|
-
console.log(chalk.yellow(`Skills directory not found: ${skillsDir}`));
|
|
51
|
-
}
|
|
59
|
+
console.log(chalk.gray(` ℹ Directory does not exist: ${skillsDir}`));
|
|
52
60
|
return skills;
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const skillDirs = await fs.readdir(skillsDir);
|
|
64
|
+
console.log(chalk.gray(` 📁 Found ${skillDirs.length} item(s) in ${skillsDir}`));
|
|
56
65
|
|
|
57
66
|
for (const skillDir of skillDirs) {
|
|
67
|
+
// Skip hidden files and directories
|
|
68
|
+
if (skillDir.startsWith('.')) continue;
|
|
69
|
+
|
|
58
70
|
const skillPath = path.join(skillsDir, skillDir);
|
|
59
71
|
|
|
60
72
|
try {
|
|
61
73
|
const stat = await fs.stat(skillPath);
|
|
62
|
-
if (!stat.isDirectory())
|
|
74
|
+
if (!stat.isDirectory()) {
|
|
75
|
+
console.log(chalk.gray(` ⊘ Skipping non-directory: ${skillDir}`));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
63
78
|
|
|
64
79
|
// Look for SKILL.md
|
|
65
80
|
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
66
81
|
|
|
67
82
|
if (await fs.pathExists(skillMdPath)) {
|
|
83
|
+
console.log(chalk.gray(` ✓ Found SKILL.md in ${skillDir}`));
|
|
68
84
|
const skillData = await this.parseSkill(skillMdPath, skillPath, skillDir, source);
|
|
69
85
|
if (skillData) {
|
|
70
86
|
skills.push(skillData);
|
|
87
|
+
console.log(chalk.green(` ✅ Loaded skill: ${skillData.name}`));
|
|
71
88
|
}
|
|
89
|
+
} else {
|
|
90
|
+
console.log(chalk.gray(` ⊘ No SKILL.md in ${skillDir}`));
|
|
72
91
|
}
|
|
73
92
|
} catch (error) {
|
|
74
|
-
console.warn(chalk.yellow(`
|
|
93
|
+
console.warn(chalk.yellow(` ⚠ Error loading skill ${skillDir}:`), error.message);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return skills;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.warn(chalk.yellow(`Warning: Error loading skills from ${skillsDir}:`), error.message);
|
|
100
|
+
return skills;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async loadPluginSkills() {
|
|
105
|
+
const skills = [];
|
|
106
|
+
const pluginsDir = path.join(this.claudeDir, 'plugins', 'marketplaces');
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
if (!(await fs.pathExists(pluginsDir))) {
|
|
110
|
+
console.log(chalk.gray(` ℹ Plugins directory does not exist: ${pluginsDir}`));
|
|
111
|
+
return skills;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const marketplaces = await fs.readdir(pluginsDir);
|
|
115
|
+
console.log(chalk.gray(` 📁 Found ${marketplaces.length} marketplace(s)`));
|
|
116
|
+
|
|
117
|
+
for (const marketplace of marketplaces) {
|
|
118
|
+
if (marketplace.startsWith('.')) continue;
|
|
119
|
+
|
|
120
|
+
const marketplacePath = path.join(pluginsDir, marketplace, 'plugins');
|
|
121
|
+
|
|
122
|
+
if (!(await fs.pathExists(marketplacePath))) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const plugins = await fs.readdir(marketplacePath);
|
|
127
|
+
console.log(chalk.gray(` 📦 Scanning marketplace: ${marketplace} (${plugins.length} plugin(s))`));
|
|
128
|
+
|
|
129
|
+
for (const plugin of plugins) {
|
|
130
|
+
if (plugin.startsWith('.')) continue;
|
|
131
|
+
|
|
132
|
+
const skillsPath = path.join(marketplacePath, plugin, 'skills');
|
|
133
|
+
|
|
134
|
+
if (!(await fs.pathExists(skillsPath))) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const skillDirs = await fs.readdir(skillsPath);
|
|
139
|
+
|
|
140
|
+
for (const skillDir of skillDirs) {
|
|
141
|
+
if (skillDir.startsWith('.')) continue;
|
|
142
|
+
|
|
143
|
+
const skillPath = path.join(skillsPath, skillDir);
|
|
144
|
+
const stat = await fs.stat(skillPath);
|
|
145
|
+
|
|
146
|
+
if (!stat.isDirectory()) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const skillMdPath = path.join(skillPath, 'SKILL.md');
|
|
151
|
+
|
|
152
|
+
if (await fs.pathExists(skillMdPath)) {
|
|
153
|
+
console.log(chalk.gray(` ✓ Found plugin skill: ${skillDir} from ${marketplace}`));
|
|
154
|
+
const skillData = await this.parseSkill(skillMdPath, skillPath, skillDir, 'Plugin');
|
|
155
|
+
if (skillData) {
|
|
156
|
+
skills.push(skillData);
|
|
157
|
+
console.log(chalk.green(` ✅ Loaded plugin skill: ${skillData.name}`));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
75
161
|
}
|
|
76
162
|
}
|
|
77
163
|
|
|
78
164
|
return skills;
|
|
79
165
|
} catch (error) {
|
|
80
|
-
console.warn(chalk.yellow(`Warning: Error loading skills
|
|
166
|
+
console.warn(chalk.yellow(`Warning: Error loading plugin skills:`), error.message);
|
|
81
167
|
return skills;
|
|
82
168
|
}
|
|
83
169
|
}
|
package/src/validation/README.md
CHANGED
|
@@ -443,7 +443,7 @@ validatorScore = max(0, 100 - (errors * 25) - (warnings * 5))
|
|
|
443
443
|
|
|
444
444
|
## Integration with Component Generation
|
|
445
445
|
|
|
446
|
-
The security validation system integrates with `generate_components_json.py`:
|
|
446
|
+
The security validation system integrates with `scripts/generate_components_json.py`:
|
|
447
447
|
|
|
448
448
|
1. **Automatic Validation** - Runs before generating components.json
|
|
449
449
|
2. **Metadata Inclusion** - Security scores, hashes, validation status
|