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.
- package/package.json +1 -1
- package/src/analytics.js +171 -41
package/package.json
CHANGED
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
|
-
//
|
|
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
|
-
|
|
257
|
-
return 'typing';
|
|
285
|
+
return 'Claude Code working...';
|
|
258
286
|
} else if (lastMessageMinutesAgo < 3) {
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
266
|
-
|
|
294
|
+
return 'Awaiting user input...';
|
|
295
|
+
} else if (lastMessageMinutesAgo < 5) {
|
|
296
|
+
return 'User may be typing...';
|
|
267
297
|
}
|
|
268
298
|
}
|
|
269
299
|
|
|
270
|
-
// Fallback
|
|
271
|
-
if (minutesAgo < 5) return 'active';
|
|
272
|
-
if (minutesAgo < 60) return '
|
|
273
|
-
return '
|
|
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
|
-
.
|
|
766
|
+
.conversation-state {
|
|
737
767
|
color: #d57455;
|
|
738
|
-
font-
|
|
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
|
-
<
|
|
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
|
|
1324
|
+
function exportSession() {
|
|
1241
1325
|
if (!currentSession) return;
|
|
1242
1326
|
|
|
1243
|
-
|
|
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
|
-
//
|
|
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 (
|
|
1254
|
-
sessionData
|
|
1255
|
-
|
|
1256
|
-
|
|
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(
|
|
1273
|
-
alert(
|
|
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);
|