claude-code-templates 1.8.0 → 1.8.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/README.md +246 -0
- package/package.json +26 -12
- package/src/analytics/core/ConversationAnalyzer.js +754 -0
- package/src/analytics/core/FileWatcher.js +285 -0
- package/src/analytics/core/ProcessDetector.js +242 -0
- package/src/analytics/core/SessionAnalyzer.js +597 -0
- package/src/analytics/core/StateCalculator.js +190 -0
- package/src/analytics/data/DataCache.js +550 -0
- package/src/analytics/notifications/NotificationManager.js +448 -0
- package/src/analytics/notifications/WebSocketServer.js +526 -0
- package/src/analytics/utils/PerformanceMonitor.js +455 -0
- package/src/analytics-web/assets/js/main.js +312 -0
- package/src/analytics-web/components/Charts.js +114 -0
- package/src/analytics-web/components/ConversationTable.js +437 -0
- package/src/analytics-web/components/Dashboard.js +573 -0
- package/src/analytics-web/components/SessionTimer.js +596 -0
- package/src/analytics-web/index.html +882 -49
- package/src/analytics-web/index.html.original +1939 -0
- package/src/analytics-web/services/DataService.js +357 -0
- package/src/analytics-web/services/StateService.js +276 -0
- package/src/analytics-web/services/WebSocketService.js +523 -0
- package/src/analytics.js +626 -2317
- package/src/analytics.log +0 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationManager - Manages notifications and real-time updates
|
|
3
|
+
* Part of the modular backend architecture for Phase 3
|
|
4
|
+
*/
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
|
|
7
|
+
class NotificationManager {
|
|
8
|
+
constructor(webSocketServer) {
|
|
9
|
+
this.webSocketServer = webSocketServer;
|
|
10
|
+
this.notificationHistory = [];
|
|
11
|
+
this.maxHistorySize = 1000;
|
|
12
|
+
this.subscribers = new Map();
|
|
13
|
+
this.throttleMap = new Map();
|
|
14
|
+
this.defaultThrottleTime = 1000; // 1 second
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the notification manager
|
|
19
|
+
*/
|
|
20
|
+
async initialize() {
|
|
21
|
+
console.log(chalk.blue('📢 Initializing Notification Manager...'));
|
|
22
|
+
|
|
23
|
+
// Setup WebSocket event listeners
|
|
24
|
+
if (this.webSocketServer) {
|
|
25
|
+
this.webSocketServer.on('refresh_requested', (data) => {
|
|
26
|
+
this.handleRefreshRequest(data);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(chalk.green('✅ Notification Manager initialized'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Send conversation state change notification
|
|
35
|
+
* @param {string} conversationId - Conversation ID
|
|
36
|
+
* @param {string} oldState - Previous state
|
|
37
|
+
* @param {string} newState - New state
|
|
38
|
+
* @param {Object} metadata - Additional metadata
|
|
39
|
+
*/
|
|
40
|
+
notifyConversationStateChange(conversationId, oldState, newState, metadata = {}) {
|
|
41
|
+
const notification = {
|
|
42
|
+
type: 'conversation_state_change',
|
|
43
|
+
conversationId,
|
|
44
|
+
oldState,
|
|
45
|
+
newState,
|
|
46
|
+
metadata,
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
id: this.generateNotificationId()
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Throttle rapid state changes for the same conversation
|
|
52
|
+
const throttleKey = `state_${conversationId}`;
|
|
53
|
+
if (this.isThrottled(throttleKey)) {
|
|
54
|
+
console.log(chalk.yellow(`⏱️ Throttling state change for conversation ${conversationId}`));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.addToHistory(notification);
|
|
59
|
+
|
|
60
|
+
// Send via WebSocket
|
|
61
|
+
if (this.webSocketServer) {
|
|
62
|
+
this.webSocketServer.notifyConversationStateChange(conversationId, newState, {
|
|
63
|
+
oldState,
|
|
64
|
+
...metadata
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Send to local subscribers
|
|
69
|
+
this.notifySubscribers('conversation_state_change', notification);
|
|
70
|
+
|
|
71
|
+
console.log(chalk.green(`🔄 State change: ${conversationId} ${oldState} → ${newState}`));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Send data refresh notification
|
|
76
|
+
* @param {Object} data - Refreshed data
|
|
77
|
+
* @param {string} source - Source of the refresh
|
|
78
|
+
*/
|
|
79
|
+
notifyDataRefresh(data, source = 'system') {
|
|
80
|
+
const notification = {
|
|
81
|
+
type: 'data_refresh',
|
|
82
|
+
data,
|
|
83
|
+
source,
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
id: this.generateNotificationId()
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Throttle data refresh notifications
|
|
89
|
+
if (this.isThrottled('data_refresh')) {
|
|
90
|
+
console.log(chalk.yellow('⏱️ Throttling data refresh notification'));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.addToHistory(notification);
|
|
95
|
+
|
|
96
|
+
// Send via WebSocket
|
|
97
|
+
if (this.webSocketServer) {
|
|
98
|
+
this.webSocketServer.notifyDataRefresh(data);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Send to local subscribers
|
|
102
|
+
this.notifySubscribers('data_refresh', notification);
|
|
103
|
+
|
|
104
|
+
console.log(chalk.green(`📊 Data refreshed (source: ${source})`));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Send system status notification
|
|
109
|
+
* @param {Object} status - System status
|
|
110
|
+
* @param {string} level - Notification level (info, warning, error)
|
|
111
|
+
*/
|
|
112
|
+
notifySystemStatus(status, level = 'info') {
|
|
113
|
+
const notification = {
|
|
114
|
+
type: 'system_status',
|
|
115
|
+
status,
|
|
116
|
+
level,
|
|
117
|
+
timestamp: new Date().toISOString(),
|
|
118
|
+
id: this.generateNotificationId()
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
this.addToHistory(notification);
|
|
122
|
+
|
|
123
|
+
// Send via WebSocket
|
|
124
|
+
if (this.webSocketServer) {
|
|
125
|
+
this.webSocketServer.notifySystemStatus({
|
|
126
|
+
...status,
|
|
127
|
+
level
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Send to local subscribers
|
|
132
|
+
this.notifySubscribers('system_status', notification);
|
|
133
|
+
|
|
134
|
+
const emoji = level === 'error' ? '❌' : level === 'warning' ? '⚠️' : 'ℹ️';
|
|
135
|
+
console.log(chalk[level === 'error' ? 'red' : level === 'warning' ? 'yellow' : 'blue'](`${emoji} System status: ${status.message || JSON.stringify(status)}`));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Send file change notification
|
|
140
|
+
* @param {string} filePath - Path of changed file
|
|
141
|
+
* @param {string} changeType - Type of change (created, modified, deleted)
|
|
142
|
+
*/
|
|
143
|
+
notifyFileChange(filePath, changeType) {
|
|
144
|
+
const notification = {
|
|
145
|
+
type: 'file_change',
|
|
146
|
+
filePath,
|
|
147
|
+
changeType,
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
id: this.generateNotificationId()
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Throttle file change notifications for the same file
|
|
153
|
+
const throttleKey = `file_${filePath}`;
|
|
154
|
+
if (this.isThrottled(throttleKey, 2000)) { // 2 second throttle for files
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.addToHistory(notification);
|
|
159
|
+
|
|
160
|
+
// Send via WebSocket
|
|
161
|
+
if (this.webSocketServer) {
|
|
162
|
+
this.webSocketServer.broadcast({
|
|
163
|
+
type: 'file_change',
|
|
164
|
+
data: {
|
|
165
|
+
filePath,
|
|
166
|
+
changeType
|
|
167
|
+
}
|
|
168
|
+
}, 'file_updates');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Send to local subscribers
|
|
172
|
+
this.notifySubscribers('file_change', notification);
|
|
173
|
+
|
|
174
|
+
console.log(chalk.cyan(`📁 File ${changeType}: ${filePath}`));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Send process change notification
|
|
179
|
+
* @param {Array} processes - Current processes
|
|
180
|
+
* @param {Array} changedProcesses - Processes that changed
|
|
181
|
+
*/
|
|
182
|
+
notifyProcessChange(processes, changedProcesses) {
|
|
183
|
+
const notification = {
|
|
184
|
+
type: 'process_change',
|
|
185
|
+
processes,
|
|
186
|
+
changedProcesses,
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
id: this.generateNotificationId()
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Throttle process change notifications
|
|
192
|
+
if (this.isThrottled('process_change', 5000)) { // 5 second throttle for processes
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
this.addToHistory(notification);
|
|
197
|
+
|
|
198
|
+
// Send via WebSocket
|
|
199
|
+
if (this.webSocketServer) {
|
|
200
|
+
this.webSocketServer.broadcast({
|
|
201
|
+
type: 'process_change',
|
|
202
|
+
data: {
|
|
203
|
+
processes,
|
|
204
|
+
changedProcesses
|
|
205
|
+
}
|
|
206
|
+
}, 'process_updates');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Send to local subscribers
|
|
210
|
+
this.notifySubscribers('process_change', notification);
|
|
211
|
+
|
|
212
|
+
if (changedProcesses.length > 0) {
|
|
213
|
+
console.log(chalk.blue(`⚡ Process changes detected: ${changedProcesses.length} processes`));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Handle refresh request from WebSocket client
|
|
219
|
+
* @param {Object} data - Request data
|
|
220
|
+
*/
|
|
221
|
+
handleRefreshRequest(data) {
|
|
222
|
+
console.log(chalk.blue(`🔄 Refresh requested by client: ${data.clientId}`));
|
|
223
|
+
|
|
224
|
+
// Emit refresh event that analytics server can listen to
|
|
225
|
+
this.notifySubscribers('refresh_requested', {
|
|
226
|
+
clientId: data.clientId,
|
|
227
|
+
timestamp: new Date().toISOString()
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Subscribe to notifications
|
|
233
|
+
* @param {string} type - Notification type
|
|
234
|
+
* @param {Function} callback - Callback function
|
|
235
|
+
* @returns {Function} Unsubscribe function
|
|
236
|
+
*/
|
|
237
|
+
subscribe(type, callback) {
|
|
238
|
+
if (!this.subscribers.has(type)) {
|
|
239
|
+
this.subscribers.set(type, new Set());
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.subscribers.get(type).add(callback);
|
|
243
|
+
|
|
244
|
+
// Return unsubscribe function
|
|
245
|
+
return () => {
|
|
246
|
+
const typeSubscribers = this.subscribers.get(type);
|
|
247
|
+
if (typeSubscribers) {
|
|
248
|
+
typeSubscribers.delete(callback);
|
|
249
|
+
if (typeSubscribers.size === 0) {
|
|
250
|
+
this.subscribers.delete(type);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Notify all subscribers of a specific type
|
|
258
|
+
* @param {string} type - Notification type
|
|
259
|
+
* @param {Object} notification - Notification data
|
|
260
|
+
*/
|
|
261
|
+
notifySubscribers(type, notification) {
|
|
262
|
+
const typeSubscribers = this.subscribers.get(type);
|
|
263
|
+
if (!typeSubscribers) return;
|
|
264
|
+
|
|
265
|
+
typeSubscribers.forEach(callback => {
|
|
266
|
+
try {
|
|
267
|
+
callback(notification);
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(chalk.red(`Error in notification subscriber for ${type}:`), error);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Check if a notification type is throttled
|
|
276
|
+
* @param {string} key - Throttle key
|
|
277
|
+
* @param {number} throttleTime - Throttle time in milliseconds
|
|
278
|
+
* @returns {boolean} Is throttled
|
|
279
|
+
*/
|
|
280
|
+
isThrottled(key, throttleTime = this.defaultThrottleTime) {
|
|
281
|
+
const now = Date.now();
|
|
282
|
+
const lastTime = this.throttleMap.get(key);
|
|
283
|
+
|
|
284
|
+
if (lastTime && (now - lastTime) < throttleTime) {
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.throttleMap.set(key, now);
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Add notification to history
|
|
294
|
+
* @param {Object} notification - Notification to add
|
|
295
|
+
*/
|
|
296
|
+
addToHistory(notification) {
|
|
297
|
+
this.notificationHistory.push(notification);
|
|
298
|
+
|
|
299
|
+
// Keep history size manageable
|
|
300
|
+
if (this.notificationHistory.length > this.maxHistorySize) {
|
|
301
|
+
this.notificationHistory.shift();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get notification history
|
|
307
|
+
* @param {string} type - Filter by type (optional)
|
|
308
|
+
* @param {number} limit - Limit number of results
|
|
309
|
+
* @returns {Array} Notification history
|
|
310
|
+
*/
|
|
311
|
+
getHistory(type = null, limit = 100) {
|
|
312
|
+
let history = this.notificationHistory;
|
|
313
|
+
|
|
314
|
+
if (type) {
|
|
315
|
+
history = history.filter(notification => notification.type === type);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return history.slice(-limit);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Clear notification history
|
|
323
|
+
* @param {string} type - Clear specific type only (optional)
|
|
324
|
+
*/
|
|
325
|
+
clearHistory(type = null) {
|
|
326
|
+
if (type) {
|
|
327
|
+
this.notificationHistory = this.notificationHistory.filter(
|
|
328
|
+
notification => notification.type !== type
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
this.notificationHistory = [];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
console.log(chalk.yellow(`🗑️ Cleared notification history${type ? ` for type: ${type}` : ''}`));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Generate unique notification ID
|
|
339
|
+
* @returns {string} Notification ID
|
|
340
|
+
*/
|
|
341
|
+
generateNotificationId() {
|
|
342
|
+
return `notif_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get notification statistics
|
|
347
|
+
* @returns {Object} Notification statistics
|
|
348
|
+
*/
|
|
349
|
+
getStats() {
|
|
350
|
+
const typeCount = {};
|
|
351
|
+
this.notificationHistory.forEach(notification => {
|
|
352
|
+
typeCount[notification.type] = (typeCount[notification.type] || 0) + 1;
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
historySize: this.notificationHistory.length,
|
|
357
|
+
maxHistorySize: this.maxHistorySize,
|
|
358
|
+
subscriberCount: this.subscribers.size,
|
|
359
|
+
typeCount,
|
|
360
|
+
throttleMapSize: this.throttleMap.size,
|
|
361
|
+
webSocketConnected: this.webSocketServer ? this.webSocketServer.isRunning : false,
|
|
362
|
+
webSocketClients: this.webSocketServer ? this.webSocketServer.getStats().clientCount : 0
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Create a batch notification for multiple changes
|
|
368
|
+
* @param {Array} notifications - Array of notifications
|
|
369
|
+
* @param {string} batchType - Type of batch
|
|
370
|
+
*/
|
|
371
|
+
createBatch(notifications, batchType = 'batch') {
|
|
372
|
+
if (notifications.length === 0) return;
|
|
373
|
+
|
|
374
|
+
const batchNotification = {
|
|
375
|
+
type: batchType,
|
|
376
|
+
notifications,
|
|
377
|
+
count: notifications.length,
|
|
378
|
+
timestamp: new Date().toISOString(),
|
|
379
|
+
id: this.generateNotificationId()
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
this.addToHistory(batchNotification);
|
|
383
|
+
|
|
384
|
+
// Send via WebSocket
|
|
385
|
+
if (this.webSocketServer) {
|
|
386
|
+
this.webSocketServer.broadcast({
|
|
387
|
+
type: batchType,
|
|
388
|
+
data: {
|
|
389
|
+
notifications,
|
|
390
|
+
count: notifications.length
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Send to local subscribers
|
|
396
|
+
this.notifySubscribers(batchType, batchNotification);
|
|
397
|
+
|
|
398
|
+
console.log(chalk.green(`📦 Batch notification sent: ${notifications.length} items`));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Cleanup throttle map periodically
|
|
403
|
+
*/
|
|
404
|
+
cleanupThrottleMap() {
|
|
405
|
+
const now = Date.now();
|
|
406
|
+
const maxAge = this.defaultThrottleTime * 10; // 10x throttle time
|
|
407
|
+
|
|
408
|
+
this.throttleMap.forEach((timestamp, key) => {
|
|
409
|
+
if (now - timestamp > maxAge) {
|
|
410
|
+
this.throttleMap.delete(key);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Start periodic cleanup
|
|
417
|
+
*/
|
|
418
|
+
startPeriodicCleanup() {
|
|
419
|
+
this.cleanupInterval = setInterval(() => {
|
|
420
|
+
this.cleanupThrottleMap();
|
|
421
|
+
}, 60000); // Clean up every minute
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Stop periodic cleanup
|
|
426
|
+
*/
|
|
427
|
+
stopPeriodicCleanup() {
|
|
428
|
+
if (this.cleanupInterval) {
|
|
429
|
+
clearInterval(this.cleanupInterval);
|
|
430
|
+
this.cleanupInterval = null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Shutdown the notification manager
|
|
436
|
+
*/
|
|
437
|
+
async shutdown() {
|
|
438
|
+
console.log(chalk.yellow('📢 Shutting down Notification Manager...'));
|
|
439
|
+
|
|
440
|
+
this.stopPeriodicCleanup();
|
|
441
|
+
this.subscribers.clear();
|
|
442
|
+
this.throttleMap.clear();
|
|
443
|
+
|
|
444
|
+
console.log(chalk.green('✅ Notification Manager shut down'));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
module.exports = NotificationManager;
|