claude-code-templates 1.5.17 → 1.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.5.17",
3
+ "version": "1.6.1",
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": {
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Claude Code Analytics - Terminal</title>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
7
8
  <style>
8
9
  * {
9
10
  margin: 0;
@@ -120,6 +121,111 @@
120
121
  margin-top: 2px;
121
122
  }
122
123
 
124
+ .chart-controls {
125
+ display: flex;
126
+ align-items: center;
127
+ justify-content: space-between;
128
+ gap: 16px;
129
+ margin: 20px 0;
130
+ padding: 12px 0;
131
+ border-top: 1px solid #21262d;
132
+ border-bottom: 1px solid #21262d;
133
+ }
134
+
135
+ .chart-controls-left {
136
+ display: flex;
137
+ align-items: center;
138
+ gap: 16px;
139
+ }
140
+
141
+ .chart-controls-right {
142
+ display: flex;
143
+ align-items: center;
144
+ gap: 12px;
145
+ }
146
+
147
+ .date-control {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 8px;
151
+ }
152
+
153
+ .date-label {
154
+ color: #7d8590;
155
+ font-size: 0.875rem;
156
+ }
157
+
158
+ .date-input {
159
+ background: #21262d;
160
+ border: 1px solid #30363d;
161
+ color: #c9d1d9;
162
+ padding: 6px 12px;
163
+ border-radius: 4px;
164
+ font-family: inherit;
165
+ font-size: 0.875rem;
166
+ cursor: pointer;
167
+ }
168
+
169
+ .date-input:focus {
170
+ outline: none;
171
+ border-color: #d57455;
172
+ }
173
+
174
+ .refresh-btn {
175
+ background: none;
176
+ border: 1px solid #30363d;
177
+ color: #7d8590;
178
+ padding: 6px 12px;
179
+ border-radius: 4px;
180
+ cursor: pointer;
181
+ font-family: inherit;
182
+ font-size: 0.875rem;
183
+ transition: all 0.2s ease;
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 6px;
187
+ }
188
+
189
+ .refresh-btn:hover {
190
+ border-color: #d57455;
191
+ color: #d57455;
192
+ }
193
+
194
+ .refresh-btn.loading {
195
+ opacity: 0.6;
196
+ cursor: not-allowed;
197
+ }
198
+
199
+ .charts-container {
200
+ display: grid;
201
+ grid-template-columns: 2fr 1fr;
202
+ gap: 30px;
203
+ margin: 20px 0 30px 0;
204
+ }
205
+
206
+ .chart-card {
207
+ background: #161b22;
208
+ border: 1px solid #30363d;
209
+ border-radius: 6px;
210
+ padding: 20px;
211
+ position: relative;
212
+ }
213
+
214
+ .chart-title {
215
+ color: #d57455;
216
+ font-size: 0.875rem;
217
+ text-transform: uppercase;
218
+ margin-bottom: 16px;
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ }
223
+
224
+ .chart-canvas {
225
+ width: 100% !important;
226
+ height: 200px !important;
227
+ }
228
+
123
229
  .filter-bar {
124
230
  display: flex;
125
231
  align-items: center;
@@ -190,6 +296,23 @@
190
296
  .session-id {
191
297
  color: #d57455;
192
298
  font-family: monospace;
299
+ display: flex;
300
+ align-items: center;
301
+ gap: 6px;
302
+ }
303
+
304
+ .process-indicator {
305
+ display: inline-block;
306
+ width: 6px;
307
+ height: 6px;
308
+ background: #3fb950;
309
+ border-radius: 50%;
310
+ animation: pulse 2s infinite;
311
+ cursor: help;
312
+ }
313
+
314
+ .process-indicator.orphan {
315
+ background: #f85149;
193
316
  }
194
317
 
195
318
  .session-id-container {
@@ -587,6 +710,35 @@
587
710
  gap: 20px;
588
711
  }
589
712
 
713
+ .chart-controls {
714
+ flex-direction: column;
715
+ gap: 12px;
716
+ align-items: stretch;
717
+ }
718
+
719
+ .chart-controls-left {
720
+ flex-direction: column;
721
+ gap: 12px;
722
+ }
723
+
724
+ .chart-controls-right {
725
+ justify-content: center;
726
+ }
727
+
728
+ .charts-container {
729
+ grid-template-columns: 1fr;
730
+ gap: 20px;
731
+ margin: 20px 0;
732
+ }
733
+
734
+ .chart-card {
735
+ padding: 16px;
736
+ }
737
+
738
+ .chart-canvas {
739
+ height: 180px !important;
740
+ }
741
+
590
742
  .filter-bar {
591
743
  flex-direction: column;
592
744
  align-items: flex-start;
@@ -664,6 +816,40 @@
664
816
  </div>
665
817
  </div>
666
818
 
819
+ <div class="chart-controls">
820
+ <div class="chart-controls-left">
821
+ <div class="date-control">
822
+ <span class="date-label">from:</span>
823
+ <input type="date" id="dateFrom" class="date-input">
824
+ </div>
825
+ <div class="date-control">
826
+ <span class="date-label">to:</span>
827
+ <input type="date" id="dateTo" class="date-input">
828
+ </div>
829
+ </div>
830
+ <div class="chart-controls-right">
831
+ <button class="refresh-btn" onclick="refreshCharts()" id="refreshBtn">
832
+ 🔄 refresh charts
833
+ </button>
834
+ </div>
835
+ </div>
836
+
837
+ <div class="charts-container">
838
+ <div class="chart-card">
839
+ <div class="chart-title">
840
+ 📊 token usage over time
841
+ </div>
842
+ <canvas id="tokenChart" class="chart-canvas"></canvas>
843
+ </div>
844
+
845
+ <div class="chart-card">
846
+ <div class="chart-title">
847
+ 🎯 project activity distribution
848
+ </div>
849
+ <canvas id="projectChart" class="chart-canvas"></canvas>
850
+ </div>
851
+ </div>
852
+
667
853
  <div class="filter-bar">
668
854
  <span class="filter-label">filter conversations:</span>
669
855
  <div class="filter-buttons">
@@ -730,6 +916,9 @@
730
916
  let allConversations = [];
731
917
  let currentFilter = 'active';
732
918
  let currentSession = null;
919
+ let tokenChart = null;
920
+ let projectChart = null;
921
+ let allData = null;
733
922
 
734
923
  async function loadData() {
735
924
  try {
@@ -746,6 +935,15 @@
746
935
 
747
936
  updateStats(data.summary);
748
937
  allConversations = data.conversations;
938
+ allData = data; // Store data globally for access
939
+ window.allData = data; // Keep for backward compatibility
940
+
941
+ // Initialize date inputs on first load
942
+ if (!document.getElementById('dateFrom').value) {
943
+ initializeDateInputs();
944
+ }
945
+
946
+ updateCharts(data);
749
947
  updateSessionsTable();
750
948
 
751
949
  } catch (error) {
@@ -769,6 +967,296 @@
769
967
  }
770
968
  }
771
969
 
970
+ function initializeDateInputs() {
971
+ const today = new Date();
972
+ const sevenDaysAgo = new Date(today);
973
+ sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
974
+
975
+ document.getElementById('dateFrom').value = sevenDaysAgo.toISOString().split('T')[0];
976
+ document.getElementById('dateTo').value = today.toISOString().split('T')[0];
977
+ }
978
+
979
+ function getDateRange() {
980
+ const fromDate = new Date(document.getElementById('dateFrom').value);
981
+ const toDate = new Date(document.getElementById('dateTo').value);
982
+ toDate.setHours(23, 59, 59, 999); // Include the entire end date
983
+
984
+ return { fromDate, toDate };
985
+ }
986
+
987
+ function filterConversationsByDate(conversations) {
988
+ const { fromDate, toDate } = getDateRange();
989
+
990
+ return conversations.filter(conv => {
991
+ const convDate = new Date(conv.lastModified);
992
+ return convDate >= fromDate && convDate <= toDate;
993
+ });
994
+ }
995
+
996
+ function updateCharts(data) {
997
+ // Wait for Chart.js to load before creating charts
998
+ if (typeof Chart === 'undefined') {
999
+ console.log('Chart.js not loaded yet, retrying in 100ms...');
1000
+ setTimeout(() => updateCharts(data), 100);
1001
+ return;
1002
+ }
1003
+
1004
+ // Use ALL conversations but filter chart display by date range
1005
+ // This maintains the original behavior
1006
+
1007
+ // Update Token Usage Over Time Chart
1008
+ updateTokenChart(data.conversations);
1009
+
1010
+ // Update Project Activity Distribution Chart
1011
+ updateProjectChart(data.conversations);
1012
+ }
1013
+
1014
+ async function refreshCharts() {
1015
+ const refreshBtn = document.getElementById('refreshBtn');
1016
+ refreshBtn.classList.add('loading');
1017
+ refreshBtn.textContent = '🔄 refreshing...';
1018
+
1019
+ try {
1020
+ // Use existing data but re-filter and update charts
1021
+ if (allData) {
1022
+ updateCharts(allData);
1023
+ }
1024
+ } catch (error) {
1025
+ console.error('Error refreshing charts:', error);
1026
+ } finally {
1027
+ refreshBtn.classList.remove('loading');
1028
+ refreshBtn.textContent = '🔄 refresh charts';
1029
+ }
1030
+ }
1031
+
1032
+ function updateTokenChart(conversations) {
1033
+ // Check if Chart.js is available
1034
+ if (typeof Chart === 'undefined') {
1035
+ console.warn('Chart.js not available for updateTokenChart');
1036
+ return;
1037
+ }
1038
+
1039
+ // Prepare data for selected date range
1040
+ const { fromDate, toDate } = getDateRange();
1041
+ const dateRange = [];
1042
+
1043
+ const currentDate = new Date(fromDate);
1044
+ while (currentDate <= toDate) {
1045
+ dateRange.push({
1046
+ date: currentDate.toISOString().split('T')[0],
1047
+ label: currentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
1048
+ tokens: 0
1049
+ });
1050
+ currentDate.setDate(currentDate.getDate() + 1);
1051
+ }
1052
+
1053
+ // Aggregate tokens by day
1054
+ conversations.forEach(conv => {
1055
+ const convDate = new Date(conv.lastModified).toISOString().split('T')[0];
1056
+ const dayData = dateRange.find(day => day.date === convDate);
1057
+ if (dayData) {
1058
+ dayData.tokens += conv.tokens;
1059
+ }
1060
+ });
1061
+
1062
+ const ctx = document.getElementById('tokenChart').getContext('2d');
1063
+
1064
+ if (tokenChart) {
1065
+ tokenChart.destroy();
1066
+ }
1067
+
1068
+ tokenChart = new Chart(ctx, {
1069
+ type: 'line',
1070
+ data: {
1071
+ labels: dateRange.map(day => day.label),
1072
+ datasets: [{
1073
+ label: 'Tokens',
1074
+ data: dateRange.map(day => day.tokens),
1075
+ borderColor: '#d57455',
1076
+ backgroundColor: 'rgba(213, 116, 85, 0.1)',
1077
+ borderWidth: 2,
1078
+ pointBackgroundColor: '#d57455',
1079
+ pointBorderColor: '#d57455',
1080
+ pointRadius: 4,
1081
+ pointHoverRadius: 6,
1082
+ fill: true,
1083
+ tension: 0.3
1084
+ }]
1085
+ },
1086
+ options: {
1087
+ responsive: true,
1088
+ maintainAspectRatio: false,
1089
+ plugins: {
1090
+ legend: {
1091
+ display: false
1092
+ },
1093
+ tooltip: {
1094
+ backgroundColor: '#161b22',
1095
+ titleColor: '#d57455',
1096
+ bodyColor: '#c9d1d9',
1097
+ borderColor: '#30363d',
1098
+ borderWidth: 1,
1099
+ titleFont: {
1100
+ family: 'Monaco, Menlo, Ubuntu Mono, monospace',
1101
+ size: 12
1102
+ },
1103
+ bodyFont: {
1104
+ family: 'Monaco, Menlo, Ubuntu Mono, monospace',
1105
+ size: 11
1106
+ },
1107
+ callbacks: {
1108
+ title: function(context) {
1109
+ return context[0].label;
1110
+ },
1111
+ label: function(context) {
1112
+ return `Tokens: ${context.parsed.y.toLocaleString()}`;
1113
+ }
1114
+ }
1115
+ }
1116
+ },
1117
+ interaction: {
1118
+ intersect: false,
1119
+ mode: 'index'
1120
+ },
1121
+ hover: {
1122
+ animationDuration: 200
1123
+ },
1124
+ scales: {
1125
+ x: {
1126
+ grid: {
1127
+ color: '#30363d',
1128
+ borderColor: '#30363d'
1129
+ },
1130
+ ticks: {
1131
+ color: '#7d8590',
1132
+ font: {
1133
+ family: 'Monaco, Menlo, Ubuntu Mono, monospace',
1134
+ size: 11
1135
+ }
1136
+ }
1137
+ },
1138
+ y: {
1139
+ grid: {
1140
+ color: '#30363d',
1141
+ borderColor: '#30363d'
1142
+ },
1143
+ ticks: {
1144
+ color: '#7d8590',
1145
+ font: {
1146
+ family: 'Monaco, Menlo, Ubuntu Mono, monospace',
1147
+ size: 11
1148
+ },
1149
+ callback: function(value) {
1150
+ return value.toLocaleString();
1151
+ }
1152
+ }
1153
+ }
1154
+ }
1155
+ }
1156
+ });
1157
+ }
1158
+
1159
+ function updateProjectChart(conversations) {
1160
+ // Check if Chart.js is available
1161
+ if (typeof Chart === 'undefined') {
1162
+ console.warn('Chart.js not available for updateProjectChart');
1163
+ return;
1164
+ }
1165
+
1166
+ // Aggregate data by project
1167
+ const projectData = {};
1168
+
1169
+ conversations.forEach(conv => {
1170
+ if (!projectData[conv.project]) {
1171
+ projectData[conv.project] = 0;
1172
+ }
1173
+ projectData[conv.project] += conv.tokens;
1174
+ });
1175
+
1176
+ // Get top 5 projects and group others
1177
+ const sortedProjects = Object.entries(projectData)
1178
+ .sort(([,a], [,b]) => b - a)
1179
+ .slice(0, 5);
1180
+
1181
+ const othersTotal = Object.entries(projectData)
1182
+ .slice(5)
1183
+ .reduce((sum, [,tokens]) => sum + tokens, 0);
1184
+
1185
+ if (othersTotal > 0) {
1186
+ sortedProjects.push(['others', othersTotal]);
1187
+ }
1188
+
1189
+ // Terminal-style colors
1190
+ const colors = [
1191
+ '#d57455', // Orange
1192
+ '#3fb950', // Green
1193
+ '#a5d6ff', // Blue
1194
+ '#f97316', // Orange variant
1195
+ '#c9d1d9', // Light gray
1196
+ '#7d8590' // Gray
1197
+ ];
1198
+
1199
+ const ctx = document.getElementById('projectChart').getContext('2d');
1200
+
1201
+ if (projectChart) {
1202
+ projectChart.destroy();
1203
+ }
1204
+
1205
+ projectChart = new Chart(ctx, {
1206
+ type: 'doughnut',
1207
+ data: {
1208
+ labels: sortedProjects.map(([project]) => project),
1209
+ datasets: [{
1210
+ data: sortedProjects.map(([,tokens]) => tokens),
1211
+ backgroundColor: colors.slice(0, sortedProjects.length),
1212
+ borderColor: '#161b22',
1213
+ borderWidth: 2,
1214
+ hoverBorderWidth: 3
1215
+ }]
1216
+ },
1217
+ options: {
1218
+ responsive: true,
1219
+ maintainAspectRatio: false,
1220
+ plugins: {
1221
+ legend: {
1222
+ position: 'bottom',
1223
+ labels: {
1224
+ color: '#7d8590',
1225
+ font: {
1226
+ family: 'Monaco, Menlo, Ubuntu Mono, monospace',
1227
+ size: 10
1228
+ },
1229
+ padding: 15,
1230
+ usePointStyle: true,
1231
+ pointStyle: 'circle'
1232
+ }
1233
+ },
1234
+ tooltip: {
1235
+ backgroundColor: '#161b22',
1236
+ titleColor: '#d57455',
1237
+ bodyColor: '#c9d1d9',
1238
+ borderColor: '#30363d',
1239
+ borderWidth: 1,
1240
+ titleFont: {
1241
+ family: 'Monaco, Menlo, Ubuntu Mono, monospace'
1242
+ },
1243
+ bodyFont: {
1244
+ family: 'Monaco, Menlo, Ubuntu Mono, monospace'
1245
+ },
1246
+ callbacks: {
1247
+ label: function(context) {
1248
+ const total = context.dataset.data.reduce((sum, value) => sum + value, 0);
1249
+ const percentage = ((context.parsed / total) * 100).toFixed(1);
1250
+ return `${context.label}: ${context.parsed.toLocaleString()} tokens (${percentage}%)`;
1251
+ }
1252
+ }
1253
+ }
1254
+ },
1255
+ cutout: '60%'
1256
+ }
1257
+ });
1258
+ }
1259
+
772
1260
  function updateSessionsTable() {
773
1261
  const tableBody = document.getElementById('sessionsTable');
774
1262
  const noSessionsDiv = document.getElementById('noSessions');
@@ -791,7 +1279,10 @@
791
1279
  <tr onclick="showSessionDetail('${conv.id}')" style="cursor: pointer;">
792
1280
  <td>
793
1281
  <div class="session-id-container">
794
- <div class="session-id">${conv.id.substring(0, 8)}...</div>
1282
+ <div class="session-id">
1283
+ ${conv.id.substring(0, 8)}...
1284
+ ${conv.runningProcess ? `<span class="process-indicator" title="Active claude process (PID: ${conv.runningProcess.pid})"></span>` : ''}
1285
+ </div>
795
1286
  <div class="status-squares">
796
1287
  ${generateStatusSquaresHTML(conv.statusSquares || [])}
797
1288
  </div>
@@ -806,6 +1297,33 @@
806
1297
  <td class="status-${conv.status}">${conv.status}</td>
807
1298
  </tr>
808
1299
  `).join('');
1300
+
1301
+ // NEW: Add orphan processes (active claude commands without conversation)
1302
+ if (window.allData && window.allData.orphanProcesses && window.allData.orphanProcesses.length > 0) {
1303
+ const orphanRows = window.allData.orphanProcesses.map(process => `
1304
+ <tr style="background: rgba(248, 81, 73, 0.1); cursor: default;">
1305
+ <td>
1306
+ <div class="session-id-container">
1307
+ <div class="session-id">
1308
+ orphan-${process.pid}
1309
+ <span class="process-indicator orphan" title="Orphan claude process (PID: ${process.pid})"></span>
1310
+ </div>
1311
+ </div>
1312
+ </td>
1313
+ <td class="session-project">${process.workingDir}</td>
1314
+ <td class="session-model">Unknown</td>
1315
+ <td class="session-messages">-</td>
1316
+ <td class="session-tokens">-</td>
1317
+ <td class="session-time">Running</td>
1318
+ <td class="conversation-state">Active process</td>
1319
+ <td class="status-active">orphan</td>
1320
+ </tr>
1321
+ `).join('');
1322
+
1323
+ if (currentFilter === 'active' || currentFilter === 'all') {
1324
+ tableBody.innerHTML += orphanRows;
1325
+ }
1326
+ }
809
1327
  }
810
1328
 
811
1329
  function formatTime(date) {
@@ -1172,11 +1690,26 @@
1172
1690
  }
1173
1691
  }
1174
1692
 
1175
- // Load initial data
1176
- loadData();
1177
-
1178
- // Refresh data every 5 seconds
1179
- setInterval(loadData, 5000);
1693
+ // Wait for DOM and Chart.js to load
1694
+ document.addEventListener('DOMContentLoaded', function() {
1695
+ // Check if Chart.js is loaded
1696
+ function initWhenReady() {
1697
+ if (typeof Chart !== 'undefined') {
1698
+ console.log('Chart.js loaded successfully');
1699
+ loadData();
1700
+ // No automatic refresh - manual only
1701
+ } else {
1702
+ console.log('Waiting for Chart.js to load...');
1703
+ setTimeout(initWhenReady, 100);
1704
+ }
1705
+ }
1706
+
1707
+ initWhenReady();
1708
+
1709
+ // Add event listeners for date inputs
1710
+ document.getElementById('dateFrom').addEventListener('change', refreshCharts);
1711
+ document.getElementById('dateTo').addEventListener('change', refreshCharts);
1712
+ });
1180
1713
 
1181
1714
  // Add keyboard shortcut for refresh (F5 or Ctrl+R)
1182
1715
  document.addEventListener('keydown', function(e) {