claude-code-templates 1.5.6 → 1.5.8

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 +171 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
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
@@ -137,7 +137,8 @@ class ClaudeAnalytics {
137
137
  created: stats.birthtime,
138
138
  tokens: this.estimateTokens(content),
139
139
  project: projectFromPath || this.extractProjectFromConversation(parsedMessages),
140
- status: this.determineConversationStatus(parsedMessages, stats.mtime)
140
+ status: this.determineConversationStatus(parsedMessages, stats.mtime),
141
+ conversationState: this.determineConversationState(parsedMessages, stats.mtime)
141
142
  };
142
143
 
143
144
  conversations.push(conversation);
@@ -249,28 +250,57 @@ class ClaudeAnalytics {
249
250
  const lastMessageTime = new Date(lastMessage.timestamp);
250
251
  const lastMessageMinutesAgo = (now - lastMessageTime) / (1000 * 60);
251
252
 
252
- // Advanced status logic
253
+ // Simplified status logic - typing is now part of active
254
+ if (lastMessage.role === 'user' && lastMessageMinutesAgo < 3) {
255
+ return 'active';
256
+ } else if (lastMessage.role === 'assistant' && lastMessageMinutesAgo < 5) {
257
+ return 'active';
258
+ }
259
+
260
+ // Fallback to file modification time for edge cases
261
+ if (minutesAgo < 5) return 'active';
262
+ if (minutesAgo < 60) return 'recent';
263
+ return 'inactive';
264
+ }
265
+
266
+ determineConversationState(messages, lastModified) {
267
+ const now = new Date();
268
+ const timeDiff = now - lastModified;
269
+ const minutesAgo = timeDiff / (1000 * 60);
270
+
271
+ if (messages.length === 0) {
272
+ return minutesAgo < 5 ? 'Waiting for input...' : 'Idle';
273
+ }
274
+
275
+ // Sort messages by timestamp to get the actual conversation flow
276
+ const sortedMessages = messages.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
277
+ const lastMessage = sortedMessages[sortedMessages.length - 1];
278
+ const lastMessageTime = new Date(lastMessage.timestamp);
279
+ const lastMessageMinutesAgo = (now - lastMessageTime) / (1000 * 60);
280
+
281
+ // Detailed conversation state logic
253
282
  if (lastMessage.role === 'user') {
254
283
  // User sent last message
255
284
  if (lastMessageMinutesAgo < 0.5) {
256
- // Very recent user message, likely typing or waiting for response
257
- return 'typing';
285
+ return 'Claude Code working...';
258
286
  } else if (lastMessageMinutesAgo < 3) {
259
- // Recent user message, waiting for assistant
260
- return 'active';
287
+ return 'Awaiting response...';
288
+ } else {
289
+ return 'User typing...';
261
290
  }
262
291
  } else if (lastMessage.role === 'assistant') {
263
292
  // Assistant sent last message
264
293
  if (lastMessageMinutesAgo < 2) {
265
- // Recent assistant response, conversation is active
266
- return 'active';
294
+ return 'Awaiting user input...';
295
+ } else if (lastMessageMinutesAgo < 5) {
296
+ return 'User may be typing...';
267
297
  }
268
298
  }
269
299
 
270
- // Fallback to file modification time for edge cases
271
- if (minutesAgo < 5) return 'active';
272
- if (minutesAgo < 60) return 'recent';
273
- return 'inactive';
300
+ // Fallback states
301
+ if (minutesAgo < 5) return 'Recently active';
302
+ if (minutesAgo < 60) return 'Idle';
303
+ return 'Inactive';
274
304
  }
275
305
 
