claude-code-templates 1.10.0 → 1.11.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/README.md +6 -0
- package/bin/create-claude-config.js +1 -0
- package/package.json +2 -2
- package/src/analytics/core/ConversationAnalyzer.js +94 -20
- package/src/analytics/core/FileWatcher.js +146 -11
- package/src/analytics/data/DataCache.js +124 -19
- package/src/analytics/notifications/NotificationManager.js +37 -0
- package/src/analytics/notifications/WebSocketServer.js +1 -1
- package/src/analytics-web/FRONT_ARCHITECTURE.md +46 -0
- package/src/analytics-web/assets/js/{main.js → main.js.deprecated} +32 -3
- package/src/analytics-web/components/AgentsPage.js +2535 -0
- package/src/analytics-web/components/App.js +430 -0
- package/src/analytics-web/components/{Dashboard.js → Dashboard.js.deprecated} +23 -7
- package/src/analytics-web/components/DashboardPage.js +1527 -0
- package/src/analytics-web/components/Sidebar.js +197 -0
- package/src/analytics-web/components/ToolDisplay.js +539 -0
- package/src/analytics-web/index.html +3275 -1792
- package/src/analytics-web/services/DataService.js +89 -16
- package/src/analytics-web/services/StateService.js +9 -0
- package/src/analytics-web/services/WebSocketService.js +17 -5
- package/src/analytics.js +323 -35
- package/src/console-bridge.js +610 -0
- package/src/file-operations.js +143 -23
- package/src/hook-scanner.js +21 -1
- package/src/index.js +24 -1
- package/src/templates.js +28 -0
- package/src/test-console-bridge.js +67 -0
- package/src/utils.js +46 -0
- package/templates/ruby/.claude/commands/model.md +360 -0
- package/templates/ruby/.claude/commands/test.md +480 -0
- package/templates/ruby/.claude/settings.json +146 -0
- package/templates/ruby/.mcp.json +83 -0
- package/templates/ruby/CLAUDE.md +284 -0
- package/templates/ruby/examples/rails-app/.claude/commands/authentication.md +490 -0
- package/templates/ruby/examples/rails-app/CLAUDE.md +376 -0
package/src/analytics.js
CHANGED
|
@@ -14,6 +14,7 @@ const DataCache = require('./analytics/data/DataCache');
|
|
|
14
14
|
const WebSocketServer = require('./analytics/notifications/WebSocketServer');
|
|
15
15
|
const NotificationManager = require('./analytics/notifications/NotificationManager');
|
|
16
16
|
const PerformanceMonitor = require('./analytics/utils/PerformanceMonitor');
|
|
17
|
+
const ConsoleBridge = require('./console-bridge');
|
|
17
18
|
|
|
18
19
|
class ClaudeAnalytics {
|
|
19
20
|
constructor() {
|
|
@@ -32,6 +33,7 @@ class ClaudeAnalytics {
|
|
|
32
33
|
this.webSocketServer = null;
|
|
33
34
|
this.notificationManager = null;
|
|
34
35
|
this.httpServer = null;
|
|
36
|
+
this.consoleBridge = null;
|
|
35
37
|
this.data = {
|
|
36
38
|
conversations: [],
|
|
37
39
|
summary: {},
|
|
@@ -494,6 +496,36 @@ class ClaudeAnalytics {
|
|
|
494
496
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
495
497
|
}
|
|
496
498
|
|
|
499
|
+
/**
|
|
500
|
+
* Handle conversation file changes and detect new messages
|
|
501
|
+
* @param {string} conversationId - Conversation ID that changed
|
|
502
|
+
* @param {string} filePath - Path to the conversation file
|
|
503
|
+
*/
|
|
504
|
+
async handleConversationChange(conversationId, filePath) {
|
|
505
|
+
try {
|
|
506
|
+
|
|
507
|
+
// Get the latest messages from the file
|
|
508
|
+
const messages = await this.conversationAnalyzer.getParsedConversation(filePath);
|
|
509
|
+
|
|
510
|
+
if (messages && messages.length > 0) {
|
|
511
|
+
// Get the most recent message
|
|
512
|
+
const latestMessage = messages[messages.length - 1];
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
// Send WebSocket notification for new message
|
|
516
|
+
if (this.notificationManager) {
|
|
517
|
+
this.notificationManager.notifyNewMessage(conversationId, latestMessage, {
|
|
518
|
+
totalMessages: messages.length,
|
|
519
|
+
timestamp: new Date().toISOString()
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.error(chalk.red(`Error handling conversation change for ${conversationId}:`), error);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
497
529
|
setupFileWatchers() {
|
|
498
530
|
// Setup file watchers using the FileWatcher module
|
|
499
531
|
this.fileWatcher.setupFileWatchers(
|
|
@@ -513,11 +545,30 @@ class ClaudeAnalytics {
|
|
|
513
545
|
this.data.orphanProcesses = enrichmentResult.orphanProcesses;
|
|
514
546
|
},
|
|
515
547
|
// DataCache for cache invalidation
|
|
516
|
-
this.dataCache
|
|
548
|
+
this.dataCache,
|
|
549
|
+
// Conversation change callback for real-time message updates
|
|
550
|
+
async (conversationId, filePath) => {
|
|
551
|
+
await this.handleConversationChange(conversationId, filePath);
|
|
552
|
+
}
|
|
517
553
|
);
|
|
518
554
|
}
|
|
519
555
|
|
|
520
556
|
setupWebServer() {
|
|
557
|
+
// Add CORS middleware
|
|
558
|
+
this.app.use((req, res, next) => {
|
|
559
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
560
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
561
|
+
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
|
|
562
|
+
|
|
563
|
+
// Handle preflight requests
|
|
564
|
+
if (req.method === 'OPTIONS') {
|
|
565
|
+
res.sendStatus(200);
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
next();
|
|
570
|
+
});
|
|
571
|
+
|
|
521
572
|
// Add performance monitoring middleware
|
|
522
573
|
this.app.use(this.performanceMonitor.createExpressMiddleware());
|
|
523
574
|
|
|
@@ -532,8 +583,7 @@ class ClaudeAnalytics {
|
|
|
532
583
|
|
|
533
584
|
// Memory cleanup: limit conversation history to prevent memory buildup
|
|
534
585
|
if (this.data.conversations && this.data.conversations.length > 150) {
|
|
535
|
-
|
|
536
|
-
this.data.conversations = this.data.conversations
|
|
586
|
+
this.data.conversations = this.data.conversations
|
|
537
587
|
.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified))
|
|
538
588
|
.slice(0, 150);
|
|
539
589
|
}
|
|
@@ -557,6 +607,39 @@ class ClaudeAnalytics {
|
|
|
557
607
|
}
|
|
558
608
|
});
|
|
559
609
|
|
|
610
|
+
// Paginated conversations endpoint
|
|
611
|
+
this.app.get('/api/conversations', async (req, res) => {
|
|
612
|
+
try {
|
|
613
|
+
const page = parseInt(req.query.page) || 0;
|
|
614
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
615
|
+
const offset = page * limit;
|
|
616
|
+
|
|
617
|
+
// Sort conversations by lastModified (most recent first)
|
|
618
|
+
const sortedConversations = [...this.data.conversations]
|
|
619
|
+
.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
|
|
620
|
+
|
|
621
|
+
const paginatedConversations = sortedConversations.slice(offset, offset + limit);
|
|
622
|
+
const totalCount = this.data.conversations.length;
|
|
623
|
+
const hasMore = offset + limit < totalCount;
|
|
624
|
+
|
|
625
|
+
res.json({
|
|
626
|
+
conversations: paginatedConversations,
|
|
627
|
+
pagination: {
|
|
628
|
+
page,
|
|
629
|
+
limit,
|
|
630
|
+
offset,
|
|
631
|
+
totalCount,
|
|
632
|
+
hasMore,
|
|
633
|
+
currentCount: paginatedConversations.length
|
|
634
|
+
},
|
|
635
|
+
timestamp: new Date().toISOString()
|
|
636
|
+
});
|
|
637
|
+
} catch (error) {
|
|
638
|
+
console.error('Error getting paginated conversations:', error);
|
|
639
|
+
res.status(500).json({ error: 'Failed to get conversations' });
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
560
643
|
this.app.get('/api/realtime', async (req, res) => {
|
|
561
644
|
const realtimeWithTimestamp = {
|
|
562
645
|
...this.data.realtimeStats,
|
|
@@ -568,7 +651,6 @@ class ClaudeAnalytics {
|
|
|
568
651
|
|
|
569
652
|
// Force refresh endpoint
|
|
570
653
|
this.app.get('/api/refresh', async (req, res) => {
|
|
571
|
-
console.log(chalk.blue('🔄 Manual refresh requested...'));
|
|
572
654
|
await this.loadInitialData();
|
|
573
655
|
res.json({
|
|
574
656
|
success: true,
|
|
@@ -577,35 +659,115 @@ class ClaudeAnalytics {
|
|
|
577
659
|
});
|
|
578
660
|
});
|
|
579
661
|
|
|
580
|
-
// NEW: Ultra-fast endpoint
|
|
662
|
+
// NEW: Ultra-fast endpoint for ALL conversation states
|
|
581
663
|
this.app.get('/api/conversation-state', async (req, res) => {
|
|
582
664
|
try {
|
|
583
|
-
//
|
|
665
|
+
// Detect running processes for accurate state calculation
|
|
584
666
|
const runningProcesses = await this.processDetector.detectRunningClaudeProcesses();
|
|
585
|
-
const activeStates =
|
|
667
|
+
const activeStates = {};
|
|
586
668
|
|
|
587
|
-
//
|
|
669
|
+
// Calculate states for ALL conversations, not just those with runningProcess
|
|
588
670
|
for (const conversation of this.data.conversations) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
if
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
project: conversation.project,
|
|
596
|
-
state: state,
|
|
597
|
-
timestamp: Date.now()
|
|
598
|
-
});
|
|
671
|
+
try {
|
|
672
|
+
let state;
|
|
673
|
+
|
|
674
|
+
// First try quick calculation if there's a running process
|
|
675
|
+
if (conversation.runningProcess) {
|
|
676
|
+
state = this.stateCalculator.quickStateCalculation(conversation, runningProcesses);
|
|
599
677
|
}
|
|
678
|
+
|
|
679
|
+
// If no quick state found, use full state calculation
|
|
680
|
+
if (!state) {
|
|
681
|
+
// For conversations without running processes, use basic heuristics
|
|
682
|
+
const now = new Date();
|
|
683
|
+
const timeDiff = (now - new Date(conversation.lastModified)) / (1000 * 60); // minutes
|
|
684
|
+
|
|
685
|
+
if (timeDiff < 5) {
|
|
686
|
+
state = 'Recently active';
|
|
687
|
+
} else if (timeDiff < 60) {
|
|
688
|
+
state = 'Idle';
|
|
689
|
+
} else if (timeDiff < 1440) { // 24 hours
|
|
690
|
+
state = 'Inactive';
|
|
691
|
+
} else {
|
|
692
|
+
state = 'Old';
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// Store state with conversation ID as key
|
|
697
|
+
activeStates[conversation.id] = state;
|
|
698
|
+
|
|
699
|
+
} catch (error) {
|
|
700
|
+
activeStates[conversation.id] = 'unknown';
|
|
600
701
|
}
|
|
601
702
|
}
|
|
602
703
|
|
|
603
704
|
res.json({ activeStates, timestamp: Date.now() });
|
|
604
705
|
} catch (error) {
|
|
706
|
+
console.error('Error getting conversation states:', error);
|
|
605
707
|
res.status(500).json({ error: 'Failed to get conversation states' });
|
|
606
708
|
}
|
|
607
709
|
});
|
|
608
710
|
|
|
711
|
+
// Conversation messages endpoint with optional pagination
|
|
712
|
+
this.app.get('/api/conversations/:id/messages', async (req, res) => {
|
|
713
|
+
try {
|
|
714
|
+
const conversationId = req.params.id;
|
|
715
|
+
const page = parseInt(req.query.page);
|
|
716
|
+
const limit = parseInt(req.query.limit);
|
|
717
|
+
|
|
718
|
+
const conversation = this.data.conversations.find(conv => conv.id === conversationId);
|
|
719
|
+
|
|
720
|
+
if (!conversation) {
|
|
721
|
+
return res.status(404).json({ error: 'Conversation not found' });
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Read all messages from the JSONL file
|
|
725
|
+
const allMessages = await this.conversationAnalyzer.getParsedConversation(conversation.filePath);
|
|
726
|
+
|
|
727
|
+
// If pagination parameters are provided, use pagination
|
|
728
|
+
if (!isNaN(page) && !isNaN(limit)) {
|
|
729
|
+
// Sort messages by timestamp (newest first for reverse pagination)
|
|
730
|
+
const sortedMessages = allMessages.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
731
|
+
|
|
732
|
+
// Calculate pagination
|
|
733
|
+
const totalCount = sortedMessages.length;
|
|
734
|
+
const offset = page * limit;
|
|
735
|
+
const hasMore = offset + limit < totalCount;
|
|
736
|
+
|
|
737
|
+
// Get page of messages (reverse order - newest first)
|
|
738
|
+
const paginatedMessages = sortedMessages.slice(offset, offset + limit);
|
|
739
|
+
|
|
740
|
+
// For display, we want messages in chronological order (oldest first)
|
|
741
|
+
const messagesInDisplayOrder = [...paginatedMessages].reverse();
|
|
742
|
+
|
|
743
|
+
res.json({
|
|
744
|
+
conversationId,
|
|
745
|
+
messages: messagesInDisplayOrder,
|
|
746
|
+
pagination: {
|
|
747
|
+
page,
|
|
748
|
+
limit,
|
|
749
|
+
offset,
|
|
750
|
+
totalCount,
|
|
751
|
+
hasMore,
|
|
752
|
+
currentCount: paginatedMessages.length
|
|
753
|
+
},
|
|
754
|
+
timestamp: new Date().toISOString()
|
|
755
|
+
});
|
|
756
|
+
} else {
|
|
757
|
+
// Non-paginated response (backward compatibility)
|
|
758
|
+
res.json({
|
|
759
|
+
conversationId,
|
|
760
|
+
messages: allMessages,
|
|
761
|
+
messageCount: allMessages.length,
|
|
762
|
+
timestamp: new Date().toISOString()
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
} catch (error) {
|
|
766
|
+
console.error('Error loading conversation messages:', error);
|
|
767
|
+
res.status(500).json({ error: 'Failed to load conversation messages' });
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
609
771
|
// Session data endpoint for Max plan usage tracking
|
|
610
772
|
this.app.get('/api/session/data', async (req, res) => {
|
|
611
773
|
try {
|
|
@@ -798,17 +960,10 @@ class ClaudeAnalytics {
|
|
|
798
960
|
}
|
|
799
961
|
});
|
|
800
962
|
|
|
801
|
-
if (hasChanges) {
|
|
802
|
-
console.log(chalk.gray(`⚡ State update: ${activeConvs.length} active conversations`));
|
|
803
|
-
activeConvs.forEach(conv => {
|
|
804
|
-
console.log(chalk.gray(` 📊 ${conv.project}: ${conv.conversationState}`));
|
|
805
|
-
});
|
|
806
|
-
}
|
|
807
963
|
}
|
|
808
964
|
|
|
809
965
|
// Memory cleanup: limit conversation history to prevent memory buildup
|
|
810
966
|
if (this.data.conversations.length > 100) {
|
|
811
|
-
console.log(chalk.yellow(`🧹 Cleaning up conversation history: ${this.data.conversations.length} -> 100`));
|
|
812
967
|
this.data.conversations = this.data.conversations
|
|
813
968
|
.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified))
|
|
814
969
|
.slice(0, 100);
|
|
@@ -915,6 +1070,33 @@ class ClaudeAnalytics {
|
|
|
915
1070
|
}
|
|
916
1071
|
});
|
|
917
1072
|
|
|
1073
|
+
// Cache management endpoint
|
|
1074
|
+
this.app.post('/api/cache/clear', (req, res) => {
|
|
1075
|
+
try {
|
|
1076
|
+
// Clear specific cache types or all
|
|
1077
|
+
const { type } = req.body;
|
|
1078
|
+
|
|
1079
|
+
if (!type || type === 'all') {
|
|
1080
|
+
// Clear all caches
|
|
1081
|
+
this.dataCache.invalidateComputations();
|
|
1082
|
+
this.dataCache.caches.parsedConversations.clear();
|
|
1083
|
+
this.dataCache.caches.fileContent.clear();
|
|
1084
|
+
this.dataCache.caches.fileStats.clear();
|
|
1085
|
+
res.json({ success: true, message: 'All caches cleared' });
|
|
1086
|
+
} else if (type === 'conversations') {
|
|
1087
|
+
// Clear only conversation-related caches
|
|
1088
|
+
this.dataCache.caches.parsedConversations.clear();
|
|
1089
|
+
this.dataCache.caches.fileContent.clear();
|
|
1090
|
+
res.json({ success: true, message: 'Conversation caches cleared' });
|
|
1091
|
+
} else {
|
|
1092
|
+
res.status(400).json({ error: 'Invalid cache type. Use "all" or "conversations"' });
|
|
1093
|
+
}
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
console.error('Error clearing cache:', error);
|
|
1096
|
+
res.status(500).json({ error: 'Failed to clear cache' });
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
|
|
918
1100
|
// Main dashboard route
|
|
919
1101
|
this.app.get('/', (req, res) => {
|
|
920
1102
|
res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
|
|
@@ -936,13 +1118,23 @@ class ClaudeAnalytics {
|
|
|
936
1118
|
});
|
|
937
1119
|
}
|
|
938
1120
|
|
|
939
|
-
async openBrowser() {
|
|
1121
|
+
async openBrowser(openTo = null) {
|
|
1122
|
+
const baseUrl = `http://localhost:${this.port}`;
|
|
1123
|
+
let fullUrl = baseUrl;
|
|
1124
|
+
|
|
1125
|
+
// Add fragment/hash for specific page
|
|
1126
|
+
if (openTo === 'agents') {
|
|
1127
|
+
fullUrl = `${baseUrl}/#agents`;
|
|
1128
|
+
console.log(chalk.blue('🌐 Opening browser to Claude Code Chats...'));
|
|
1129
|
+
} else {
|
|
1130
|
+
console.log(chalk.blue('🌐 Opening browser to Claude Code Analytics...'));
|
|
1131
|
+
}
|
|
1132
|
+
|
|
940
1133
|
try {
|
|
941
|
-
await open(
|
|
942
|
-
console.log(chalk.blue('🌐 Opening browser...'));
|
|
1134
|
+
await open(fullUrl);
|
|
943
1135
|
} catch (error) {
|
|
944
1136
|
console.log(chalk.yellow('Could not open browser automatically. Please visit:'));
|
|
945
|
-
console.log(chalk.cyan(
|
|
1137
|
+
console.log(chalk.cyan(fullUrl));
|
|
946
1138
|
}
|
|
947
1139
|
}
|
|
948
1140
|
|
|
@@ -962,14 +1154,84 @@ class ClaudeAnalytics {
|
|
|
962
1154
|
this.notificationManager = new NotificationManager(this.webSocketServer);
|
|
963
1155
|
await this.notificationManager.initialize();
|
|
964
1156
|
|
|
1157
|
+
// Connect notification manager to file watcher for typing detection
|
|
1158
|
+
this.fileWatcher.setNotificationManager(this.notificationManager);
|
|
1159
|
+
|
|
965
1160
|
// Setup notification subscriptions
|
|
966
1161
|
this.setupNotificationSubscriptions();
|
|
967
1162
|
|
|
968
|
-
|
|
1163
|
+
// Initialize Console Bridge for Claude Code interaction
|
|
1164
|
+
await this.initializeConsoleBridge();
|
|
1165
|
+
|
|
1166
|
+
console.log(chalk.green('✅ WebSocket, notifications, and console bridge initialized'));
|
|
969
1167
|
} catch (error) {
|
|
970
1168
|
console.error(chalk.red('❌ Failed to initialize WebSocket:'), error);
|
|
971
1169
|
}
|
|
972
1170
|
}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* Initialize Console Bridge for Claude Code interaction
|
|
1174
|
+
*/
|
|
1175
|
+
async initializeConsoleBridge() {
|
|
1176
|
+
try {
|
|
1177
|
+
console.log(chalk.blue('🌉 Initializing Console Bridge...'));
|
|
1178
|
+
|
|
1179
|
+
// Create console bridge on a different port (3334)
|
|
1180
|
+
this.consoleBridge = new ConsoleBridge({
|
|
1181
|
+
port: 3334,
|
|
1182
|
+
debug: false // Set to true for detailed debugging
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
// Initialize the bridge
|
|
1186
|
+
const success = await this.consoleBridge.initialize();
|
|
1187
|
+
|
|
1188
|
+
if (success) {
|
|
1189
|
+
console.log(chalk.green('✅ Console Bridge initialized on port 3334'));
|
|
1190
|
+
console.log(chalk.cyan('🔌 Web interface can connect to ws://localhost:3334 for console interactions'));
|
|
1191
|
+
|
|
1192
|
+
// Bridge console interactions to main WebSocket
|
|
1193
|
+
this.setupConsoleBridgeIntegration();
|
|
1194
|
+
} else {
|
|
1195
|
+
console.warn(chalk.yellow('⚠️ Console Bridge failed to initialize - console interactions disabled'));
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
console.warn(chalk.yellow('⚠️ Console Bridge initialization failed:'), error.message);
|
|
1200
|
+
console.log(chalk.gray('Console interactions will not be available, but analytics will continue normally'));
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Setup integration between Console Bridge and main WebSocket
|
|
1206
|
+
*/
|
|
1207
|
+
setupConsoleBridgeIntegration() {
|
|
1208
|
+
if (!this.consoleBridge || !this.webSocketServer) return;
|
|
1209
|
+
|
|
1210
|
+
// Forward console interactions from bridge to main WebSocket
|
|
1211
|
+
this.consoleBridge.on('console_interaction', (interactionData) => {
|
|
1212
|
+
console.log(chalk.blue('📡 Forwarding console interaction to web interface'));
|
|
1213
|
+
|
|
1214
|
+
// Broadcast to main WebSocket clients
|
|
1215
|
+
this.webSocketServer.broadcast({
|
|
1216
|
+
type: 'console_interaction',
|
|
1217
|
+
data: interactionData
|
|
1218
|
+
});
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
// Listen for responses from main WebSocket and forward to bridge
|
|
1222
|
+
this.webSocketServer.on('console_response', (responseData) => {
|
|
1223
|
+
console.log(chalk.blue('📱 Forwarding console response to Claude Code'));
|
|
1224
|
+
|
|
1225
|
+
if (this.consoleBridge) {
|
|
1226
|
+
this.consoleBridge.handleWebMessage({
|
|
1227
|
+
type: 'console_response',
|
|
1228
|
+
data: responseData
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
console.log(chalk.green('🔗 Console Bridge integration established'));
|
|
1234
|
+
}
|
|
973
1235
|
|
|
974
1236
|
/**
|
|
975
1237
|
* Setup notification subscriptions
|
|
@@ -977,7 +1239,6 @@ class ClaudeAnalytics {
|
|
|
977
1239
|
setupNotificationSubscriptions() {
|
|
978
1240
|
// Subscribe to refresh requests from WebSocket clients
|
|
979
1241
|
this.notificationManager.subscribe('refresh_requested', async (notification) => {
|
|
980
|
-
console.log(chalk.blue('🔄 Refresh requested via WebSocket'));
|
|
981
1242
|
await this.loadInitialData();
|
|
982
1243
|
|
|
983
1244
|
// Notify clients of the refreshed data
|
|
@@ -1108,6 +1369,7 @@ class ClaudeAnalytics {
|
|
|
1108
1369
|
}
|
|
1109
1370
|
}
|
|
1110
1371
|
|
|
1372
|
+
|
|
1111
1373
|
stop() {
|
|
1112
1374
|
// Stop file watchers
|
|
1113
1375
|
this.fileWatcher.stop();
|
|
@@ -1123,6 +1385,11 @@ class ClaudeAnalytics {
|
|
|
1123
1385
|
this.notificationManager.shutdown();
|
|
1124
1386
|
}
|
|
1125
1387
|
|
|
1388
|
+
// Shutdown console bridge
|
|
1389
|
+
if (this.consoleBridge) {
|
|
1390
|
+
this.consoleBridge.shutdown();
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1126
1393
|
if (this.httpServer) {
|
|
1127
1394
|
this.httpServer.close();
|
|
1128
1395
|
}
|
|
@@ -1140,7 +1407,14 @@ class ClaudeAnalytics {
|
|
|
1140
1407
|
}
|
|
1141
1408
|
|
|
1142
1409
|
async function runAnalytics(options = {}) {
|
|
1143
|
-
|
|
1410
|
+
// Determine if we're opening to a specific page
|
|
1411
|
+
const openTo = options.openTo;
|
|
1412
|
+
|
|
1413
|
+
if (openTo === 'agents') {
|
|
1414
|
+
console.log(chalk.blue('💬 Starting Claude Code Chats Dashboard...'));
|
|
1415
|
+
} else {
|
|
1416
|
+
console.log(chalk.blue('📊 Starting Claude Code Analytics Dashboard...'));
|
|
1417
|
+
}
|
|
1144
1418
|
|
|
1145
1419
|
const analytics = new ClaudeAnalytics();
|
|
1146
1420
|
|
|
@@ -1151,9 +1425,15 @@ async function runAnalytics(options = {}) {
|
|
|
1151
1425
|
// Web dashboard files are now static in analytics-web directory
|
|
1152
1426
|
|
|
1153
1427
|
await analytics.startServer();
|
|
1154
|
-
await analytics.openBrowser();
|
|
1155
|
-
|
|
1156
|
-
|
|
1428
|
+
await analytics.openBrowser(openTo);
|
|
1429
|
+
|
|
1430
|
+
if (openTo === 'agents') {
|
|
1431
|
+
console.log(chalk.green('✅ Claude Code Chats dashboard is running!'));
|
|
1432
|
+
console.log(chalk.cyan(`📱 Access at: http://localhost:${analytics.port}/#agents`));
|
|
1433
|
+
} else {
|
|
1434
|
+
console.log(chalk.green('✅ Analytics dashboard is running!'));
|
|
1435
|
+
console.log(chalk.cyan(`📱 Access at: http://localhost:${analytics.port}`));
|
|
1436
|
+
}
|
|
1157
1437
|
console.log(chalk.gray('Press Ctrl+C to stop the server'));
|
|
1158
1438
|
|
|
1159
1439
|
// Handle graceful shutdown
|
|
@@ -1173,6 +1453,14 @@ async function runAnalytics(options = {}) {
|
|
|
1173
1453
|
}
|
|
1174
1454
|
|
|
1175
1455
|
|
|
1456
|
+
// If this file is executed directly, run analytics
|
|
1457
|
+
if (require.main === module) {
|
|
1458
|
+
runAnalytics().catch(error => {
|
|
1459
|
+
console.error(chalk.red('❌ Analytics startup failed:'), error);
|
|
1460
|
+
process.exit(1);
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1176
1464
|
module.exports = {
|
|
1177
1465
|
runAnalytics
|
|
1178
1466
|
};
|