Skip to content

Instantly share code, notes, and snippets.

@billygl
Last active May 29, 2026 02:29
Show Gist options
  • Select an option

  • Save billygl/92d716b2d253396d378d60b8c660b862 to your computer and use it in GitHub Desktop.

Select an option

Save billygl/92d716b2d253396d378d60b8c660b862 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Claude Usage Time Progress Bars (Live Updates)
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Adds live-updating time elapsed progress bars below token usage bars on Claude.ai settings
// @author You
// @match https://claude.ai/*
// @run-at document-idle
// @grant none
// ==/UserScript==
(function() {
'use strict';
let updateInterval = null;
const activeTrackers = {};
const CONFIGS = {
session: {
id: 'session',
totalMinutes: 5 * 60, // 5 hours
parseTime: (text) => {
let hrs = 0, mins = 0;
const hrMatch = text.match(/(\d+)\s*hr/i);
const minMatch = text.match(/(\d+)\s*min/i);
if (hrMatch) hrs = parseInt(hrMatch[1], 10);
if (minMatch) mins = parseInt(minMatch[1], 10);
return Date.now() + ((hrs * 60 + mins) * 60 * 1000);
}
},
weekly: {
id: 'weekly',
totalMinutes: 7 * 24 * 60, // 7 days
parseTime: (text) => {
if (text.includes('hr') || text.includes('min')) {
return CONFIGS.session.parseTime(text);
}
const match = text.match(/Resets\s+([A-Za-z]+)\s+(\d{1,2}):(\d{2})\s+(AM|PM)/i);
if (!match) return Date.now();
const [ , dayStr, hrStr, minStr, ampm ] = match;
const dayLower = dayStr.toLowerCase();
let hours = parseInt(hrStr, 10);
const minutes = parseInt(minStr, 10);
if (ampm.toUpperCase() === 'PM' && hours < 12) hours += 12;
if (ampm.toUpperCase() === 'AM' && hours === 12) hours = 0;
const now = new Date();
let dayDiff = 0;
if (dayLower.startsWith('tod')) {
dayDiff = 0;
} else if (dayLower.startsWith('tom')) {
dayDiff = 1;
} else {
const days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
const targetDay = days.findIndex(d => dayLower.startsWith(d));
if (targetDay !== -1) {
dayDiff = targetDay - now.getDay();
if (dayDiff < 0) dayDiff += 7;
else if (dayDiff === 0) {
const tempTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, 0).getTime();
if (now.getTime() > tempTime) dayDiff += 7;
}
}
}
return new Date(now.getFullYear(), now.getMonth(), now.getDate() + dayDiff, hours, minutes, 0).getTime();
}
}
};
function updateAllBars() {
const trackerIds = Object.keys(activeTrackers);
if (trackerIds.length === 0 && updateInterval) {
clearInterval(updateInterval);
updateInterval = null;
return;
}
trackerIds.forEach(id => {
const timeRow = document.getElementById(`tm-time-row-${id}`);
if (!timeRow) {
delete activeTrackers[id];
return;
}
const targetTime = activeTrackers[id];
const config = CONFIGS[id];
const progressBar = document.getElementById(`tm-time-bar-${id}`);
const progressText = document.getElementById(`tm-time-text-${id}`);
const timeProgressRole = timeRow.querySelector('[role="progressbar"]');
const remainingMs = targetTime - Date.now();
const remainingMinutes = Math.max(0, remainingMs / 1000 / 60);
let elapsedMinutes = config.totalMinutes - remainingMinutes;
if (elapsedMinutes < 0) elapsedMinutes = 0;
if (elapsedMinutes > config.totalMinutes) elapsedMinutes = config.totalMinutes;
const elapsedPercent = ((elapsedMinutes / config.totalMinutes) * 100).toFixed(1);
const progressLabel = `${elapsedPercent}% elapsed`;
if (progressBar && progressText && timeProgressRole && progressText.textContent !== progressLabel) {
progressBar.style.width = `${elapsedPercent}%`;
timeProgressRole.setAttribute('aria-valuenow', elapsedPercent);
progressText.textContent = progressLabel;
}
});
}
function injectTrackers() {
const usageRows = document.querySelectorAll('div.flex.w-full.flex-row.flex-wrap.items-center.justify-between');
usageRows.forEach(row => {
const titleSpan = row.querySelector('span.text-primary');
if (!titleSpan) return;
const title = titleSpan.textContent.trim();
const resetSpan = row.querySelector('span.text-secondary');
if (!resetSpan || !resetSpan.textContent.includes('Resets')) return;
const rightContainer = row.querySelector('div.pl-6');
if (!rightContainer) return;
let config;
if (title === 'Current session') {
config = CONFIGS.session;
} else if (title === 'All models') {
config = CONFIGS.weekly;
} else {
return;
}
if (!activeTrackers[config.id]) {
activeTrackers[config.id] = config.parseTime(resetSpan.textContent);
}
let timeRow = document.getElementById(`tm-time-row-${config.id}`);
if (!timeRow) {
const wrapper = document.createElement('div');
wrapper.id = `tm-time-wrapper-${config.id}`;
wrapper.className = 'flex flex-col flex-1 justify-center gap-1';
rightContainer.parentNode.insertBefore(wrapper, rightContainer);
wrapper.appendChild(rightContainer);
rightContainer.classList.remove('flex-1');
timeRow = document.createElement('div');
timeRow.id = `tm-time-row-${config.id}`;
timeRow.className = 'flex items-center gap-3 pl-6 md:max-w-xl';
timeRow.innerHTML = `
<div class="min-w-[200px] flex-1">
<div role="progressbar" aria-label="Time Elapsed" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="relative flex h-2 w-full items-center overflow-hidden rounded-full bg-alpha-2">
<div id="tm-time-bar-${config.id}" class="h-full rounded-full transition-all bg-fill-accent" style="width: 0%;"></div>
</div>
</div>
<span id="tm-time-text-${config.id}" class="min-w-[5.5rem] whitespace-nowrap text-right text-footnote text-secondary">0.0% elapsed</span>
`;
wrapper.appendChild(timeRow);
}
});
updateAllBars();
if (Object.keys(activeTrackers).length > 0 && !updateInterval) {
updateInterval = setInterval(updateAllBars, 60000);
}
}
const observer = new MutationObserver(() => {
injectTrackers();
});
// Fallback to documentElement if body somehow still isn't ready
const targetNode = document.body || document.documentElement;
observer.observe(targetNode, {
childList: true,
subtree: true
});
})();
@billygl

billygl commented May 29, 2026

Copy link
Copy Markdown
Author

you will have something like this using Tampermonkey
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment