claude-code-templates 1.5.2 → 1.5.4

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 +586 -157
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-templates",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
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
@@ -364,6 +364,39 @@ class ClaudeAnalytics {
364
364
  });
365
365
  });
366
366
 
367
+ // Session detail endpoint
368
+ this.app.get('/api/session/:sessionId', async (req, res) => {
369
+ const sessionId = req.params.sessionId;
370
+
371
+ try {
372
+ const session = this.data.conversations.find(conv => conv.id === sessionId);
373
+ if (!session) {
374
+ return res.status(404).json({ error: 'Session not found' });
375
+ }
376
+
377
+ // Read the actual conversation file
378
+ const content = await fs.readFile(session.filePath, 'utf8');
379
+ const lines = content.trim().split('\n').filter(line => line.trim());
380
+ const messages = lines.map(line => {
381
+ try {
382
+ return JSON.parse(line);
383
+ } catch {
384
+ return null;
385
+ }
386
+ }).filter(Boolean);
387
+
388
+ res.json({
389
+ session: session,
390
+ messages: messages,
391
+ timestamp: new Date().toISOString()
392
+ });
393
+
394
+ } catch (error) {
395
+ console.error(chalk.red('Error loading session details:'), error.message);
396
+ res.status(500).json({ error: 'Failed to load session details' });
397
+ }
398
+ });
399
+
367
400
  // Main dashboard route
