UNPKG

ccusage-widget

Version:

A beautiful macOS desktop widget that displays your Claude Code usage statistics in real-time

209 lines 8.55 kB
"use strict"; const formatNumber = (num) => { if (num >= 1000000) { return (num / 1000000).toFixed(1) + 'M'; } else if (num >= 1000) { return (num / 1000).toFixed(1) + 'k'; } return num.toString(); }; const formatCost = (cost) => { return '$' + cost.toFixed(2); }; const formatDate = (dateString) => { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); if (diffHours < 1) { const diffMins = Math.floor(diffMs / (1000 * 60)); return diffMins + 'm ago'; } else if (diffHours < 24) { return diffHours + 'h ago'; } else { const diffDays = Math.floor(diffHours / 24); return diffDays + 'd ago'; } }; const formatModels = (models) => { return models.map(m => { if (m.includes('opus')) return 'Opus'; if (m.includes('sonnet')) return 'Sonnet'; if (m.includes('haiku')) return 'Haiku'; return m; }).join(', '); }; const formatBlockTime = (startTime, endTime) => { try { const start = new Date(startTime); const end = new Date(endTime); if (isNaN(start.getTime()) || isNaN(end.getTime())) { return 'Invalid time range'; } const formatTime = (date) => { return date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }); }; return `${formatTime(start)} - ${formatTime(end)}`; } catch (error) { console.error('Error formatting block time:', error); return 'Invalid time range'; } }; const formatDateLabel = (dateString) => { const date = new Date(dateString + 'T00:00:00'); const today = new Date(); today.setHours(0, 0, 0, 0); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); if (date.getTime() === today.getTime()) { return 'Today'; } else if (date.getTime() === yesterday.getTime()) { return 'Yesterday'; } else { return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); } }; async function loadUsageData(showLoading = true) { const loadingEl = document.getElementById('loading'); const errorEl = document.getElementById('error'); const statsEl = document.getElementById('usage-stats'); try { if (showLoading) { loadingEl.style.display = 'flex'; errorEl.style.display = 'none'; statsEl.style.display = 'none'; } const data = await window.ccusageAPI.getUsageData(); // Update today's stats if (data.today) { document.getElementById('today-tokens').textContent = formatNumber(data.today.tokens); document.getElementById('today-cost').textContent = formatCost(data.today.cost); document.getElementById('today-models').textContent = formatModels(data.today.models); // Update label if not today const todayDate = new Date().toISOString().split('T')[0]; if (data.today.date && data.today.date !== todayDate) { const dateLabel = document.querySelector('.today .stat-label'); if (dateLabel) { dateLabel.textContent = formatDateLabel(data.today.date); } } } else { document.getElementById('today-tokens').textContent = '0'; document.getElementById('today-cost').textContent = '$0.00'; document.getElementById('today-models').textContent = 'No usage'; } // Update this month's stats if (data.thisMonth) { document.getElementById('month-tokens').textContent = formatNumber(data.thisMonth.tokens); document.getElementById('month-cost').textContent = formatCost(data.thisMonth.cost); } else { document.getElementById('month-tokens').textContent = '0'; document.getElementById('month-cost').textContent = '$0.00'; } // Update total stats document.getElementById('total-tokens').textContent = formatNumber(data.total.tokens); document.getElementById('total-cost').textContent = formatCost(data.total.cost); // Update current block stats if (data.currentBlock) { document.getElementById('blocks-total').textContent = formatNumber(data.currentBlock.tokens); document.getElementById('blocks-cost').textContent = formatCost(data.currentBlock.cost); document.getElementById('blocks-time').textContent = formatBlockTime(data.currentBlock.startTime, data.currentBlock.endTime); } else { document.getElementById('blocks-total').textContent = '0'; document.getElementById('blocks-cost').textContent = '$0.00'; document.getElementById('blocks-time').textContent = 'No active block'; } // Update recent sessions const sessionsList = document.getElementById('sessions-list'); sessionsList.innerHTML = ''; console.log('Recent sessions data:', data.recentSessions); if (data.recentSessions && data.recentSessions.length > 0) { let sessionsAdded = 0; data.recentSessions.forEach((session) => { console.log('Processing session:', session); // More lenient check - just need a name if (session && session.name) { const sessionEl = document.createElement('div'); sessionEl.className = 'session-item'; // Format session name for display - capitalize first letter let displayName = session.name; if (displayName === 'Unknown Session') { displayName = 'session'; } // Capitalize first letter for better display displayName = displayName.charAt(0).toUpperCase() + displayName.slice(1); sessionEl.innerHTML = ` <span class="session-name" title="${displayName}">${displayName}</span> <span class="session-cost">${formatCost(session.cost || 0)}</span> `; sessionsList.appendChild(sessionEl); sessionsAdded++; } }); // If no valid sessions were added if (sessionsAdded === 0) { sessionsList.innerHTML = '<div style="text-align: center; color: #666;">No session data available</div>'; } } else { sessionsList.innerHTML = '<div style="text-align: center; color: #666;">No recent sessions</div>'; } // Update last updated time document.getElementById('last-updated').textContent = 'Updated ' + formatDate(data.lastUpdated); loadingEl.style.display = 'none'; statsEl.style.display = 'flex'; } catch (error) { console.error('Error loading usage data:', error); loadingEl.style.display = 'none'; errorEl.style.display = 'flex'; // Check if there's an error message in the data if (error && error.error) { document.getElementById('error-message').textContent = error.error; } else { document.getElementById('error-message').textContent = error instanceof Error ? error.message : 'Failed to load usage data'; } } } // Set up event listeners document.addEventListener('DOMContentLoaded', () => { // Initial load loadUsageData(); // Refresh button document.getElementById('refresh-btn').addEventListener('click', () => loadUsageData()); // Minimize button document.getElementById('minimize-btn').addEventListener('click', () => { window.close(); }); // Opacity slider const opacitySlider = document.getElementById('opacity-slider'); opacitySlider.addEventListener('input', (e) => { const opacity = parseInt(e.target.value) / 100; window.ccusageAPI.updateOpacity(opacity); }); // Listen for refresh events from main process window.ccusageAPI.onRefreshData(() => { loadUsageData(); }); // Auto-refresh every minute setInterval(() => loadUsageData(false), 60000); }); //# sourceMappingURL=renderer.js.map