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.
- package/package.json +1 -1
- package/src/analytics.js +252 -28
package/package.json
CHANGED
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
|
-
<
|
|
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"
|
|
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
|
|
1476
|
+
function exportSession() {
|
|
1299
1477
|
if (!currentSession) return;
|
|
1300
1478
|
|
|
1301
|
-
|
|
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
|
-
//
|
|
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 (
|
|
1312
|
-
sessionData
|
|
1313
|
-
|
|
1314
|
-
|
|
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(
|
|
1331
|
-
alert(
|
|
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);
|