368
401
  this.app.get('/', (req, res) => {
369
402
  res.sendFile(path.join(__dirname, 'analytics-web', 'index.html'));
@@ -445,7 +478,7 @@ async function createWebDashboard() {
445
478
  <head>
446
479
  <meta charset="UTF-8">
447
480
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
448
- <title>Claude Code Analytics Dashboard</title>
481
+ <title>Claude Code Analytics - Terminal</title>
449
482
  <style>
450
483
  * {
451
484
  margin: 0;
@@ -454,227 +487,439 @@ async function createWebDashboard() {
454
487
  }
455
488
 
456
489
  body {
457
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
458
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
459
- color: #333;
490
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
491
+ background: #0d1117;
492
+ color: #c9d1d9;
460
493
  min-height: 100vh;
494
+ line-height: 1.4;
461
495
  }
462
496
 
463
- .container {
464
- max-width: 1200px;
497
+ .terminal {
498
+ max-width: 1400px;
465
499
  margin: 0 auto;
466
500
  padding: 20px;
467
501
  }
468
502
 
469
- .header {
470
- text-align: center;
471
- color: white;
472
- margin-bottom: 30px;
503
+ .terminal-header {
504
+ border-bottom: 1px solid #30363d;
505
+ padding-bottom: 20px;
506
+ margin-bottom: 20px;
473
507
  }
474
508
 
475
- .header h1 {
476
- font-size: 2.5rem;
477
- margin-bottom: 10px;
509
+ .terminal-title {
510
+ color: #58a6ff;
511
+ font-size: 1.25rem;
512
+ font-weight: normal;
513
+ display: flex;
514
+ align-items: center;
515
+ gap: 8px;
478
516
  }
479
517
 
480
- .status-indicator {
481
- display: inline-block;
482
- width: 12px;
483
- height: 12px;
518
+ .status-dot {
519
+ width: 8px;
520
+ height: 8px;
484
521
  border-radius: 50%;
485
- background: #4ade80;
522
+ background: #3fb950;
486
523
  animation: pulse 2s infinite;
487
- margin-right: 8px;
488
524
  }
489
525
 
490
526
  @keyframes pulse {
491
527
  0%, 100% { opacity: 1; }
492
- 50% { opacity: 0.5; }
528
+ 50% { opacity: 0.6; }
493
529
  }
494
530
 
495
- .stats-grid {
496
- display: grid;
497
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
498
- gap: 20px;
499
- margin-bottom: 30px;
531
+ .terminal-subtitle {
532
+ color: #7d8590;
533
+ font-size: 0.875rem;
534
+ margin-top: 4px;
500
535
  }
501
536
 
502
- .stat-card {
503
- background: white;
504
- border-radius: 12px;
505
- padding: 24px;
506
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
507
- transition: transform 0.2s ease;
537
+ .stats-bar {
538
+ display: flex;
539
+ gap: 40px;
540
+ margin: 20px 0;
541
+ flex-wrap: wrap;
508
542
  }
509
543
 
510
- .stat-card:hover {
511
- transform: translateY(-2px);
544
+ .stat {
545
+ display: flex;
546
+ align-items: center;
547
+ gap: 8px;
512
548
  }
513
549
 
514
- .stat-card h3 {
515
- color: #6b7280;
550
+ .stat-label {
551
+ color: #7d8590;
516
552
  font-size: 0.875rem;
517
- text-transform: uppercase;
518
- letter-spacing: 0.05em;
519
- margin-bottom: 8px;
520
553
  }
521
554
 
522
- .stat-card .value {
523
- font-size: 2rem;
555
+ .stat-value {
556
+ color: #58a6ff;
524
557
  font-weight: bold;
525
- color: #1f2937;
526
- margin-bottom: 4px;
527
558
  }
528
559
 
529
- .stat-card .label {
530
- color: #9ca3af;
560
+ .filter-bar {
561
+ display: flex;
562
+ align-items: center;
563
+ gap: 16px;
564
+ margin: 20px 0;
565
+ padding: 12px 0;
566
+ border-top: 1px solid #21262d;
567
+ border-bottom: 1px solid #21262d;
568
+ }
569
+
570
+ .filter-label {
571
+ color: #7d8590;
531
572
  font-size: 0.875rem;
532
573
  }
533
574
 
534
- .content-grid {
535
- display: grid;
536
- grid-template-columns: 1fr 1fr;
537
- gap: 20px;
575
+ .filter-buttons {
576
+ display: flex;
577
+ gap: 8px;
538
578
  }
539
579
 
540
- .panel {
541
- background: white;
542
- border-radius: 12px;
543
- padding: 24px;
544
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
580
+ .filter-btn {
581
+ background: none;
582
+ border: 1px solid #30363d;
583
+ color: #7d8590;
584
+ padding: 4px 12px;
585
+ border-radius: 4px;
586
+ cursor: pointer;
587
+ font-family: inherit;
588
+ font-size: 0.875rem;
589
+ transition: all 0.2s ease;
545
590
  }
546
591
 
547
- .panel h2 {
548
- color: #1f2937;
549
- margin-bottom: 20px;
550
- font-size: 1.25rem;
592
+ .filter-btn:hover {
593
+ border-color: #58a6ff;
594
+ color: #58a6ff;
551
595
  }
552
596
 
553
- .conversation-item, .project-item {
554
- display: flex;
555
- justify-content: space-between;
556
- align-items: center;
557
- padding: 12px 0;
558
- border-bottom: 1px solid #f3f4f6;
597
+ .filter-btn.active {
598
+ background: #58a6ff;
599
+ border-color: #58a6ff;
600
+ color: #0d1117;
559
601
  }
560
602
 
561
- .conversation-item:last-child, .project-item:last-child {
562
- border-bottom: none;
603
+ .sessions-table {
604
+ width: 100%;
605
+ border-collapse: collapse;
563
606
  }
564
607
 
565
- .item-info h4 {
566
- color: #1f2937;
608
+ .sessions-table th {
609
+ text-align: left;
610
+ padding: 8px 12px;
611
+ color: #7d8590;
567
612
  font-size: 0.875rem;
568
- margin-bottom: 4px;
613
+ font-weight: normal;
614
+ border-bottom: 1px solid #30363d;
569
615
  }
570
616
 
571
- .item-info p {
572
- color: #6b7280;
573
- font-size: 0.75rem;
617
+ .sessions-table td {
618
+ padding: 8px 12px;
619
+ font-size: 0.875rem;
620
+ border-bottom: 1px solid #21262d;
574
621
  }
575
622
 
576
- .status-badge {
577
- padding: 4px 8px;
578
- border-radius: 12px;
579
- font-size: 0.75rem;
580
- font-weight: 500;
623
+ .sessions-table tr:hover {
624
+ background: #161b22;
625
+ }
626
+
627
+ .session-id {
628
+ color: #58a6ff;
629
+ font-family: monospace;
630
+ }
631
+
632
+ .session-project {
633
+ color: #c9d1d9;
634
+ }
635
+
636
+ .session-messages {
637
+ color: #7d8590;
638
+ }
639
+
640
+ .session-tokens {
641
+ color: #f85149;
642
+ }
643
+
644
+ .session-time {
645
+ color: #7d8590;
646
+ font-size: 0.8rem;
581
647
  }
582
648
 
583
649
  .status-active {
584
- background: #d1fae5;
585
- color: #065f46;
650
+ color: #3fb950;
651
+ font-weight: bold;
586
652
  }
587
653
 
588
654
  .status-recent {
589
- background: #fef3c7;
590
- color: #92400e;
655
+ color: #d29922;
591
656
  }
592
657
 
593
658
  .status-inactive {
594
- background: #f3f4f6;
595
- color: #6b7280;
659
+ color: #7d8590;
596
660
  }
597
661
 
598
- .loading {
662
+ .loading, .error {
599
663
  text-align: center;
600
- color: white;
601
664
  padding: 40px;
665
+ color: #7d8590;
602
666
  }
603
667
 
604
668
  .error {
605
- background: #fef2f2;
606
- color: #dc2626;
669
+ color: #f85149;
670
+ }
671
+
672
+ .no-sessions {
673
+ text-align: center;
674
+ padding: 40px;
675
+ color: #7d8590;
676
+ font-style: italic;
677
+ }
678
+
679
+ .session-detail {
680
+ display: none;
681
+ margin-top: 20px;
682
+ }
683
+
684
+ .session-detail.active {
685
+ display: block;
686
+ }
687
+
688
+ .detail-header {
689
+ display: flex;
690
+ justify-content: space-between;
691
+ align-items: center;
692
+ padding: 16px 0;
693
+ border-bottom: 1px solid #30363d;
694
+ margin-bottom: 20px;
695
+ }
696
+
697
+ .detail-title {
698
+ color: #58a6ff;
699
+ font-size: 1.1rem;
700
+ }
701
+
702
+ .detail-actions {
703
+ display: flex;
704
+ gap: 12px;
705
+ }
706
+
707
+ .btn {
708
+ background: none;
709
+ border: 1px solid #30363d;
710
+ color: #7d8590;
711
+ padding: 6px 12px;
712
+ border-radius: 4px;
713
+ cursor: pointer;
714
+ font-family: inherit;
715
+ font-size: 0.875rem;
716
+ transition: all 0.2s ease;
717
+ }
718
+
719
+ .btn:hover {
720
+ border-color: #58a6ff;
721
+ color: #58a6ff;
722
+ }
723
+
724
+ .btn-primary {
725
+ background: #58a6ff;
726
+ border-color: #58a6ff;
727
+ color: #0d1117;
728
+ }
729
+
730
+ .btn-primary:hover {
731
+ background: #79c0ff;
732
+ border-color: #79c0ff;
733
+ }
734
+
735
+ .session-info {
736
+ display: grid;
737
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
738
+ gap: 20px;
739
+ margin-bottom: 30px;
740
+ }
741
+
742
+ .info-item {
743
+ display: flex;
744
+ flex-direction: column;
745
+ gap: 4px;
746
+ }
747
+
748
+ .info-label {
749
+ color: #7d8590;
750
+ font-size: 0.75rem;
751
+ text-transform: uppercase;
752
+ }
753
+
754
+ .info-value {
755
+ color: #c9d1d9;
756
+ font-size: 0.875rem;
757
+ }
758
+
759
+ .conversation-history {
760
+ border: 1px solid #30363d;
761
+ border-radius: 6px;
762
+ max-height: 600px;
763
+ overflow-y: auto;
764
+ }
765
+
766
+ .message {
607
767
  padding: 16px;
608
- border-radius: 8px;
609
- margin: 20px 0;
768
+ border-bottom: 1px solid #21262d;
769
+ }
770
+
771
+ .message:last-child {
772
+ border-bottom: none;
773
+ }
774
+
775
+ .message-header {
776
+ display: flex;
777
+ justify-content: space-between;
778
+ align-items: center;
779
+ margin-bottom: 8px;
780
+ }
781
+
782
+ .message-role {
783
+ color: #58a6ff;
784
+ font-size: 0.875rem;
785
+ font-weight: bold;
786
+ }
787
+
788
+ .message-role.user {
789
+ color: #3fb950;
790
+ }
791
+
792
+ .message-role.assistant {
793
+ color: #58a6ff;
794
+ }
795
+
796
+ .message-time {
797
+ color: #7d8590;
798
+ font-size: 0.75rem;
799
+ }
800
+
801
+ .message-content {
802
+ color: #c9d1d9;
803
+ font-size: 0.875rem;
804
+ line-height: 1.5;
805
+ white-space: pre-wrap;
806
+ word-wrap: break-word;
807
+ }
808
+
809
+ .back-btn {
810
+ margin-bottom: 20px;
610
811
  }
611
812
 
612
813
  @media (max-width: 768px) {
613
- .content-grid {
614
- grid-template-columns: 1fr;
814
+ .stats-bar {
815
+ gap: 20px;
816
+ }
817
+
818
+ .filter-bar {
819
+ flex-direction: column;
820
+ align-items: flex-start;
821
+ gap: 8px;
615
822
  }
616
823
 
617
- .header h1 {
618
- font-size: 2rem;
824
+ .sessions-table {
825
+ font-size: 0.8rem;
826
+ }
827
+
828
+ .sessions-table th,
829
+ .sessions-table td {
830
+ padding: 6px 8px;
619
831
  }
620
832
  }
621
833
  </style>
622
834
  </head>
623
835
  <body>
624
- <div class="container">
625
- <div class="header">
626
- <h1>
627
- <span class="status-indicator"></span>
628
- Claude Code Analytics
629
- </h1>
630
- <p>Real-time monitoring of your Claude Code usage</p>
631
- <p id="lastUpdate" style="font-size: 0.75rem; opacity: 0.8;"></p>
836
+ <div class="terminal">
837
+ <div class="terminal-header">
838
+ <div class="terminal-title">
839
+ <span class="status-dot"></span>
840
+ claude-code-analytics
841
+ </div>
842
+ <div class="terminal-subtitle">real-time monitoring dashboard</div>
843
+ <div class="terminal-subtitle" id="lastUpdate"></div>
632
844
  </div>
633
845
 
634
846
  <div id="loading" class="loading">
635
- <p>Loading analytics data...</p>
847
+ loading claude code data...
636
848
  </div>
637
849
 
638
850
  <div id="error" class="error" style="display: none;">
639
- <p>Failed to load analytics data. Please check if Claude Code is installed.</p>
851
+ error: failed to load claude code data
640
852
  </div>
641
853
 
642
854
  <div id="dashboard" style="display: none;">
643
- <div class="stats-grid">
644
- <div class="stat-card">
645
- <h3>Total Sessions</h3>
646
- <div class="value" id="totalSessions">0</div>
647
- <div class="label">Conversations</div>
855
+ <div class="stats-bar">
856
+ <div class="stat">
857
+ <span class="stat-label">sessions:</span>
858
+ <span class="stat-value" id="totalSessions">0</span>
648
859
  </div>
649
- <div class="stat-card">
650
- <h3>Total Tokens</h3>
651
- <div class="value" id="totalTokens">0</div>
652
- <div class="label">Estimated</div>
860
+ <div class="stat">
861
+ <span class="stat-label">tokens:</span>
862
+ <span class="stat-value" id="totalTokens">0</span>
653
863
  </div>
654
- <div class="stat-card">
655
- <h3>Active Projects</h3>
656
- <div class="value" id="activeProjects">0</div>
657
- <div class="label">Currently</div>
864
+ <div class="stat">
865
+ <span class="stat-label">projects:</span>
866
+ <span class="stat-value" id="activeProjects">0</span>
658
867
  </div>
659
- <div class="stat-card">
660
- <h3>Data Size</h3>
661
- <div class="value" id="dataSize">0</div>
662
- <div class="label">Total</div>
868
+ <div class="stat">
869
+ <span class="stat-label">storage:</span>
870
+ <span class="stat-value" id="dataSize">0</span>
663
871
  </div>
664
872
  </div>
665
873
 
666
- <div class="content-grid">
667
- <div class="panel">
668
- <h2>Recent Conversations</h2>
669
- <div id="conversations">
670
- <!-- Conversations will be loaded here -->
874
+ <div class="filter-bar">
875
+ <span class="filter-label">filter sessions:</span>
876
+ <div class="filter-buttons">
877
+ <button class="filter-btn active" data-filter="active">active</button>
878
+ <button class="filter-btn" data-filter="recent">recent</button>
879
+ <button class="filter-btn" data-filter="inactive">inactive</button>
880
+ <button class="filter-btn" data-filter="all">all</button>
881
+ </div>
882
+ </div>
883
+
884
+ <table class="sessions-table">
885
+ <thead>
886
+ <tr>
887
+ <th>session id</th>
888
+ <th>project</th>
889
+ <th>messages</th>
890
+ <th>tokens</th>
891
+ <th>last activity</th>
892
+ <th>status</th>
893
+ </tr>
894
+ </thead>
895
+ <tbody id="sessionsTable">
896
+ <!-- Sessions will be loaded here -->
897
+ </tbody>
898
+ </table>
899
+
900
+ <div id="noSessions" class="no-sessions" style="display: none;">
901
+ no sessions found for current filter
902
+ </div>
903
+
904
+ <div id="sessionDetail" class="session-detail">
905
+ <button class="btn back-btn" onclick="showSessionsList()">← back to sessions</button>
906
+
907
+ <div class="detail-header">
908
+ <div class="detail-title" id="detailTitle">session details</div>
909
+ <div class="detail-actions">
910
+ <button class="btn" onclick="exportSessionCSV()">export csv</button>
911
+ <button class="btn btn-primary" onclick="refreshSessionDetail()">refresh</button>
671
912
  </div>
672
913
  </div>
673
914
 
674
- <div class="panel">
675
- <h2>Active Projects</h2>
676
- <div id="projects">
677
- <!-- Projects will be loaded here -->
915
+ <div class="session-info" id="sessionInfo">
916
+ <!-- Session info will be loaded here -->
917
+ </div>
918
+
919
+ <div>
920
+ <h3 style="color: #7d8590; margin-bottom: 16px; font-size: 0.875rem; text-transform: uppercase;">conversation history</h3>
921
+ <div class="conversation-history" id="conversationHistory">
922
+ <!-- Conversation history will be loaded here -->
678
923
  </div>
679
924
  </div>
680
925
  </div>
@@ -682,6 +927,10 @@ async function createWebDashboard() {
682
927
  </div>
683
928
 
684
929
  <script>
930
+ let allConversations = [];
931
+ let currentFilter = 'active';
932
+ let currentSession = null;
933
+
685
934
  async function loadData() {
686
935
  try {
687
936
  const response = await fetch('/api/data');
@@ -694,11 +943,11 @@ async function createWebDashboard() {
694
943
 
695
944
  // Update timestamp
696
945
  document.getElementById('lastUpdate').textContent =
697
- \`Last updated: \${data.lastUpdate}\`;
946
+ \`last update: \${data.lastUpdate}\`;
698
947
 
699
948
  updateStats(data.summary);
700
- updateConversations(data.conversations);
701
- updateProjects(data.activeProjects);
949
+ allConversations = data.conversations;
950
+ updateSessionsTable();
702
951
 
703
952
  } catch (error) {
704
953
  document.getElementById('loading').style.display = 'none';
@@ -714,42 +963,222 @@ async function createWebDashboard() {
714
963
  document.getElementById('dataSize').textContent = summary.totalFileSize;
715
964
  }
716
965
 
717
- function updateConversations(conversations) {
718
- const container = document.getElementById('conversations');
966
+ function updateSessionsTable() {
967
+ const tableBody = document.getElementById('sessionsTable');
968
+ const noSessionsDiv = document.getElementById('noSessions');
969
+
970
+ // Filter conversations based on current filter
971
+ let filteredConversations = allConversations;
972
+ if (currentFilter !== 'all') {
973
+ filteredConversations = allConversations.filter(conv => conv.status === currentFilter);
974
+ }
719
975
 
720
- if (conversations.length === 0) {
721
- container.innerHTML = '<p style="color: #6b7280; text-align: center; padding: 20px;">No conversations found</p>';
976
+ if (filteredConversations.length === 0) {
977
+ tableBody.innerHTML = '';
978
+ noSessionsDiv.style.display = 'block';
722
979
  return;
723
980
  }
724
981
 
725
- container.innerHTML = conversations.slice(0, 10).map(conv => \`
726
- <div class="conversation-item">
727
- <div class="item-info">
728
- <h4>\${conv.project}</h4>
729
- <p>\${conv.messageCount} messages • \${conv.tokens.toLocaleString()} tokens</p>
730
- </div>
731
- <span class="status-badge status-\${conv.status}">\${conv.status}</span>
732
- </div>
982
+ noSessionsDiv.style.display = 'none';
983
+
984
+ tableBody.innerHTML = filteredConversations.map(conv => \`
985
+ <tr onclick="showSessionDetail('\${conv.id}')" style="cursor: pointer;">
986
+ <td class="session-id">\${conv.id.substring(0, 8)}...</td>
987
+ <td class="session-project">\${conv.project}</td>
988
+ <td class="session-messages">\${conv.messageCount}</td>
989
+ <td class="session-tokens">\${conv.tokens.toLocaleString()}</td>
990
+ <td class="session-time">\${formatTime(conv.lastModified)}</td>
991
+ <td class="status-\${conv.status}">\${conv.status}</td>
992
+ </tr>
733
993
  \`).join('');
734
994
  }
735
995
 
736
- function updateProjects(projects) {
737
- const container = document.getElementById('projects');
996
+ function formatTime(date) {
997
+ const now = new Date();
998
+ const diff = now - new Date(date);
999
+ const minutes = Math.floor(diff / (1000 * 60));
1000
+ const hours = Math.floor(minutes / 60);
1001
+ const days = Math.floor(hours / 24);
738
1002
 
739
- if (projects.length === 0) {
740
- container.innerHTML = '<p style="color: #6b7280; text-align: center; padding: 20px;">No projects found</p>';
741
- return;
742
- }
1003
+ if (minutes < 1) return 'now';
1004
+ if (minutes < 60) return \`\${minutes}m ago\`;
1005
+ if (hours < 24) return \`\${hours}h ago\`;
1006
+ return \`\${days}d ago\`;
1007
+ }
1008
+
1009
+ // Filter button handlers
1010
+ document.addEventListener('DOMContentLoaded', function() {
1011
+ const filterButtons = document.querySelectorAll('.filter-btn');
743
1012
 
744
- container.innerHTML = projects.slice(0, 10).map(project => \`
745
- <div class="project-item">
746
- <div class="item-info">
747
- <h4>\${project.name}</h4>
748
- <p>\${project.todoFiles} todo files</p>
749
- </div>
750
- <span class="status-badge status-\${project.status}">\${project.status}</span>
1013
+ filterButtons.forEach(button => {
1014
+ button.addEventListener('click', function() {
1015
+ // Remove active class from all buttons
1016
+ filterButtons.forEach(btn => btn.classList.remove('active'));
1017
+
1018
+ // Add active class to clicked button
1019
+ this.classList.add('active');
1020
+
1021
+ // Update current filter
1022
+ currentFilter = this.dataset.filter;
1023
+
1024
+ // Update table
1025
+ updateSessionsTable();
1026
+ });
1027
+ });
1028
+ });
1029
+
1030
+ // Session detail functions
1031
+ async function showSessionDetail(sessionId) {
1032
+ currentSession = allConversations.find(conv => conv.id === sessionId);
1033
+ if (!currentSession) return;
1034
+
1035
+ // Hide sessions list and show detail
1036
+ document.querySelector('.filter-bar').style.display = 'none';
1037
+ document.querySelector('.sessions-table').style.display = 'none';
1038
+ document.getElementById('noSessions').style.display = 'none';
1039
+ document.getElementById('sessionDetail').classList.add('active');
1040
+
1041
+ // Update title
1042
+ document.getElementById('detailTitle').textContent = \`session: \${sessionId.substring(0, 8)}...\`;
1043
+
1044
+ // Load session info
1045
+ updateSessionInfo(currentSession);
1046
+
1047
+ // Load conversation history
1048
+ await loadConversationHistory(currentSession);
1049
+ }
1050
+
1051
+ function showSessionsList() {
1052
+ document.getElementById('sessionDetail').classList.remove('active');
1053
+ document.querySelector('.filter-bar').style.display = 'flex';
1054
+ document.querySelector('.sessions-table').style.display = 'table';
1055
+ updateSessionsTable();
1056
+ currentSession = null;
1057
+ }
1058
+
1059
+ function updateSessionInfo(session) {
1060
+ const container = document.getElementById('sessionInfo');
1061
+
1062
+ container.innerHTML = \`
1063
+ <div class="info-item">
1064
+ <div class="info-label">session id</div>
1065
+ <div class="info-value">\${session.id}</div>
751
1066
  </div>
752
- \`).join('');
1067
+ <div class="info-item">
1068
+ <div class="info-label">project</div>
1069
+ <div class="info-value">\${session.project}</div>
1070
+ </div>
1071
+ <div class="info-item">
1072
+ <div class="info-label">messages</div>
1073
+ <div class="info-value">\${session.messageCount}</div>
1074
+ </div>
1075
+ <div class="info-item">
1076
+ <div class="info-label">tokens (estimated)</div>
1077
+ <div class="info-value">\${session.tokens.toLocaleString()}</div>
1078
+ </div>
1079
+ <div class="info-item">
1080
+ <div class="info-label">file size</div>
1081
+ <div class="info-value">\${formatBytes(session.fileSize)}</div>
1082
+ </div>
1083
+ <div class="info-item">
1084
+ <div class="info-label">created</div>
1085
+ <div class="info-value">\${new Date(session.created).toLocaleString()}</div>
1086
+ </div>
1087
+ <div class="info-item">
1088
+ <div class="info-label">last modified</div>
1089
+ <div class="info-value">\${new Date(session.lastModified).toLocaleString()}</div>
1090
+ </div>
1091
+ <div class="info-item">
1092
+ <div class="info-label">status</div>
1093
+ <div class="info-value status-\${session.status}">\${session.status}</div>
1094
+ </div>
1095
+ \`;
1096
+ }
1097
+
1098
+ async function loadConversationHistory(session) {
1099
+ try {
1100
+ const response = await fetch(\`/api/session/\${session.id}\`);
1101
+ const sessionData = await response.json();
1102
+
1103
+ const container = document.getElementById('conversationHistory');
1104
+
1105
+ if (!sessionData.messages || sessionData.messages.length === 0) {
1106
+ container.innerHTML = '<div style="padding: 20px; text-align: center; color: #7d8590;">no messages found</div>';
1107
+ return;
1108
+ }
1109
+
1110
+ container.innerHTML = sessionData.messages.map((message, index) => \`
1111
+ <div class="message">
1112
+ <div class="message-header">
1113
+ <div class="message-role \${message.role}">\${message.role}</div>
1114
+ <div class="message-time">message #\${index + 1}</div>
1115
+ </div>
1116
+ <div class="message-content">\${truncateContent(message.content || 'no content')}</div>
1117
+ </div>
1118
+ \`).join('');
1119
+
1120
+ } catch (error) {
1121
+ document.getElementById('conversationHistory').innerHTML =
1122
+ '<div style="padding: 20px; text-align: center; color: #f85149;">error loading conversation history</div>';
1123
+ console.error('Failed to load conversation history:', error);
1124
+ }
1125
+ }
1126
+
1127
+ function truncateContent(content, maxLength = 500) {
1128
+ if (typeof content !== 'string') return 'no content';
1129
+ if (content.length <= maxLength) return content;
1130
+ return content.substring(0, maxLength) + '...';
1131
+ }
1132
+
1133
+ function formatBytes(bytes) {
1134
+ if (bytes === 0) return '0 B';
1135
+ const k = 1024;
1136
+ const sizes = ['B', 'KB', 'MB', 'GB'];
1137
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1138
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
1139
+ }
1140
+
1141
+ function exportSessionCSV() {
1142
+ if (!currentSession) return;
1143
+
1144
+ // Create CSV content
1145
+ let csvContent = 'Session ID,Project,Message Count,Tokens,File Size,Created,Last Modified,Status\\n';
1146
+ 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\`;
1147
+
1148
+ csvContent += 'Message #,Role,Content\\n';
1149
+
1150
+ // Add conversation history if loaded
1151
+ fetch(\`/api/session/\${currentSession.id}\`)
1152
+ .then(response => response.json())
1153
+ .then(sessionData => {
1154
+ if (sessionData.messages) {
1155
+ sessionData.messages.forEach((message, index) => {
1156
+ const content = (message.content || 'no content').replace(/"/g, '""');
1157
+ csvContent += \`\${index + 1},"\${message.role}","\${content}"\\n\`;
1158
+ });
1159
+ }
1160
+
1161
+ // Download CSV
1162
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
1163
+ const link = document.createElement('a');
1164
+ const url = URL.createObjectURL(blob);
1165
+ link.setAttribute('href', url);
1166
+ link.setAttribute('download', \`claude-session-\${currentSession.id.substring(0, 8)}.csv\`);
1167
+ link.style.visibility = 'hidden';
1168
+ document.body.appendChild(link);
1169
+ link.click();
1170
+ document.body.removeChild(link);
1171
+ })
1172
+ .catch(error => {
1173
+ console.error('Failed to export CSV:', error);
1174
+ alert('Failed to export CSV. Please try again.');
1175
+ });
1176
+ }
1177
+
1178
+ function refreshSessionDetail() {
1179
+ if (currentSession) {
1180
+ loadConversationHistory(currentSession);
1181
+ }
753
1182
  }
754
1183
 
755
1184
  // Manual refresh function