claude-code-templates 1.5.7 → 1.5.9

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 +252 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.5.7",
3
+ "version": "1.5.9",
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
@@ -138,7 +138,8 @@ class ClaudeAnalytics {
138
138
  tokens: this.estimateTokens(content),
139
139
  project: projectFromPath || this.extractProjectFromConversation(parsedMessages),
140
140
  status: this.determineConversationStatus(parsedMessages, stats.mtime),
141
- conversationState: this.determineConversationState(parsedMessages, stats.mtime)
141
+ conversationState: this.determineConversationState(parsedMessages, stats.mtime),
142
+ statusSquares: this.generateStatusSquares(parsedMessages)
142
143
  };
143
144
 
144
145
  conversations.push(conversation);
@@ -303,6 +304,78 @@ class ClaudeAnalytics {
303
304
  return 'Inactive';
304
305
  }
305
306
 
307
+ generateStatusSquares(messages) {
308
+ if (!messages || messages.length === 0) {
309
+ return [];
310
+ }
311
+
312
+ // Sort messages by timestamp and take last 10 for status squares
313
+ const sortedMessages = messages.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
314
+ const recentMessages = sortedMessages.slice(-10);
315
+
316
+ return recentMessages.map((message, index) => {
317
+ const messageNum = sortedMessages.length - recentMessages.length + index + 1;
318
+
319
+ // Determine status based on message content and role
320
+ if (message.role === 'user') {
321
+ return {
322
+ type: 'pending',
323
+ tooltip: `Message #${messageNum}: User input`
324
+ };
325
+ } else if (message.role === 'assistant') {
326
+ // Check if the message contains tool usage or errors
327
+ const content = message.content || '';
328
+
329
+ if (typeof content === 'string') {
330
+ if (content.includes('[Tool:') || content.includes('tool_use')) {
331
+ return {
332
+ type: 'tool',
333
+ tooltip: `Message #${messageNum}: Tool execution`
334
+ };
335
+ } else if (content.includes('error') || content.includes('Error') || content.includes('failed')) {
336
+ return {
337
+ type: 'error',
338
+ tooltip: `Message #${messageNum}: Error in response`
339
+ };
340
+ } else {
341
+ return {
342
+ type: 'success',
343
+ tooltip: `Message #${messageNum}: Successful response`
344
+ };
345
+ }
346
+ } else if (Array.isArray(content)) {
347
+ // Check for tool_use blocks in array content
348
+ const hasToolUse = content.some(block => block.type === 'tool_use');
349
+ const hasError = content.some(block =>
350
+ block.type === 'text' && (block.text?.includes('error') || block.text?.includes('Error'))
351
+ );
352
+
353
+ if (hasError) {
354
+ return {
355
+ type: 'error',
356
+ tooltip: `Message #${messageNum}: Error in response`
357
+ };
358
+ } else if (hasToolUse) {
359
+ return {
360
+ type: 'tool',
361
+ tooltip: `Message #${messageNum}: Tool execution`
362
+ };
363
+ } else {
364
+ return {
365
+ type: 'success',
366
+ tooltip: `Message #${messageNum}: Successful response`
367
+ };
368
+ }
369
+ }
370
+ }
371
+
372
+ return {
373
+ type: 'pending',
374
+ tooltip: `Message #${messageNum}: Unknown status`
375
+ };
376
+ });
377
+ }
378
+
306
379
  determineProjectStatus(lastActivity) {
307
380
  const now = new Date();
308
381
  const timeDiff = now - lastActivity;
@@ -731,6 +804,10 @@ async function createWebDashboard() {
731
804
  .session-id {
732
805
  color: #d57455;
733
806
  font-family: monospace;
807
+ display: flex;
808
+ align-items: center;
809
+ flex-wrap: wrap;
810
+ gap: 4px;
734
811
  }
735
812
 
736
813
  .session-project {
@@ -787,6 +864,66 @@ async function createWebDashboard() {
787
864
  50% { opacity: 0.6; }
788
865
  }
789
866
 
867
+ .status-squares {
868
+ display: flex;
869
+ gap: 2px;
870
+ align-items: center;
871
+ margin-left: 4px;
872
+ flex-wrap: wrap;
873
+ }
874
+
875
+ .status-square {
876
+ width: 8px;
877
+ height: 8px;
878
+ border-radius: 1px;
879
+ cursor: help;
880
+ position: relative;
881
+ }
882
+
883
+ .status-square.success {
884
+ background: #d57455;
885
+ }
886
+
887
+ .status-square.tool {
888
+ background: #f97316;
889
+ }
890
+
891
+ .status-square.error {
892
+ background: #dc2626;
893
+ }
894
+
895
+ .status-square.pending {
896
+ background: #6b7280;
897
+ }
898
+
899
+ .status-square:hover::after {
900
+ content: attr(data-tooltip);
901
+ position: absolute;
902
+ bottom: 100%;
903
+ left: 50%;
904
+ transform: translateX(-50%);
905
+ background: #1c1c1c;
906
+ color: #fff;
907
+ padding: 4px 8px;
908
+ border-radius: 4px;
909
+ font-size: 0.75rem;
910
+ white-space: nowrap;
911
+ z-index: 1000;
912
+ margin-bottom: 4px;
913
+ border: 1px solid #30363d;
914
+ }
915
+
916
+ .status-square:hover::before {
917
+ content: '';
918
+ position: absolute;
919
+ bottom: 100%;
920
+ left: 50%;
921
+ transform: translateX(-50%);
922
+ border: 4px solid transparent;
923
+ border-top-color: #30363d;
924
+ z-index: 1000;
925
+ }
926
+
790
927
  .loading, .error {
791
928
  text-align: center;
792
929
  padding: 40px;
@@ -830,6 +967,28 @@ async function createWebDashboard() {
830
967
  .detail-actions {
831
968
  display: flex;
832
969
  gap: 12px;
970
+ align-items: center;
971
+ }
972
+
973
+ .export-format-select {
974
+ background: #21262d;
975
+ border: 1px solid #30363d;
976
+ color: #c9d1d9;
977
+ padding: 6px 12px;
978
+ border-radius: 4px;
979
+ font-family: inherit;
980
+ font-size: 0.875rem;
981
+ cursor: pointer;
982
+ }
983
+
984
+ .export-format-select:focus {
985
+ outline: none;
986
+ border-color: #d57455;
987
+ }
988
+
989
+ .export-format-select option {
990
+ background: #21262d;
991
+ color: #c9d1d9;
833
992
  }
834
993
 
835
994
  .btn {
@@ -1036,7 +1195,11 @@ async function createWebDashboard() {
1036
1195
  <div class="detail-header">
1037
1196
  <div class="detail-title" id="detailTitle">session details</div>
1038
1197
  <div class="detail-actions">
1039
- <button class="btn" onclick="exportSessionCSV()">export csv</button>
1198
+ <select id="exportFormat" class="export-format-select">
1199
+ <option value="csv">CSV</option>
1200
+ <option value="json">JSON</option>
1201
+ </select>
1202
+ <button class="btn" onclick="exportSession()">export</button>
1040
1203
  <button class="btn btn-primary" onclick="refreshSessionDetail()">refresh</button>
1041
1204
  </div>
1042
1205
  </div>
@@ -1112,7 +1275,12 @@ async function createWebDashboard() {
1112
1275
 
1113
1276
  tableBody.innerHTML = filteredConversations.map(conv => \`
1114
1277
  <tr onclick="showSessionDetail('\${conv.id}')" style="cursor: pointer;">
1115
- <td class="session-id">\${conv.id.substring(0, 8)}...</td>
1278
+ <td class="session-id">
1279
+ \${conv.id.substring(0, 8)}...
1280
+ <div class="status-squares">
1281
+ \${generateStatusSquaresHTML(conv.statusSquares || [])}
1282
+ </div>
1283
+ </td>
1116
1284
  <td class="session-project">\${conv.project}</td>
1117
1285
  <td class="session-messages">\${conv.messageCount}</td>
1118
1286
  <td class="session-tokens">\${conv.tokens.toLocaleString()}</td>
@@ -1156,6 +1324,16 @@ async function createWebDashboard() {
1156
1324
  return '';
1157
1325
  }
1158
1326
 
1327
+ function generateStatusSquaresHTML(statusSquares) {
1328
+ if (!statusSquares || statusSquares.length === 0) {
1329
+ return '';
1330
+ }
1331
+
1332
+ return statusSquares.map(square =>
1333
+ \`<div class="status-square \${square.type}" data-tooltip="\${square.tooltip}"></div>\`
1334
+ ).join('');
1335
+ }
1336
+
1159
1337
  // Filter button handlers
1160
1338
  document.addEventListener('DOMContentLoaded', function() {
1161
1339
  const filterButtons = document.querySelectorAll('.filter-btn');
@@ -1295,43 +1473,89 @@ async function createWebDashboard() {
1295
1473
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1296
1474
  }
1297
1475
 
1298
- function exportSessionCSV() {
1476
+ function exportSession() {
1299
1477
  if (!currentSession) return;
1300
1478
 
1301
- // Create CSV content
1302
- let csvContent = 'Session ID,Project,Message Count,Tokens,File Size,Created,Last Modified,Status\\n';
1303
- 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\`;
1304
-
1305
- csvContent += 'Message #,Role,Content\\n';
1479
+ const format = document.getElementById('exportFormat').value;
1306
1480
 
1307
- // Add conversation history if loaded
1481
+ // Fetch conversation history and export
1308
1482
  fetch(\`/api/session/\${currentSession.id}\`)
1309
1483
  .then(response => response.json())
1310
1484
  .then(sessionData => {
1311
- if (sessionData.messages) {
1312
- sessionData.messages.forEach((message, index) => {
1313
- const content = (message.content || 'no content').replace(/"/g, '""');
1314
- csvContent += \`\${index + 1},"\${message.role}","\${content}"\\n\`;
1315
- });
1485
+ if (format === 'csv') {
1486
+ exportSessionAsCSV(sessionData);
1487
+ } else if (format === 'json') {
1488
+ exportSessionAsJSON(sessionData);
1316
1489
  }
1317
-
1318
- // Download CSV
1319
- const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1320
- const link = document.createElement('a');
1321
- const url = URL.createObjectURL(blob);
1322
- link.setAttribute('href', url);
1323
- link.setAttribute('download', \`claude-session-\${currentSession.id.substring(0, 8)}.csv\`);
1324
- link.style.visibility = 'hidden';
1325
- document.body.appendChild(link);
1326
- link.click();
1327
- document.body.removeChild(link);
1328
1490
  })
1329
1491
  .catch(error => {
1330
- console.error('Failed to export CSV:', error);
1331
- alert('Failed to export CSV. Please try again.');
1492
+ console.error(\`Failed to export \${format.toUpperCase()}:\`, error);
1493
+ alert(\`Failed to export \${format.toUpperCase()}. Please try again.\`);
1332
1494
  });
1333
1495
  }
1334
1496
 
1497
+ function exportSessionAsCSV(sessionData) {
1498
+ // Create CSV content
1499
+ let csvContent = 'Session ID,Project,Message Count,Tokens,File Size,Created,Last Modified,Conversation State,Status\\n';
1500
+ 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\`;
1501
+
1502
+ csvContent += 'Message #,Role,Timestamp,Content\\n';
1503
+
1504
+ // Add conversation history
1505
+ if (sessionData.messages) {
1506
+ sessionData.messages.forEach((message, index) => {
1507
+ const content = (message.content || 'no content').replace(/"/g, '""');
1508
+ const timestamp = message.timestamp ? new Date(message.timestamp).toISOString() : 'unknown';
1509
+ csvContent += \`\${index + 1},"\${message.role}","\${timestamp}","\${content}"\\n\`;
1510
+ });
1511
+ }
1512
+
1513
+ // Download CSV
1514
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1515
+ downloadFile(blob, \`claude-session-\${currentSession.id.substring(0, 8)}.csv\`);
1516
+ }
1517
+
1518
+ function exportSessionAsJSON(sessionData) {
1519
+ // Create comprehensive JSON export
1520
+ const exportData = {
1521
+ session: {
1522
+ id: currentSession.id,
1523
+ filename: currentSession.filename,
1524
+ project: currentSession.project,
1525
+ messageCount: currentSession.messageCount,
1526
+ tokens: currentSession.tokens,
1527
+ fileSize: currentSession.fileSize,
1528
+ created: currentSession.created,
1529
+ lastModified: currentSession.lastModified,
1530
+ conversationState: currentSession.conversationState,
1531
+ status: currentSession.status
1532
+ },
1533
+ messages: sessionData.messages || [],
1534
+ metadata: {
1535
+ exportedAt: new Date().toISOString(),
1536
+ exportFormat: 'json',
1537
+ toolVersion: '1.5.7'
1538
+ }
1539
+ };
1540
+
1541
+ // Download JSON
1542
+ const jsonString = JSON.stringify(exportData, null, 2);
1543
+ const blob = new Blob([jsonString], { type: 'application/json;charset=utf-8;' });
1544
+ downloadFile(blob, \`claude-session-\${currentSession.id.substring(0, 8)}.json\`);
1545
+ }
1546
+
1547
+ function downloadFile(blob, filename) {
1548
+ const link = document.createElement('a');
1549
+ const url = URL.createObjectURL(blob);
1550
+ link.setAttribute('href', url);
1551
+ link.setAttribute('download', filename);
1552
+ link.style.visibility = 'hidden';
1553
+ document.body.appendChild(link);
1554
+ link.click();
1555
+ document.body.removeChild(link);
1556
+ URL.revokeObjectURL(url);
1557
+ }
1558
+
1335
1559
  function refreshSessionDetail() {
1336
1560
  if (currentSession) {
1337
1561
  loadConversationHistory(currentSession);