claude-code-templates 1.5.3 → 1.5.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/analytics.js +346 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "CLI tool to setup Claude Code configurations with framework-specific commands, automation hooks and MCP Servers for your projects",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/analytics.js CHANGED
@@ -364,6 +364,39 @@ class ClaudeAnalytics {
364
364
  });
365
365
  });
366
366
 
367
+ // Session detail endpoint
368
+ this.app.get('/api/session/:sessionId', async (req, res) => {
369
+ const sessionId = req.params.sessionId;
370
+
371
+ try {
372
+ const session = this.data.conversations.find(conv => conv.id === sessionId);
373
+ if (!session) {
374
+ return res.status(404).json({ error: 'Session not found' });
375
+ }
376
+
377
+ // Read the actual conversation file
378
+ const content = await fs.readFile(session.filePath, 'utf8');
379
+ const lines = content.trim().split('\n').filter(line => line.trim());
380
+ const messages = lines.map(line => {
381
+ try {
382
+ return JSON.parse(line);
383
+ } catch {
384
+ return null;
385
+ }
386
+ }).filter(Boolean);
387
+
388
+ res.json({
389
+ session: session,
390
+ messages: messages,
391
+ timestamp: new Date().toISOString()
392
+ });
393
+
394
+ } catch (error) {
395
+ console.error(chalk.red('Error loading session details:'), error.message);
396
+ res.status(500).json({ error: 'Failed to load session details' });
397
+ }
398
+ });
399
+
367
400
  // Main dashboard route
