claude-code-templates 1.5.4 → 1.5.6
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 +121 -22
package/package.json
CHANGED
package/src/analytics.js
CHANGED
|
@@ -112,17 +112,32 @@ class ClaudeAnalytics {
|
|
|
112
112
|
// Extract project name from path
|
|
113
113
|
const projectFromPath = this.extractProjectFromPath(filePath);
|
|
114
114
|
|
|
115
|
+
// Parse messages to get their content for status determination
|
|
116
|
+
const parsedMessages = lines.map(line => {
|
|
117
|
+
try {
|
|
118
|
+
const item = JSON.parse(line);
|
|
119
|
+
if (item.message && item.message.role) {
|
|
120
|
+
return {
|
|
121
|
+
role: item.message.role,
|
|
122
|
+
timestamp: new Date(item.timestamp),
|
|
123
|
+
content: item.message.content
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
} catch {}
|
|
127
|
+
return null;
|
|
128
|
+
}).filter(Boolean);
|
|
129
|
+
|
|
115
130
|
const conversation = {
|
|
116
131
|
id: filename.replace('.jsonl', ''),
|
|
117
132
|
filename: filename,
|
|
118
133
|
filePath: filePath,
|
|
119
|
-
messageCount:
|
|
134
|
+
messageCount: parsedMessages.length,
|
|
120
135
|
fileSize: stats.size,
|
|
121
136
|
lastModified: stats.mtime,
|
|
122
137
|
created: stats.birthtime,
|
|
123
138
|
tokens: this.estimateTokens(content),
|
|
124
|
-
project: projectFromPath || this.extractProjectFromConversation(
|
|
125
|
-
status: this.determineConversationStatus(
|
|
139
|
+
project: projectFromPath || this.extractProjectFromConversation(parsedMessages),
|
|
140
|
+
status: this.determineConversationStatus(parsedMessages, stats.mtime)
|
|
126
141
|
};
|
|
127
142
|
|
|
128
143
|
conversations.push(conversation);
|
|
@@ -224,6 +239,35 @@ class ClaudeAnalytics {
|
|
|
224
239
|
const timeDiff = now - lastModified;
|
|
225
240
|
const minutesAgo = timeDiff / (1000 * 60);
|
|
226
241
|
|
|
242
|
+
if (messages.length === 0) {
|
|
243
|
+
return minutesAgo < 5 ? 'active' : 'inactive';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Sort messages by timestamp to get the actual conversation flow
|
|
247
|
+
const sortedMessages = messages.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
248
|
+
const lastMessage = sortedMessages[sortedMessages.length - 1];
|
|
249
|
+
const lastMessageTime = new Date(lastMessage.timestamp);
|
|
250
|
+
const lastMessageMinutesAgo = (now - lastMessageTime) / (1000 * 60);
|
|
251
|
+
|
|
252
|
+
// Advanced status logic
|
|
253
|
+
if (lastMessage.role === 'user') {
|
|
254
|
+
// User sent last message
|
|
255
|
+
if (lastMessageMinutesAgo < 0.5) {
|
|
256
|
+
// Very recent user message, likely typing or waiting for response
|
|
257
|
+
return 'typing';
|
|
258
|
+
} else if (lastMessageMinutesAgo < 3) {
|
|
259
|
+
// Recent user message, waiting for assistant
|
|
260
|
+
return 'active';
|
|
261
|
+
}
|
|
262
|
+
} else if (lastMessage.role === 'assistant') {
|
|
263
|
+
// Assistant sent last message
|
|
264
|
+
if (lastMessageMinutesAgo < 2) {
|
|
265
|
+
// Recent assistant response, conversation is active
|
|
266
|
+
return 'active';
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Fallback to file modification time for edge cases
|
|
227
271
|
if (minutesAgo < 5) return 'active';
|
|
228
272
|
if (minutesAgo < 60) return 'recent';
|
|
229
273
|
return 'inactive';
|
|
@@ -377,7 +421,7 @@ class ClaudeAnalytics {
|
|
|
377
421
|
// Read the actual conversation file
|
|
378
422
|
const content = await fs.readFile(session.filePath, 'utf8');
|
|
379
423
|
const lines = content.trim().split('\n').filter(line => line.trim());
|
|
380
|
-
const
|
|
424
|
+
const rawMessages = lines.map(line => {
|
|
381
425
|
try {
|
|
382
426
|
return JSON.parse(line);
|
|
383
427
|
} catch {
|
|
@@ -385,6 +429,36 @@ class ClaudeAnalytics {
|
|
|
385
429
|
}
|
|
386
430
|
}).filter(Boolean);
|
|
387
431
|
|
|
432
|
+
// Extract actual messages from Claude Code format
|
|
433
|
+
const messages = rawMessages.map(item => {
|
|
434
|
+
if (item.message && item.message.role) {
|
|
435
|
+
let content = '';
|
|
436
|
+
|
|
437
|
+
if (typeof item.message.content === 'string') {
|
|
438
|
+
content = item.message.content;
|
|
439
|
+
} else if (Array.isArray(item.message.content)) {
|
|
440
|
+
content = item.message.content
|
|
441
|
+
.map(block => {
|
|
442
|
+
if (block.type === 'text') return block.text;
|
|
443
|
+
if (block.type === 'tool_use') return `[Tool: ${block.name}]`;
|
|
444
|
+
if (block.type === 'tool_result') return '[Tool Result]';
|
|
445
|
+
return block.content || '';
|
|
446
|
+
})
|
|
447
|
+
.join('\n');
|
|
448
|
+
} else if (item.message.content && item.message.content.length) {
|
|
449
|
+
content = item.message.content[0].text || '';
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
role: item.message.role,
|
|
454
|
+
content: content || 'No content',
|
|
455
|
+
timestamp: item.timestamp,
|
|
456
|
+
type: item.type
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}).filter(Boolean);
|
|
461
|
+
|
|
388
462
|
res.json({
|
|
389
463
|
session: session,
|
|
390
464
|
messages: messages,
|
|
@@ -507,7 +581,7 @@ async function createWebDashboard() {
|
|
|
507
581
|
}
|
|
508
582
|
|
|
509
583
|
.terminal-title {
|
|
510
|
-
color: #
|
|
584
|
+
color: #d57455;
|
|
511
585
|
font-size: 1.25rem;
|
|
512
586
|
font-weight: normal;
|
|
513
587
|
display: flex;
|
|
@@ -553,7 +627,7 @@ async function createWebDashboard() {
|
|
|
553
627
|
}
|
|
554
628
|
|
|
555
629
|
.stat-value {
|
|
556
|
-
color: #
|
|
630
|
+
color: #d57455;
|
|
557
631
|
font-weight: bold;
|
|
558
632
|
}
|
|
559
633
|
|
|
@@ -590,13 +664,13 @@ async function createWebDashboard() {
|
|
|
590
664
|
}
|
|
591
665
|
|
|
592
666
|
.filter-btn:hover {
|
|
593
|
-
border-color: #
|
|
594
|
-
color: #
|
|
667
|
+
border-color: #d57455;
|
|
668
|
+
color: #d57455;
|
|
595
669
|
}
|
|
596
670
|
|
|
597
671
|
.filter-btn.active {
|
|
598
|
-
background: #
|
|
599
|
-
border-color: #
|
|
672
|
+
background: #d57455;
|
|
673
|
+
border-color: #d57455;
|
|
600
674
|
color: #0d1117;
|
|
601
675
|
}
|
|
602
676
|
|
|
@@ -625,7 +699,7 @@ async function createWebDashboard() {
|
|
|
625
699
|
}
|
|
626
700
|
|
|
627
701
|
.session-id {
|
|
628
|
-
color: #
|
|
702
|
+
color: #d57455;
|
|
629
703
|
font-family: monospace;
|
|
630
704
|
}
|
|
631
705
|
|
|
@@ -659,6 +733,17 @@ async function createWebDashboard() {
|
|
|
659
733
|
color: #7d8590;
|
|
660
734
|
}
|
|
661
735
|
|
|
736
|
+
.status-typing {
|
|
737
|
+
color: #d57455;
|
|
738
|
+
font-weight: bold;
|
|
739
|
+
animation: typing-pulse 1.5s infinite;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
@keyframes typing-pulse {
|
|
743
|
+
0%, 100% { opacity: 1; }
|
|
744
|
+
50% { opacity: 0.6; }
|
|
745
|
+
}
|
|
746
|
+
|
|
662
747
|
.loading, .error {
|
|
663
748
|
text-align: center;
|
|
664
749
|
padding: 40px;
|
|
@@ -695,7 +780,7 @@ async function createWebDashboard() {
|
|
|
695
780
|
}
|
|
696
781
|
|
|
697
782
|
.detail-title {
|
|
698
|
-
color: #
|
|
783
|
+
color: #d57455;
|
|
699
784
|
font-size: 1.1rem;
|
|
700
785
|
}
|
|
701
786
|
|
|
@@ -717,19 +802,19 @@ async function createWebDashboard() {
|
|
|
717
802
|
}
|
|
718
803
|
|
|
719
804
|
.btn:hover {
|
|
720
|
-
border-color: #
|
|
721
|
-
color: #
|
|
805
|
+
border-color: #d57455;
|
|
806
|
+
color: #d57455;
|
|
722
807
|
}
|
|
723
808
|
|
|
724
809
|
.btn-primary {
|
|
725
|
-
background: #
|
|
726
|
-
border-color: #
|
|
810
|
+
background: #d57455;
|
|
811
|
+
border-color: #d57455;
|
|
727
812
|
color: #0d1117;
|
|
728
813
|
}
|
|
729
814
|
|
|
730
815
|
.btn-primary:hover {
|
|
731
|
-
background: #
|
|
732
|
-
border-color: #
|
|
816
|
+
background: #e8956f;
|
|
817
|
+
border-color: #e8956f;
|
|
733
818
|
}
|
|
734
819
|
|
|
735
820
|
.session-info {
|
|
@@ -790,7 +875,7 @@ async function createWebDashboard() {
|
|
|
790
875
|
}
|
|
791
876
|
|
|
792
877
|
.message-role.assistant {
|
|
793
|
-
color: #
|
|
878
|
+
color: #d57455;
|
|
794
879
|
}
|
|
795
880
|
|
|
796
881
|
.message-time {
|
|
@@ -875,6 +960,7 @@ async function createWebDashboard() {
|
|
|
875
960
|
<span class="filter-label">filter sessions:</span>
|
|
876
961
|
<div class="filter-buttons">
|
|
877
962
|
<button class="filter-btn active" data-filter="active">active</button>
|
|
963
|
+
<button class="filter-btn" data-filter="typing">typing</button>
|
|
878
964
|
<button class="filter-btn" data-filter="recent">recent</button>
|
|
879
965
|
<button class="filter-btn" data-filter="inactive">inactive</button>
|
|
880
966
|
<button class="filter-btn" data-filter="all">all</button>
|
|
@@ -1006,6 +1092,16 @@ async function createWebDashboard() {
|
|
|
1006
1092
|
return \`\${days}d ago\`;
|
|
1007
1093
|
}
|
|
1008
1094
|
|
|
1095
|
+
function formatMessageTime(timestamp) {
|
|
1096
|
+
const date = new Date(timestamp);
|
|
1097
|
+
return date.toLocaleTimeString('en-US', {
|
|
1098
|
+
hour12: false,
|
|
1099
|
+
hour: '2-digit',
|
|
1100
|
+
minute: '2-digit',
|
|
1101
|
+
second: '2-digit'
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1009
1105
|
// Filter button handlers
|
|
1010
1106
|
document.addEventListener('DOMContentLoaded', function() {
|
|
1011
1107
|
const filterButtons = document.querySelectorAll('.filter-btn');
|
|
@@ -1111,7 +1207,9 @@ async function createWebDashboard() {
|
|
|
1111
1207
|
<div class="message">
|
|
1112
1208
|
<div class="message-header">
|
|
1113
1209
|
<div class="message-role \${message.role}">\${message.role}</div>
|
|
1114
|
-
<div class="message-time">
|
|
1210
|
+
<div class="message-time">
|
|
1211
|
+
#\${index + 1} • \${message.timestamp ? formatMessageTime(message.timestamp) : 'unknown time'}
|
|
1212
|
+
</div>
|
|
1115
1213
|
</div>
|
|
1116
1214
|
<div class="message-content">\${truncateContent(message.content || 'no content')}</div>
|
|
1117
1215
|
</div>
|
|
@@ -1124,10 +1222,11 @@ async function createWebDashboard() {
|
|
|
1124
1222
|
}
|
|
1125
1223
|
}
|
|
1126
1224
|
|
|
1127
|
-
function truncateContent(content, maxLength =
|
|
1225
|
+
function truncateContent(content, maxLength = 1000) {
|
|
1128
1226
|
if (typeof content !== 'string') return 'no content';
|
|
1227
|
+
if (!content.trim()) return 'empty message';
|
|
1129
1228
|
if (content.length <= maxLength) return content;
|
|
1130
|
-
return content.substring(0, maxLength) + '...';
|
|
1229
|
+
return content.substring(0, maxLength) + '\\n\\n[... message truncated ...]';
|
|
1131
1230
|
}
|
|
1132
1231
|
|
|
1133
1232
|
function formatBytes(bytes) {
|