Files
claude-use-extension/content.js
Carlos Narro ed2d44fc13 first commit
2026-03-18 23:11:18 +01:00

649 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Claude Usage Tracker - Content Script
(function() {
'use strict';
// Días en español - índice 0 = Domingo
const DAYS_ES = ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'];
const DAYS_ES_DISPLAY = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'];
const DAYS_FULL_ES = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];
// Boost 2× promotion config (March 2026)
// Peak hours: 8AM - 2PM ET (Eastern Time, UTC-4 in March)
// Outside peak = 2× boost
const BOOST_END_DATE = new Date('2026-03-28T06:59:00Z'); // March 27, 11:59 PM PT = March 28, 6:59 AM UTC
const PEAK_START_UTC = 12; // 8AM ET = 12:00 UTC
const PEAK_END_UTC = 18; // 2PM ET = 18:00 UTC
// Session tracking
let sessionStartTime = null;
const SESSION_KEY = 'claude_usage_tracker_session';
function initSession() {
const stored = localStorage.getItem(SESSION_KEY);
if (stored) {
const data = JSON.parse(stored);
const storedDate = new Date(data.startTime);
const now = new Date();
if (storedDate.toDateString() === now.toDateString() &&
(now - storedDate) < 4 * 60 * 60 * 1000) {
sessionStartTime = new Date(data.startTime);
} else {
sessionStartTime = new Date();
saveSession();
}
} else {
sessionStartTime = new Date();
saveSession();
}
}
function saveSession() {
localStorage.setItem(SESSION_KEY, JSON.stringify({
startTime: sessionStartTime.toISOString()
}));
}
function formatTime(date) {
return date.toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
});
}
/**
* Calcula el estado del boost 2×
* Retorna { isActive, isBoostPeriod, nextChangeIn, nextChangeTime, promotionEnded }
*/
function getBoostStatus() {
const now = new Date();
// Verificar si la promoción ha terminado
if (now >= BOOST_END_DATE) {
return { isActive: false, isBoostPeriod: false, promotionEnded: true };
}
const utcHour = now.getUTCHours();
const utcMinutes = now.getUTCMinutes();
// Peak hours: 12:00 - 18:00 UTC (8AM - 2PM ET)
const isPeakHour = utcHour >= PEAK_START_UTC && utcHour < PEAK_END_UTC;
const isBoostPeriod = !isPeakHour; // Boost es cuando NO es hora pico
// Calcular tiempo hasta el próximo cambio
let nextChangeHour;
if (isPeakHour) {
// Estamos en pico, próximo cambio es cuando termine (18:00 UTC)
nextChangeHour = PEAK_END_UTC;
} else {
// Estamos en boost, próximo cambio es cuando empiece el pico
if (utcHour < PEAK_START_UTC) {
nextChangeHour = PEAK_START_UTC;
} else {
// Después de las 18:00 UTC, el próximo pico es mañana a las 12:00 UTC
nextChangeHour = PEAK_START_UTC + 24;
}
}
const minutesUntilChange = (nextChangeHour * 60) - (utcHour * 60 + utcMinutes);
const hoursUntilChange = Math.floor(minutesUntilChange / 60);
const minsUntilChange = minutesUntilChange % 60;
// Calcular hora local del próximo cambio
const nextChangeTime = new Date(now);
nextChangeTime.setUTCHours(nextChangeHour % 24, 0, 0, 0);
if (nextChangeHour >= 24) {
nextChangeTime.setDate(nextChangeTime.getDate() + 1);
}
return {
isActive: true,
isBoostPeriod,
nextChangeIn: `${hoursUntilChange}h ${minsUntilChange}m`,
nextChangeTime: formatTime(nextChangeTime),
promotionEnded: false
};
}
function formatDuration(ms) {
const hours = Math.floor(ms / (1000 * 60 * 60));
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `${hours}h ${minutes}m`;
}
return `${minutes}m`;
}
const SESSION_DURATION_HOURS = 5; // Las sesiones de Claude duran 5 horas
/**
* Parsea el tiempo restante desde texto como "Se restablece en 3 h 44 min"
* Retorna el tiempo restante en minutos
*/
function parseTimeRemaining(text) {
const match = text.match(/(\d+)\s*h\s*(\d+)\s*min/i);
if (match) {
return parseInt(match[1]) * 60 + parseInt(match[2]);
}
// Solo minutos: "Se restablece en 44 min"
const minMatch = text.match(/(\d+)\s*min/i);
if (minMatch) {
return parseInt(minMatch[1]);
}
// Solo horas: "Se restablece en 3 h"
const hourMatch = text.match(/(\d+)\s*h/i);
if (hourMatch) {
return parseInt(hourMatch[1]) * 60;
}
return 0;
}
/**
* Parsea la información de la página real de Claude
* Busca los progressbar con aria-valuenow y el texto "Se restablece..."
*/
function parsePageData() {
const data = {
sessionUsage: 0,
sessionResetText: '',
sessionTimeRemainingMin: 0,
sessionStartTime: null,
sessionEndTime: null,
weeklyUsage: 0,
weeklyResetText: '',
resetDayIndex: -1,
resetHour: '',
daysUntilReset: 0,
daysPassed: 0
};
// Buscar todas las secciones con progressbar
const sections = document.querySelectorAll('section');
for (const section of sections) {
const rows = section.querySelectorAll('.flex.flex-row');
for (const row of rows) {
const labelEl = row.querySelector('p.font-base.text-text-100');
const resetTextEl = row.querySelector('p.font-base.text-text-400');
const progressBar = row.querySelector('[role="progressbar"]');
if (!labelEl || !progressBar) continue;
const label = labelEl.textContent.trim();
const usage = parseFloat(progressBar.getAttribute('aria-valuenow')) || 0;
const resetText = resetTextEl ? resetTextEl.textContent.trim() : '';
if (label === 'Sesión actual') {
data.sessionUsage = usage;
data.sessionResetText = resetText;
// Calcular tiempo restante y horas de inicio/fin
data.sessionTimeRemainingMin = parseTimeRemaining(resetText);
if (data.sessionTimeRemainingMin > 0) {
const now = new Date();
const sessionDurationMin = SESSION_DURATION_HOURS * 60;
const elapsedMin = sessionDurationMin - data.sessionTimeRemainingMin;
// Hora de inicio = ahora - tiempo transcurrido
data.sessionStartTime = new Date(now.getTime() - elapsedMin * 60 * 1000);
// Hora de fin = ahora + tiempo restante
data.sessionEndTime = new Date(now.getTime() + data.sessionTimeRemainingMin * 60 * 1000);
}
} else if (label === 'Todos los modelos') {
data.weeklyUsage = usage;
data.weeklyResetText = resetText;
// Parsear "Se restablece vie, 4:59" para obtener día y hora
const resetMatch = resetText.match(/Se restablece\s+(\w+),?\s*([\d:]+)/i);
if (resetMatch) {
const dayAbbr = resetMatch[1].toLowerCase();
data.resetHour = resetMatch[2];
// Encontrar el índice del día
data.resetDayIndex = DAYS_ES.findIndex(d => d === dayAbbr || d.startsWith(dayAbbr.substring(0, 3)));
}
}
}
}
// Calcular días hasta el reinicio y días pasados
if (data.resetDayIndex >= 0) {
const now = new Date();
const todayIndex = now.getDay(); // 0 = Domingo, 1 = Lunes, etc.
// Calcular días hasta el reinicio
let daysUntil = data.resetDayIndex - todayIndex;
if (daysUntil <= 0) {
daysUntil += 7;
}
// Si es el mismo día pero ya pasó la hora, es la próxima semana
if (daysUntil === 7 || (daysUntil === 0 && data.resetHour)) {
const [resetH, resetM] = data.resetHour.split(':').map(Number);
if (now.getHours() > resetH || (now.getHours() === resetH && now.getMinutes() >= resetM)) {
daysUntil = 7;
}
}
data.daysUntilReset = daysUntil;
data.daysPassed = 7 - daysUntil;
}
return data;
}
/**
* Calcula el desglose diario basado en los datos reales
* Usa HORAS transcurridas para mayor precisión
*/
function calculateDailyUsage(pageData) {
const { weeklyUsage, resetDayIndex, resetHour } = pageData;
const now = new Date();
const todayIndex = now.getDay();
const currentHour = now.getHours() + now.getMinutes() / 60;
// Calcular el momento exacto del último reinicio
const [resetH, resetM] = (resetHour || '5:00').split(':').map(Number);
// Encontrar la fecha del último reinicio
let lastResetDate = new Date(now);
let daysSinceReset = todayIndex - resetDayIndex;
if (daysSinceReset < 0) daysSinceReset += 7;
if (daysSinceReset === 0) {
// Es el día de reinicio, ¿ya pasó la hora?
if (now.getHours() < resetH || (now.getHours() === resetH && now.getMinutes() < resetM)) {
daysSinceReset = 7; // Aún no ha reiniciado, usar el de la semana pasada
}
}
lastResetDate.setDate(now.getDate() - daysSinceReset);
lastResetDate.setHours(resetH, resetM || 0, 0, 0);
// Horas totales transcurridas desde el reinicio
const msElapsed = now - lastResetDate;
const hoursElapsed = msElapsed / (1000 * 60 * 60);
const totalWeekHours = 7 * 24; // 168 horas
// Porcentaje de la semana transcurrido
const weekProgressPercent = (hoursElapsed / totalWeekHours) * 100;
// Uso ideal hasta ahora (proporcional a las horas transcurridas)
const idealUsedByNow = weekProgressPercent;
// Uso ideal por día (para referencia visual)
const idealDailyPercent = 100 / 7;
// Promedio real de uso por hora
const avgUsagePerHour = hoursElapsed > 0 ? weeklyUsage / hoursElapsed : 0;
const idealUsagePerHour = 100 / totalWeekHours;
// La semana empieza el mismo día del reinicio (ej: viernes)
// El primer día es el día del reinicio, desde la hora de reinicio
const startDayIndex = resetDayIndex;
// Calcular la posición del día actual dentro de la semana de facturación
// Posición 0 = día del reinicio, posición 1 = día siguiente, etc.
let todayPositionInWeek = todayIndex - resetDayIndex;
if (todayPositionInWeek < 0) todayPositionInWeek += 7;
const dailyBreakdown = [];
for (let i = 0; i < 7; i++) {
const dayIndex = (startDayIndex + i) % 7;
const isToday = (i === todayPositionInWeek);
const isPast = (i < todayPositionInWeek);
const isFuture = (i > todayPositionInWeek);
// Calcular cuántas horas de este día han transcurrido
let dayHoursElapsed = 0;
if (isPast) {
// Día completo ya pasado
if (i === 0) {
// Primer día (día del reinicio): desde hora de reinicio hasta medianoche
dayHoursElapsed = 24 - (resetH + (resetM || 0) / 60);
} else {
dayHoursElapsed = 24;
}
} else if (isToday) {
if (i === 0) {
// Hoy es el día del reinicio: desde hora de reinicio hasta ahora
dayHoursElapsed = currentHour - (resetH + (resetM || 0) / 60);
if (dayHoursElapsed < 0) dayHoursElapsed = 0;
} else {
// Día actual normal: desde medianoche hasta ahora
dayHoursElapsed = currentHour;
}
}
// Días futuros: dayHoursElapsed = 0
// Calcular horas totales del día (para el porcentaje de llenado)
let dayTotalHours = 24;
if (i === 0) {
// Primer día: solo cuenta desde la hora de reinicio
dayTotalHours = 24 - (resetH + (resetM || 0) / 60);
}
// Porcentaje de llenado del día (0-100%)
const dayFillPercent = dayTotalHours > 0 ? (dayHoursElapsed / dayTotalHours) * 100 : 0;
// Uso asignado a este día (promedio distribuido)
let usage = 0;
if (dayHoursElapsed > 0 && hoursElapsed > 0) {
// Distribuir el uso total proporcionalmente a las horas transcurridas
usage = (dayHoursElapsed / hoursElapsed) * weeklyUsage;
}
// Estado basado en si el uso del día está dentro del ideal
let status = 'pending';
if (dayHoursElapsed > 0) {
const idealForThisDay = (dayHoursElapsed / 24) * idealDailyPercent;
status = usage <= idealForThisDay * 1.1 ? 'good' :
usage <= idealForThisDay * 1.5 ? 'warning' : 'danger';
}
// Offset para el primer día (horas antes del reinicio que no cuentan)
const dayStartOffsetPercent = (i === 0) ? ((resetH + (resetM || 0) / 60) / 24) * 100 : 0;
dailyBreakdown.push({
dayIndex,
dayName: DAYS_ES_DISPLAY[dayIndex],
dayFullName: DAYS_FULL_ES[dayIndex],
isPast,
isToday,
isFuture,
isResetDay: i === 0,
usage,
idealUsage: idealDailyPercent,
dayFillPercent, // % del día transcurrido (para llenar la barra)
dayHoursElapsed,
dayTotalHours,
dayStartOffsetPercent, // % del día antes del reinicio (hueco a la izquierda)
status
});
}
// Calcular fecha estimada de fin del uso
const usageRatePerHour = hoursElapsed > 0 ? weeklyUsage / hoursElapsed : 0;
const hoursToReach100 = usageRatePerHour > 0 ? (100 - weeklyUsage) / usageRatePerHour : Infinity;
const hoursUntilReset = totalWeekHours - hoursElapsed;
let estimatedEndDate = null;
let willRunOut = false;
if (usageRatePerHour > 0 && hoursToReach100 < hoursUntilReset) {
// Se acabará antes del reinicio
willRunOut = true;
estimatedEndDate = new Date(now.getTime() + hoursToReach100 * 60 * 60 * 1000);
} else {
// Llegará al reinicio con uso disponible
estimatedEndDate = new Date(lastResetDate.getTime() + 7 * 24 * 60 * 60 * 1000);
}
// Determinar estado con más granularidad
const diffPercent = weeklyUsage - idealUsedByNow;
let status = 'perfect'; // Por debajo del ideal
if (diffPercent > 0 && diffPercent <= 5) status = 'good'; // Hasta 5% por encima
else if (diffPercent > 5 && diffPercent <= 15) status = 'warning'; // 5-15% por encima
else if (diffPercent > 15) status = 'danger'; // Más del 15% por encima
return {
dailyBreakdown,
idealDailyPercent,
idealUsedByNow,
hoursElapsed,
avgUsagePerHour,
idealUsagePerHour,
hoursUntilReset,
estimatedEndDate,
willRunOut,
hoursToReach100,
status
};
}
function createWeeklyTracker(pageData, dailyData) {
const container = document.createElement('div');
container.className = 'claude-usage-tracker-container';
container.id = 'claude-usage-tracker';
const usagePercent = pageData.weeklyUsage;
const resetDayName = DAYS_FULL_ES[pageData.resetDayIndex] || 'próximo reinicio';
const boostStatus = getBoostStatus();
container.innerHTML = `
${boostStatus.isActive ? `
<div class="boost-indicator ${boostStatus.isBoostPeriod ? 'active' : 'inactive'}">
<span class="boost-badge">${boostStatus.isBoostPeriod ? '⚡ 2×' : '1×'}</span>
<span class="boost-text">${boostStatus.isBoostPeriod ? 'Boost activo' : 'Horario normal'}</span>
<span class="boost-next">${boostStatus.isBoostPeriod ? '1× a las' : '2× a las'} ${boostStatus.nextChangeTime} (${boostStatus.nextChangeIn})</span>
</div>
` : ''}
<div class="claude-usage-tracker-title">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
Uso semanal por días
</div>
<div class="weekly-progress-container">
${dailyData.dailyBreakdown.map(day => `
<div class="day-segment ${day.isToday ? 'today' : ''} ${day.isFuture ? 'future' : ''} ${day.isResetDay ? 'reset-day' : ''}">
<div class="day-tooltip">
${day.dayFullName}: ${day.usage.toFixed(1)}% usado
${day.isToday ? ` (${day.dayHoursElapsed.toFixed(1)}h transcurridas)` : day.isPast ? ' (completo)' : ''}
${day.isResetDay ? `<br>Reinicio a las ${pageData.resetHour}` : ''}
<br>Ideal para ${day.dayHoursElapsed.toFixed(0)}h: ${((day.dayHoursElapsed / 24) * day.idealUsage).toFixed(1)}%
</div>
<div class="day-bar">
${day.isResetDay ? `<div class="day-bar-offset" style="width: ${day.dayStartOffsetPercent}%"></div>` : ''}
<div class="day-bar-bg" style="left: ${day.dayStartOffsetPercent}%; width: ${(100 - day.dayStartOffsetPercent) * day.dayFillPercent / 100}%"></div>
<div class="day-bar-fill ${day.status === 'good' ? 'under-budget' : day.status === 'warning' ? 'on-track' : day.status === 'danger' ? 'over-budget' : ''}"
style="left: ${day.dayStartOffsetPercent}%; width: ${(100 - day.dayStartOffsetPercent) * day.dayFillPercent / 100}%">
</div>
</div>
<span class="day-label">${day.dayName}</span>
</div>
`).join('')}
</div>
<div class="progress-summary">
<div class="progress-ideal">
<span class="progress-ideal-indicator ${dailyData.status}"></span>
<span>${dailyData.status === 'perfect' ? '✓ Perfecto' : dailyData.status === 'good' ? '✓ Vas bien' : dailyData.status === 'warning' ? '⚠ Atención' : '✗ Excedido'}</span>
</div>
<div class="usage-values">
<div class="usage-value">
<span class="usage-value-label">Usado</span>
<span class="usage-value-number used">${usagePercent.toFixed(1)}%</span>
</div>
<div class="usage-value">
<span class="usage-value-label">Ideal</span>
<span class="usage-value-number ideal">${dailyData.idealUsedByNow.toFixed(1)}%</span>
</div>
<div class="usage-value">
<span class="usage-value-label">Diferencia</span>
<span class="usage-value-number ${usagePercent <= dailyData.idealUsedByNow ? 'under' : 'over'}">
${usagePercent <= dailyData.idealUsedByNow ? '-' : '+'}${Math.abs(usagePercent - dailyData.idealUsedByNow).toFixed(1)}%
</span>
</div>
</div>
</div>
<div class="estimated-end ${dailyData.willRunOut ? 'warning' : ''}">
<span class="estimated-end-label">${dailyData.willRunOut ? '⚠ Se agotará el' : '📅 Reinicio el'}</span>
<span class="estimated-end-date">${dailyData.estimatedEndDate ? dailyData.estimatedEndDate.toLocaleDateString('es-ES', { weekday: 'long', day: 'numeric', month: 'short' }) : '--'}</span>
<span class="estimated-end-time">${dailyData.estimatedEndDate ? dailyData.estimatedEndDate.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : '--:--'}</span>
${dailyData.willRunOut ? `<span class="estimated-end-hours">(en ~${Math.round(dailyData.hoursToReach100)}h)</span>` : ''}
</div>
<div class="usage-info-row">
<div class="usage-stat">
<span class="usage-stat-label">Tiempo transcurrido</span>
<span class="usage-stat-value">${Math.floor(dailyData.hoursElapsed)}h ${Math.round((dailyData.hoursElapsed % 1) * 60)}m</span>
</div>
<div class="usage-stat">
<span class="usage-stat-label">Uso/día real vs ideal</span>
<span class="usage-stat-value ${dailyData.avgUsagePerHour <= dailyData.idealUsagePerHour * 1.1 ? 'good' : dailyData.avgUsagePerHour <= dailyData.idealUsagePerHour * 1.5 ? 'warning' : 'danger'}">
${(dailyData.avgUsagePerHour * 24).toFixed(2)}% / ${(dailyData.idealUsagePerHour * 24).toFixed(2)}%
</span>
</div>
<div class="usage-stat">
<span class="usage-stat-label">Horas restantes</span>
<span class="usage-stat-value">${Math.round((168 - dailyData.hoursElapsed))}h</span>
</div>
</div>
<div class="session-info-container">
<div class="session-info-title">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
Sesión actual
</div>
<div class="session-sliders">
<div class="session-slider-row">
<span class="session-slider-label">⏱ Tiempo</span>
<div class="session-slider-track">
<div class="session-slider-fill time" style="width: ${100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100}%"></div>
<div class="session-slider-marker" style="left: ${100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100}%"></div>
</div>
<span class="session-slider-value">${Math.round(100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100)}%</span>
</div>
<div class="session-slider-row">
<span class="session-slider-label">📊 Uso</span>
<div class="session-slider-track">
<div class="session-slider-fill usage ${pageData.sessionUsage <= (100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100) ? 'good' : 'over'}" style="width: ${pageData.sessionUsage}%"></div>
<div class="session-slider-marker" style="left: ${pageData.sessionUsage}%"></div>
</div>
<span class="session-slider-value">${pageData.sessionUsage}%</span>
</div>
</div>
<div class="session-info-grid">
<div class="session-info-item">
<span class="session-info-label">Inicio</span>
<span class="session-info-value">${pageData.sessionStartTime ? formatTime(pageData.sessionStartTime) : '--:--'}</span>
</div>
<div class="session-info-item">
<span class="session-info-label">Fin</span>
<span class="session-info-value">${pageData.sessionEndTime ? formatTime(pageData.sessionEndTime) : '--:--'}</span>
</div>
<div class="session-info-item">
<span class="session-info-label">Restante</span>
<span class="session-info-value">${pageData.sessionTimeRemainingMin > 0 ? Math.floor(pageData.sessionTimeRemainingMin / 60) + 'h ' + (pageData.sessionTimeRemainingMin % 60) + 'm' : '--'}</span>
</div>
</div>
</div>
<div class="reset-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
Reinicio semanal: ${resetDayName} a las ${pageData.resetHour} (en ${pageData.daysUntilReset} día${pageData.daysUntilReset !== 1 ? 's' : ''})
</div>
`;
return container;
}
function injectTracker() {
// Remove existing tracker if present
const existing = document.getElementById('claude-usage-tracker');
if (existing) {
existing.remove();
}
initSession();
// Parsear datos reales de la página
const pageData = parsePageData();
// Si no encontramos datos, no inyectar nada
if (pageData.weeklyUsage === 0 && pageData.resetDayIndex === -1) {
console.log('[Claude Usage Tracker] No se encontraron datos de uso en la página');
return;
}
const dailyData = calculateDailyUsage(pageData);
const tracker = createWeeklyTracker(pageData, dailyData);
// Insertar justo DEBAJO del título principal h1
const mainContent = document.querySelector('main');
if (!mainContent) {
console.log('[Claude Usage Tracker] No se encontró el main');
return;
}
// Buscar el h1 del título principal "Ajustes" (visible en móvil)
const mobileH1 = mainContent.querySelector('h1.font-heading');
if (mobileH1) {
// Insertar justo después del h1
mobileH1.after(tracker);
} else {
// Fallback: insertar al principio del main content
const contentDiv = mainContent.querySelector('.pb-8') || mainContent.querySelector('div > div');
if (contentDiv) {
contentDiv.prepend(tracker);
} else {
mainContent.prepend(tracker);
}
}
console.log('[Claude Usage Tracker] Inyectado en la parte superior', {
pageData,
dailyData
});
}
// Wait for page to fully load
function waitForContent() {
const observer = new MutationObserver((mutations, obs) => {
const main = document.querySelector('main');
const hasContent = main && main.textContent.length > 100;
if (hasContent) {
obs.disconnect();
setTimeout(injectTracker, 500); // Small delay to ensure content is rendered
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Timeout fallback
setTimeout(() => {
observer.disconnect();
injectTracker();
}, 3000);
}
// Initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', waitForContent);
} else {
waitForContent();
}
// Update session time periodically
setInterval(() => {
const sessionValue = document.querySelector('.session-info-value');
if (sessionValue) {
injectTracker();
}
}, 60000); // Update every minute
})();