276
306
  determineProjectStatus(lastActivity) {
@@ -733,12 +763,25 @@ async function createWebDashboard() {
733
763
  color: #7d8590;
734
764
  }
735
765
 
736
- .status-typing {
766
+ .conversation-state {
737
767
  color: #d57455;
738
- font-weight: bold;
768
+ font-style: italic;
769
+ font-size: 0.8rem;
770
+ }
771
+
772
+ .conversation-state.working {
773
+ animation: working-pulse 1.5s infinite;
774
+ }
775
+
776
+ .conversation-state.typing {
739
777
  animation: typing-pulse 1.5s infinite;
740
778
  }
741
779
 
780
+ @keyframes working-pulse {
781
+ 0%, 100% { opacity: 1; }
782
+ 50% { opacity: 0.7; }
783
+ }
784
+
742
785
  @keyframes typing-pulse {
743
786
  0%, 100% { opacity: 1; }
744
787
  50% { opacity: 0.6; }
@@ -787,6 +830,28 @@ async function createWebDashboard() {
787
830
  .detail-actions {
788
831
  display: flex;
789
832
  gap: 12px;
833
+ align-items: center;
834
+ }
835
+
836
+ .export-format-select {
837
+ background: #21262d;
838
+ border: 1px solid #30363d;
839
+ color: #c9d1d9;
840
+ padding: 6px 12px;
841
+ border-radius: 4px;
842
+ font-family: inherit;
843
+ font-size: 0.875rem;
844
+ cursor: pointer;
845
+ }
846
+
847
+ .export-format-select:focus {
848
+ outline: none;
849
+ border-color: #d57455;
850
+ }
851
+
852
+ .export-format-select option {
853
+ background: #21262d;
854
+ color: #c9d1d9;
790
855
  }
791
856
 
792
857
  .btn {
@@ -960,7 +1025,6 @@ async function createWebDashboard() {
960
1025
  <span class="filter-label">filter sessions:</span>
961
1026
  <div class="filter-buttons">
962
1027
  <button class="filter-btn active" data-filter="active">active</button>
963
- <button class="filter-btn" data-filter="typing">typing</button>
964
1028
  <button class="filter-btn" data-filter="recent">recent</button>
965
1029
  <button class="filter-btn" data-filter="inactive">inactive</button>
966
1030
  <button class="filter-btn" data-filter="all">all</button>
@@ -975,6 +1039,7 @@ async function createWebDashboard() {
975
1039
  <th>messages</th>
976
1040
  <th>tokens</th>
977
1041
  <th>last activity</th>
1042
+ <th>conversation state</th>
978
1043
  <th>status</th>
979
1044
  </tr>
980
1045
  </thead>
@@ -993,7 +1058,11 @@ async function createWebDashboard() {
993
1058
  <div class="detail-header">
994
1059
  <div class="detail-title" id="detailTitle">session details</div>
995
1060
  <div class="detail-actions">
996
- <button class="btn" onclick="exportSessionCSV()">export csv</button>
1061
+ <select id="exportFormat" class="export-format-select">
1062
+ <option value="csv">CSV</option>
1063
+ <option value="json">JSON</option>
1064
+ </select>
1065
+ <button class="btn" onclick="exportSession()">export</button>
997
1066
  <button class="btn btn-primary" onclick="refreshSessionDetail()">refresh</button>
998
1067
  </div>
999
1068
  </div>
@@ -1074,6 +1143,7 @@ async function createWebDashboard() {
1074
1143
  <td class="session-messages">\${conv.messageCount}</td>
1075
1144
  <td class="session-tokens">\${conv.tokens.toLocaleString()}</td>
1076
1145
  <td class="session-time">\${formatTime(conv.lastModified)}</td>
1146
+ <td class="conversation-state \${getStateClass(conv.conversationState)}">\${conv.conversationState}</td>
1077
1147
  <td class="status-\${conv.status}">\${conv.status}</td>
1078
1148
  </tr>
1079
1149
  \`).join('');
@@ -1102,6 +1172,16 @@ async function createWebDashboard() {
1102
1172
  });
1103
1173
  }
1104
1174
 
1175
+ function getStateClass(conversationState) {
1176
+ if (conversationState.includes('working') || conversationState.includes('Working')) {
1177
+ return 'working';
1178
+ }
1179
+ if (conversationState.includes('typing') || conversationState.includes('Typing')) {
1180
+ return 'typing';
1181
+ }
1182
+ return '';
1183
+ }
1184
+
1105
1185
  // Filter button handlers
1106
1186
  document.addEventListener('DOMContentLoaded', function() {
1107
1187
  const filterButtons = document.querySelectorAll('.filter-btn');
@@ -1184,6 +1264,10 @@ async function createWebDashboard() {
1184
1264
  <div class="info-label">last modified</div>
1185
1265
  <div class="info-value">\${new Date(session.lastModified).toLocaleString()}</div>
1186
1266
  </div>
1267
+ <div class="info-item">
1268
+ <div class="info-label">conversation state</div>
1269
+ <div class="info-value conversation-state \${getStateClass(session.conversationState)}">\${session.conversationState}</div>
1270
+ </div>
1187
1271
  <div class="info-item">
1188
1272
  <div class="info-label">status</div>
1189
1273
  <div class="info-value status-\${session.status}">\${session.status}</div>
@@ -1237,43 +1321,89 @@ async function createWebDashboard() {
1237
1321
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1238
1322
  }
1239
1323
 
1240
- function exportSessionCSV() {
1324
+ function exportSession() {
1241
1325
  if (!currentSession) return;
1242
1326
 
1243
- // Create CSV content
1244
- let csvContent = 'Session ID,Project,Message Count,Tokens,File Size,Created,Last Modified,Status\\n';
1245
- 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\`;
1246
-
1247
- csvContent += 'Message #,Role,Content\\n';
1327
+ const format = document.getElementById('exportFormat').value;
1248
1328
 
1249
- // Add conversation history if loaded
1329
+ // Fetch conversation history and export
1250
1330
  fetch(\`/api/session/\${currentSession.id}\`)
1251
1331
  .then(response => response.json())
1252
1332
  .then(sessionData => {
1253
- if (sessionData.messages) {
1254
- sessionData.messages.forEach((message, index) => {
1255
- const content = (message.content || 'no content').replace(/"/g, '""');
1256
- csvContent += \`\${index + 1},"\${message.role}","\${content}"\\n\`;
1257
- });
1333
+ if (format === 'csv') {
1334
+ exportSessionAsCSV(sessionData);
1335
+ } else if (format === 'json') {
1336
+ exportSessionAsJSON(sessionData);
1258
1337
  }
1259
-
1260
- // Download CSV
1261
- const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1262
- const link = document.createElement('a');
1263
- const url = URL.createObjectURL(blob);
1264
- link.setAttribute('href', url);
1265
- link.setAttribute('download', \`claude-session-\${currentSession.id.substring(0, 8)}.csv\`);
1266
- link.style.visibility = 'hidden';
1267
- document.body.appendChild(link);
1268
- link.click();
1269
- document.body.removeChild(link);
1270
1338
  })
1271
1339
  .catch(error => {
1272
- console.error('Failed to export CSV:', error);
1273
- alert('Failed to export CSV. Please try again.');
1340
+ console.error(\`Failed to export \${format.toUpperCase()}:\`, error);
1341
+ alert(\`Failed to export \${format.toUpperCase()}. Please try again.\`);
1274
1342
  });
1275
1343
  }
1276
1344
 
1345
+ function exportSessionAsCSV(sessionData) {
1346
+ // Create CSV content
1347
+ let csvContent = 'Session ID,Project,Message Count,Tokens,File Size,Created,Last Modified,Conversation State,Status\\n';
1348
+ csvContent += \`"\${currentSession.id}","\${currentSession.project}",\${currentSession.messageCount},\${currentSession.tokens},\${currentSession.fileSize},"\${new Date(currentSession.created).toISOString()}","\${new Date(currentSession.lastModified).toISOString()}","\${currentSession.conversationState}","\${currentSession.status}"\\n\\n\`;
1349
+
1350
+ csvContent += 'Message #,Role,Timestamp,Content\\n';
1351
+
1352
+ // Add conversation history
1353
+ if (sessionData.messages) {
1354
+ sessionData.messages.forEach((message, index) => {
1355
+ const content = (message.content || 'no content').replace(/"/g, '""');
1356
+ const timestamp = message.timestamp ? new Date(message.timestamp).toISOString() : 'unknown';
1357
+ csvContent += \`\${index + 1},"\${message.role}","\${timestamp}","\${content}"\\n\`;
1358
+ });
1359
+ }
1360
+
1361
+ // Download CSV
1362
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1363
+ downloadFile(blob, \`claude-session-\${currentSession.id.substring(0, 8)}.csv\`);
1364
+ }
1365
+
1366
+ function exportSessionAsJSON(sessionData) {
1367
+ // Create comprehensive JSON export
1368
+ const exportData = {
1369
+ session: {
1370
+ id: currentSession.id,
1371
+ filename: currentSession.filename,
1372
+ project: currentSession.project,
1373
+ messageCount: currentSession.messageCount,
1374
+ tokens: currentSession.tokens,
1375
+ fileSize: currentSession.fileSize,
1376
+ created: currentSession.created,
1377
+ lastModified: currentSession.lastModified,
1378
+ conversationState: currentSession.conversationState,
1379
+ status: currentSession.status
1380
+ },
1381
+ messages: sessionData.messages || [],
1382
+ metadata: {
1383
+ exportedAt: new Date().toISOString(),
1384
+ exportFormat: 'json',
1385
+ toolVersion: '1.5.7'
1386
+ }
1387
+ };
1388
+
1389
+ // Download JSON
1390
+ const jsonString = JSON.stringify(exportData, null, 2);
1391
+ const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8;' });
1392
+ downloadFile(blob, \`claude-session-\${currentSession.id.substring(0, 8)}.json\`);
1393
+ }
1394
+
1395
+ function downloadFile(blob, filename) {
1396
+ const link = document.createElement('a');
1397
+ const url = URL.createObjectURL(blob);
1398
+ link.setAttribute('href', url);
1399
+ link.setAttribute('download', filename);
1400
+ link.style.visibility = 'hidden';
1401
+ document.body.appendChild(link);
1402
+ link.click();
1403
+ document.body.removeChild(link);
1404
+ URL.revokeObjectURL(url);
1405
+ }
1406
+
1277
1407
  function refreshSessionDetail() {
1278
1408
  if (currentSession) {
1279
1409
  loadConversationHistory(currentSession);