368
401
  this.app.get('/', (req, res) => {
369
402
  res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
@@ -643,6 +676,140 @@ async function createWebDashboard() {
643
676
  font-style: italic;
644
677
  }
645
678
 
679
+ .session-detail {
680
+ display: none;
681
+ margin-top: 20px;
682
+ }
683
+
684
+ .session-detail.active {
685
+ display: block;
686
+ }
687
+
688
+ .detail-header {
689
+ display: flex;
690
+ justify-content: space-between;
691
+ align-items: center;
692
+ padding: 16px 0;
693
+ border-bottom: 1px solid #30363d;
694
+ margin-bottom: 20px;
695
+ }
696
+
697
+ .detail-title {
698
+ color: #58a6ff;
699
+ font-size: 1.1rem;
700
+ }
701
+
702
+ .detail-actions {
703
+ display: flex;
704
+ gap: 12px;
705
+ }
706
+
707
+ .btn {
708
+ background: none;
709
+ border: 1px solid #30363d;
710
+ color: #7d8590;
711
+ padding: 6px 12px;
712
+ border-radius: 4px;
713
+ cursor: pointer;
714
+ font-family: inherit;
715
+ font-size: 0.875rem;
716
+ transition: all 0.2s ease;
717
+ }
718
+
719
+ .btn:hover {
720
+ border-color: #58a6ff;
721
+ color: #58a6ff;
722
+ }
723
+
724
+ .btn-primary {
725
+ background: #58a6ff;
726
+ border-color: #58a6ff;
727
+ color: #0d1117;
728
+ }
729
+
730
+ .btn-primary:hover {
731
+ background: #79c0ff;
732
+ border-color: #79c0ff;
733
+ }
734
+
735
+ .session-info {
736
+ display: grid;
737
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
738
+ gap: 20px;
739
+ margin-bottom: 30px;
740
+ }
741
+
742
+ .info-item {
743
+ display: flex;
744
+ flex-direction: column;
745
+ gap: 4px;
746
+ }
747
+
748
+ .info-label {
749
+ color: #7d8590;
750
+ font-size: 0.75rem;
751
+ text-transform: uppercase;
752
+ }
753
+
754
+ .info-value {
755
+ color: #c9d1d9;
756
+ font-size: 0.875rem;
757
+ }
758
+
759
+ .conversation-history {
760
+ border: 1px solid #30363d;
761
+ border-radius: 6px;
762
+ max-height: 600px;
763
+ overflow-y: auto;
764
+ }
765
+
766
+ .message {
767
+ padding: 16px;
768
+ border-bottom: 1px solid #21262d;
769
+ }
770
+
771
+ .message:last-child {
772
+ border-bottom: none;
773
+ }
774
+
775
+ .message-header {
776
+ display: flex;
777
+ justify-content: space-between;
778
+ align-items: center;
779
+ margin-bottom: 8px;
780
+ }
781
+
782
+ .message-role {
783
+ color: #58a6ff;
784
+ font-size: 0.875rem;
785
+ font-weight: bold;
786
+ }
787
+
788
+ .message-role.user {
789
+ color: #3fb950;
790
+ }
791
+
792
+ .message-role.assistant {
793
+ color: #58a6ff;
794
+ }
795
+
796
+ .message-time {
797
+ color: #7d8590;
798
+ font-size: 0.75rem;
799
+ }
800
+
801
+ .message-content {
802
+ color: #c9d1d9;
803
+ font-size: 0.875rem;
804
+ line-height: 1.5;
805
+ white-space: pre-wrap;
806
+ word-wrap: break-word;
807
+ }
808
+
809
+ .back-btn {
810
+ margin-bottom: 20px;
811
+ }
812
+
646
813
  @media (max-width: 768px) {
647
814
  .stats-bar {
648
815
  gap: 20px;
@@ -733,12 +900,36 @@ async function createWebDashboard() {
733
900
  <div id="noSessions" class="no-sessions" style="display: none;">
734
901
  no sessions found for current filter
735
902
  </div>
903
+
904
+ <div id="sessionDetail" class="session-detail">
905
+ <button class="btn back-btn" onclick="showSessionsList()">← back to sessions</button>
906
+
907
+ <div class="detail-header">
908
+ <div class="detail-title" id="detailTitle">session details</div>
909
+ <div class="detail-actions">
910
+ <button class="btn" onclick="exportSessionCSV()">export csv</button>
911
+ <button class="btn btn-primary" onclick="refreshSessionDetail()">refresh</button>
912
+ </div>
913
+ </div>
914
+
915
+ <div class="session-info" id="sessionInfo">
916
+ <!-- Session info will be loaded here -->
917
+ </div>
918
+
919
+ <div>
920
+ <h3 style="color: #7d8590; margin-bottom: 16px; font-size: 0.875rem; text-transform: uppercase;">conversation history</h3>
921
+ <div class="conversation-history" id="conversationHistory">
922
+ <!-- Conversation history will be loaded here -->
923
+ </div>
924
+ </div>
925
+ </div>
736
926
  </div>
737
927
  </div>
738
928
 
739
929
  <script>
740
930
  let allConversations = [];
741
931
  let currentFilter = 'active';
932
+ let currentSession = null;
742
933
 
743
934
  async function loadData() {
744
935
  try {
@@ -791,7 +982,7 @@ async function createWebDashboard() {
791
982
  noSessionsDiv.style.display = 'none';
792
983
 
793
984
  tableBody.innerHTML = filteredConversations.map(conv => \`
794
- <tr>
985
+ <tr onclick="showSessionDetail('\${conv.id}')" style="cursor: pointer;">
795
986
  <td class="session-id">\${conv.id.substring(0, 8)}...</td>
796
987
  <td class="session-project">\${conv.project}</td>
797
988
  <td class="session-messages">\${conv.messageCount}</td>
@@ -836,6 +1027,160 @@ async function createWebDashboard() {
836
1027
  });
837
1028
  });
838
1029
 
1030
+ // Session detail functions
1031
+ async function showSessionDetail(sessionId) {
1032
+ currentSession = allConversations.find(conv => conv.id === sessionId);
1033
+ if (!currentSession) return;
1034
+
1035
+ // Hide sessions list and show detail
1036
+ document.querySelector('.filter-bar').style.display = 'none';
1037
+ document.querySelector('.sessions-table').style.display = 'none';
1038
+ document.getElementById('noSessions').style.display = 'none';
1039
+ document.getElementById('sessionDetail').classList.add('active');
1040
+
1041
+ // Update title
1042
+ document.getElementById('detailTitle').textContent = \`session: \${sessionId.substring(0, 8)}...\`;
1043
+
1044
+ // Load session info
1045
+ updateSessionInfo(currentSession);
1046
+
1047
+ // Load conversation history
1048
+ await loadConversationHistory(currentSession);
1049
+ }
1050
+
1051
+ function showSessionsList() {
1052
+ document.getElementById('sessionDetail').classList.remove('active');
1053
+ document.querySelector('.filter-bar').style.display = 'flex';
1054
+ document.querySelector('.sessions-table').style.display = 'table';
1055
+ updateSessionsTable();
1056
+ currentSession = null;
1057
+ }
1058
+
1059
+ function updateSessionInfo(session) {
1060
+ const container = document.getElementById('sessionInfo');
1061
+
1062
+ container.innerHTML = \`
1063
+ <div class="info-item">
1064
+ <div class="info-label">session id</div>
1065
+ <div class="info-value">\${session.id}</div>
1066
+ </div>
1067
+ <div class="info-item">
1068
+ <div class="info-label">project</div>
1069
+ <div class="info-value">\${session.project}</div>
1070
+ </div>
1071
+ <div class="info-item">
1072
+ <div class="info-label">messages</div>
1073
+ <div class="info-value">\${session.messageCount}</div>
1074
+ </div>
1075
+ <div class="info-item">
1076
+ <div class="info-label">tokens (estimated)</div>
1077
+ <div class="info-value">\${session.tokens.toLocaleString()}</div>
1078
+ </div>
1079
+ <div class="info-item">
1080
+ <div class="info-label">file size</div>
1081
+ <div class="info-value">\${formatBytes(session.fileSize)}</div>
1082
+ </div>
1083
+ <div class="info-item">
1084
+ <div class="info-label">created</div>
1085
+ <div class="info-value">\${new Date(session.created).toLocaleString()}</div>
1086
+ </div>
1087
+ <div class="info-item">
1088
+ <div class="info-label">last modified</div>
1089
+ <div class="info-value">\${new Date(session.lastModified).toLocaleString()}</div>
1090
+ </div>
1091
+ <div class="info-item">
1092
+ <div class="info-label">status</div>
1093
+ <div class="info-value status-\${session.status}">\${session.status}</div>
1094
+ </div>
1095
+ \`;
1096
+ }
1097
+
1098
+ async function loadConversationHistory(session) {
1099
+ try {
1100
+ const response = await fetch(\`/api/session/\${session.id}\`);
1101
+ const sessionData = await response.json();
1102
+
1103
+ const container = document.getElementById('conversationHistory');
1104
+
1105
+ if (!sessionData.messages || sessionData.messages.length === 0) {
1106
+ container.innerHTML = '<div style="padding: 20px; text-align: center; color: #7d8590;">no messages found</div>';
1107
+ return;
1108
+ }
1109
+
1110
+ container.innerHTML = sessionData.messages.map((message, index) => \`
1111
+ <div class="message">
1112
+ <div class="message-header">
1113
+ <div class="message-role \${message.role}">\${message.role}</div>
1114
+ <div class="message-time">message #\${index + 1}</div>
1115
+ </div>
1116
+ <div class="message-content">\${truncateContent(message.content || 'no content')}</div>
1117
+ </div>
1118
+ \`).join('');
1119
+
1120
+ } catch (error) {
1121
+ document.getElementById('conversationHistory').innerHTML =
1122
+ '<div style="padding: 20px; text-align: center; color: #f85149;">error loading conversation history</div>';
1123
+ console.error('Failed to load conversation history:', error);
1124
+ }
1125
+ }
1126
+
1127
+ function truncateContent(content, maxLength = 500) {
1128
+ if (typeof content !== 'string') return 'no content';
1129
+ if (content.length <= maxLength) return content;
1130
+ return content.substring(0, maxLength) + '...';
1131
+ }
1132
+
1133
+ function formatBytes(bytes) {
1134
+ if (bytes === 0) return '0 B';
1135
+ const k = 1024;
1136
+ const sizes = ['B', 'KB', 'MB', 'GB'];
1137
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1138
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1139
+ }
1140
+
1141
+ function exportSessionCSV() {
1142
+ if (!currentSession) return;
1143
+
1144
+ // Create CSV content
1145
+ let csvContent = 'Session ID,Project,Message Count,Tokens,File Size,Created,Last Modified,Status\\n';
1146
+ csvContent += \`"\${currentSession.id}","\${currentSession.project}",\${currentSession.messageCount},\${currentSession.tokens},\${currentSession.fileSize},"\${new Date(currentSession.created).toISOString()}","\${new Date(currentSession.lastModified).toISOString()}","\${currentSession.status}"\\n\\n\`;
1147
+
1148
+ csvContent += 'Message #,Role,Content\\n';
1149
+
1150
+ // Add conversation history if loaded
1151
+ fetch(\`/api/session/\${currentSession.id}\`)
1152
+ .then(response => response.json())
1153
+ .then(sessionData => {
1154
+ if (sessionData.messages) {
1155
+ sessionData.messages.forEach((message, index) => {
1156
+ const content = (message.content || 'no content').replace(/"/g, '""');
1157
+ csvContent += \`\${index + 1},"\${message.role}","\${content}"\\n\`;
1158
+ });
1159
+ }
1160
+
1161
+ // Download CSV
1162
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1163
+ const link = document.createElement('a');
1164
+ const url = URL.createObjectURL(blob);
1165
+ link.setAttribute('href', url);
1166
+ link.setAttribute('download', \`claude-session-\${currentSession.id.substring(0, 8)}.csv\`);
1167
+ link.style.visibility = 'hidden';
1168
+ document.body.appendChild(link);
1169
+ link.click();
1170
+ document.body.removeChild(link);
1171
+ })
1172
+ .catch(error => {
1173
+ console.error('Failed to export CSV:', error);
1174
+ alert('Failed to export CSV. Please try again.');
1175
+ });
1176
+ }
1177
+
1178
+ function refreshSessionDetail() {
1179
+ if (currentSession) {
1180
+ loadConversationHistory(currentSession);
1181
+ }
1182
+ }
1183
+
839
1184
  // Manual refresh function
840
1185
  async function forceRefresh() {
841
1186
  try {