Skip to content

Instantly share code, notes, and snippets.

@cozuya
Created March 12, 2026 01:01
Show Gist options
  • Select an option

  • Save cozuya/44c9ebcd5be2ef61461c8e29acafd478 to your computer and use it in GitHub Desktop.

Select an option

Save cozuya/44c9ebcd5be2ef61461c8e29acafd478 to your computer and use it in GitHub Desktop.
Claude code usage page tampermonkey script to show more details/do some math
// ==UserScript==
// @name Claude Usage Budget Helper
// @namespace http://tampermonkey.net/
// @version 2026-03-12
// @description Adds weekly usage pace, history, and a compact summary to the Claude usage page.
// @author You
// @match https://claude.ai/settings/usage*
// @icon https://www.google.com/s2/favicons?sz=64&domain=claude.ai
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
"use strict";
var BADGE_ID = "tm-claude-usage-budget-badge";
var STYLE_ID = "tm-claude-usage-budget-style";
var SUMMARY_ID = "tm-claude-usage-summary";
var HISTORY_STORAGE_KEY = "tm-claude-usage-all-models-history";
var MAX_HISTORY_ENTRIES = 60;
var HISTORY_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
var DAY_MS = 24 * 60 * 60 * 1000;
var WEEK_MS = 7 * DAY_MS;
var WEEKDAYS = {
Sun: 0,
Mon: 1,
Tue: 2,
Wed: 3,
Thu: 4,
Fri: 5,
Sat: 6,
};
function injectStyles() {
if (document.getElementById(STYLE_ID)) {
return;
}
var style = document.createElement("style");
style.id = STYLE_ID;
style.textContent = [
"#" + BADGE_ID + "{",
"display:inline-flex;",
"align-items:center;",
"padding:2px 8px;",
"border-radius:999px;",
"font-size:12px;",
"line-height:1.5;",
"font-weight:600;",
"white-space:nowrap;",
"background:rgba(16,163,127,0.12);",
"color:rgb(16,124,93);",
"border:1px solid rgba(16,163,127,0.25);",
"}",
"#" + BADGE_ID + '[data-tone="warn"]{',
"background:rgba(245,158,11,0.12);",
"color:rgb(180,83,9);",
"border-color:rgba(245,158,11,0.25);",
"}",
"#" + BADGE_ID + '[data-tone="danger"]{',
"background:rgba(239,68,68,0.12);",
"color:rgb(185,28,28);",
"border-color:rgba(239,68,68,0.25);",
"}",
"#" + SUMMARY_ID + "{",
"position:fixed;",
"top:16px;",
"left:50%;",
"transform:translateX(-50%);",
"z-index:9999;",
"display:flex;",
"align-items:center;",
"justify-content:center;",
"flex-wrap:wrap;",
"gap:8px;",
"width:min(980px, calc(100vw - 32px));",
"padding:10px 12px;",
"border-radius:999px;",
"background:rgba(255,255,255,0.92);",
"backdrop-filter:blur(10px);",
"color:rgb(15,23,42);",
"border:1px solid rgba(15,23,42,0.08);",
"box-shadow:0 16px 32px -20px rgba(15,23,42,0.35);",
"font-family:ui-sans-serif, system-ui, sans-serif;",
"}",
"@media (prefers-color-scheme: dark){",
"#" + SUMMARY_ID + "{",
"background:rgba(17,24,39,0.88);",
"color:rgb(241,245,249);",
"border-color:rgba(255,255,255,0.08);",
"box-shadow:0 16px 32px -20px rgba(0,0,0,0.55);",
"}",
"}",
"#" + SUMMARY_ID + " .tm-summary-title{",
"font-size:11px;",
"font-weight:700;",
"letter-spacing:0.08em;",
"text-transform:uppercase;",
"opacity:0.7;",
"margin-right:4px;",
"}",
"#" + SUMMARY_ID + " .tm-summary-chip{",
"display:inline-flex;",
"align-items:center;",
"padding:4px 10px;",
"border-radius:999px;",
"background:rgba(15,23,42,0.05);",
"font-size:12px;",
"font-weight:600;",
"line-height:1.4;",
"}",
"@media (prefers-color-scheme: dark){",
"#" + SUMMARY_ID + " .tm-summary-chip{",
"background:rgba(255,255,255,0.08);",
"}",
"}",
"#" + SUMMARY_ID + " .tm-good{",
"color:rgb(22,163,74);",
"}",
"#" + SUMMARY_ID + " .tm-warn{",
"color:rgb(217,119,6);",
"}",
"#" + SUMMARY_ID + " .tm-bad{",
"color:rgb(220,38,38);",
"}",
].join("");
document.head.appendChild(style);
}
function normalizeText(text) {
return (text || "").replace(/\s+/g, " ").trim();
}
function clamp(value, minimum, maximum) {
return Math.min(maximum, Math.max(minimum, value));
}
function roundToTenths(value) {
return Math.round(value * 10) / 10;
}
function getTone(perDayBudget) {
if (perDayBudget >= 10) {
return "ok";
}
if (perDayBudget >= 5) {
return "warn";
}
return "danger";
}
function getSummaryTone(metrics) {
if (metrics.paceDelta >= 5) {
return "tm-good";
}
if (metrics.paceDelta >= 0) {
return "tm-warn";
}
return "tm-bad";
}
function formatSignedPercent(value) {
var roundedValue = roundToTenths(Math.abs(value));
var prefix = value >= 0 ? "+" : "-";
return prefix + roundedValue.toFixed(1) + "%";
}
function formatDuration(milliseconds) {
if (milliseconds <= 0) {
return "reset imminent";
}
var totalHours = Math.floor(milliseconds / (60 * 60 * 1000));
var days = Math.floor(totalHours / 24);
var hours = totalHours % 24;
if (days > 0) {
return days + "d " + hours + "h left";
}
var minutes = Math.floor((milliseconds % (60 * 60 * 1000)) / (60 * 1000));
return hours + "h " + minutes + "m left";
}
function formatSnapshotLabel(timestamp, now) {
var snapshotDate = new Date(timestamp);
var sameDay = snapshotDate.toDateString() === new Date(now).toDateString();
var timeLabel = snapshotDate.toLocaleTimeString([], {
hour: "numeric",
minute: "2-digit",
});
if (sameDay) {
return timeLabel;
}
return (
snapshotDate.toLocaleDateString([], {
month: "short",
day: "numeric",
}) +
" " +
timeLabel
);
}
function loadHistory() {
try {
var rawHistory = window.localStorage.getItem(HISTORY_STORAGE_KEY);
if (!rawHistory) {
return [];
}
var parsedHistory = JSON.parse(rawHistory);
if (!Array.isArray(parsedHistory)) {
return [];
}
var validHistory = [];
for (var index = 0; index < parsedHistory.length; index += 1) {
var entry = parsedHistory[index];
if (
!entry ||
typeof entry.recordedAt !== "number" ||
typeof entry.remainingPercent !== "number" ||
typeof entry.resetAt !== "number"
) {
continue;
}
validHistory.push({
recordedAt: entry.recordedAt,
remainingPercent: entry.remainingPercent,
resetAt: entry.resetAt,
});
}
return validHistory.slice(-MAX_HISTORY_ENTRIES);
} catch (error) {
return [];
}
}
function saveHistory(history) {
try {
window.localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(history.slice(-MAX_HISTORY_ENTRIES)));
} catch (error) {
return;
}
}
function recordSnapshot(remainingPercent, resetDate) {
var history = loadHistory();
var snapshot = {
recordedAt: Date.now(),
remainingPercent: roundToTenths(remainingPercent),
resetAt: resetDate.getTime(),
};
var lastSnapshot = history.length > 0 ? history[history.length - 1] : null;
if (
!lastSnapshot ||
lastSnapshot.resetAt !== snapshot.resetAt ||
Math.abs(lastSnapshot.remainingPercent - snapshot.remainingPercent) >= 0.1 ||
snapshot.recordedAt - lastSnapshot.recordedAt >= HISTORY_REFRESH_INTERVAL_MS
) {
history.push(snapshot);
saveHistory(history);
}
return history;
}
function getCurrentCycleHistory(history, resetDate) {
var resetTimestamp = resetDate.getTime();
var currentCycleHistory = [];
for (var index = 0; index < history.length; index += 1) {
if (history[index].resetAt === resetTimestamp) {
currentCycleHistory.push(history[index]);
}
}
return currentCycleHistory;
}
function getHistoryDetails(history) {
if (history.length <= 1) {
return {
historyLine: "History: first snapshot this cycle",
lastChangeLine: "Last change: waiting for another snapshot",
};
}
var lastSnapshot = history[history.length - 1];
var previousSnapshot = history[history.length - 2];
var delta = roundToTenths(lastSnapshot.remainingPercent - previousSnapshot.remainingPercent);
var elapsedMs = lastSnapshot.recordedAt - previousSnapshot.recordedAt;
var recentSnapshots = history.slice(-4);
var historyParts = [];
for (var index = 0; index < recentSnapshots.length; index += 1) {
var snapshot = recentSnapshots[index];
var isLatest = index === recentSnapshots.length - 1;
historyParts.push(
snapshot.remainingPercent.toFixed(1) +
"%@" +
(isLatest ? "now" : formatSnapshotLabel(snapshot.recordedAt, lastSnapshot.recordedAt))
);
}
return {
historyLine: "History: " + historyParts.join(" -> "),
lastChangeLine:
"Last change: " +
formatSignedPercent(delta) +
" since " +
formatSnapshotLabel(previousSnapshot.recordedAt, lastSnapshot.recordedAt) +
" (" +
formatDuration(elapsedMs).replace(" left", "") +
")",
};
}
function findAllModelsCard() {
var divs = document.querySelectorAll("div");
var candidates = [];
for (var index = 0; index < divs.length; index += 1) {
var element = divs[index];
var paragraphs = element.querySelectorAll("p");
var hasAllModelsLabel = false;
var hasResetText = false;
var hasUsedText = false;
for (var paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex += 1) {
var text = normalizeText(paragraphs[paragraphIndex].textContent);
if (text === "All models") {
hasAllModelsLabel = true;
}
if (/^Resets\s+[A-Z][a-z]{2}\s+\d{1,2}:\d{2}\s+(AM|PM)$/i.test(text)) {
hasResetText = true;
}
if (/^\d+(?:\.\d+)?%\s*used$/i.test(text)) {
hasUsedText = true;
}
}
if (hasAllModelsLabel && hasResetText && hasUsedText) {
candidates.push(element);
}
}
if (candidates.length === 0) {
return null;
}
candidates.sort(function (left, right) {
return normalizeText(left.textContent).length - normalizeText(right.textContent).length;
});
return candidates[0];
}
function findUsedElement(card) {
var paragraphs = card.querySelectorAll("p");
for (var index = 0; index < paragraphs.length; index += 1) {
var text = normalizeText(paragraphs[index].textContent);
if (/^\d+(?:\.\d+)?%\s*used$/i.test(text)) {
return paragraphs[index];
}
}
return null;
}
function getRemainingPercent(usedElement) {
var text = normalizeText(usedElement.textContent);
var match = text.match(/(\d+(?:\.\d+)?)%\s*used/i);
if (!match) {
return null;
}
return 100 - Number(match[1]);
}
function findResetText(card) {
var paragraphs = card.querySelectorAll("p");
for (var index = 0; index < paragraphs.length; index += 1) {
var text = normalizeText(paragraphs[index].textContent);
if (/^Resets\s+[A-Z][a-z]{2}\s+\d{1,2}:\d{2}\s+(AM|PM)$/i.test(text)) {
return text;
}
}
return null;
}
function parseResetDate(resetText) {
var match = resetText.match(/^Resets\s+([A-Z][a-z]{2})\s+(\d{1,2}):(\d{2})\s+(AM|PM)$/i);
if (!match) {
return null;
}
var weekdayIndex = WEEKDAYS[match[1]];
if (typeof weekdayIndex !== "number") {
return null;
}
var hour = Number(match[2]);
var minute = Number(match[3]);
var meridiem = match[4].toUpperCase();
if (meridiem === "PM" && hour !== 12) {
hour += 12;
}
if (meridiem === "AM" && hour === 12) {
hour = 0;
}
var now = new Date();
var resetDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0);
var dayDelta = (weekdayIndex - now.getDay() + 7) % 7;
resetDate.setDate(resetDate.getDate() + dayDelta);
if (resetDate.getTime() <= now.getTime()) {
resetDate.setDate(resetDate.getDate() + 7);
}
return resetDate;
}
function getMetrics(remainingPercent, resetDate) {
var now = Date.now();
var resetTimestamp = resetDate.getTime();
var timeLeftMs = Math.max(0, resetTimestamp - now);
var daysLeft = timeLeftMs / DAY_MS;
var cycleStartMs = resetTimestamp - WEEK_MS;
var elapsedMs = clamp(now - cycleStartMs, 0, WEEK_MS);
var daysElapsed = elapsedMs / DAY_MS;
var usedPercent = 100 - remainingPercent;
var idealRemaining = clamp((timeLeftMs / WEEK_MS) * 100, 0, 100);
var paceDelta = remainingPercent - idealRemaining;
var perDayBudget = daysLeft > 0 ? remainingPercent / daysLeft : remainingPercent;
var avgDailyUsed = daysElapsed > 0 ? usedPercent / daysElapsed : 0;
return {
avgDailyUsed: avgDailyUsed,
idealRemaining: idealRemaining,
paceDelta: paceDelta,
perDayBudget: perDayBudget,
resetTimestamp: resetTimestamp,
timeLeftMs: timeLeftMs,
usedPercent: usedPercent,
};
}
function upsertBadge(usedElement, metrics) {
var badge = document.getElementById(BADGE_ID);
if (!badge) {
badge = document.createElement("span");
badge.id = BADGE_ID;
}
badge.textContent = metrics.perDayBudget.toFixed(1) + "%/day | " + formatSignedPercent(metrics.paceDelta) + " pace";
badge.title =
"Ideal remaining now: " +
metrics.idealRemaining.toFixed(1) +
"% | Avg used: " +
metrics.avgDailyUsed.toFixed(1) +
"%/day | Reset: " +
new Date(metrics.resetTimestamp).toLocaleString();
var tone = getTone(metrics.perDayBudget);
if (tone === "ok") {
badge.removeAttribute("data-tone");
} else {
badge.setAttribute("data-tone", tone);
}
if (badge.parentElement !== usedElement.parentElement) {
usedElement.insertAdjacentElement("afterend", badge);
}
}
function upsertSummary(metrics, currentCycleHistory, remainingPercent) {
var summary = document.getElementById(SUMMARY_ID);
if (!summary) {
summary = document.createElement("div");
summary.id = SUMMARY_ID;
document.body.appendChild(summary);
}
var historyDetails = getHistoryDetails(currentCycleHistory);
var paceLabel = metrics.paceDelta >= 0 ? "ahead" : "behind";
var toneClass = getSummaryTone(metrics);
summary.innerHTML =
'<div class="tm-summary-title">Claude All Models</div>' +
'<div class="tm-summary-chip">' +
remainingPercent.toFixed(1) +
"% left</div>" +
'<div class="tm-summary-chip">' +
metrics.usedPercent.toFixed(1) +
"% used</div>" +
'<div class="tm-summary-chip">' +
metrics.perDayBudget.toFixed(1) +
"%/day budget</div>" +
'<div class="tm-summary-chip"><span class="' +
toneClass +
"\">" +
paceLabel +
" " +
formatSignedPercent(metrics.paceDelta) +
"</span></div>" +
'<div class="tm-summary-chip">Ideal ' +
metrics.idealRemaining.toFixed(1) +
"% now</div>" +
'<div class="tm-summary-chip">Avg use ' +
metrics.avgDailyUsed.toFixed(1) +
"%/day</div>" +
'<div class="tm-summary-chip">' +
formatDuration(metrics.timeLeftMs) +
"</div>" +
'<div class="tm-summary-chip">' +
historyDetails.lastChangeLine +
"</div>" +
'<div class="tm-summary-chip">' +
historyDetails.historyLine +
"</div>";
}
function refreshUsage() {
var card = findAllModelsCard();
if (!card) {
return;
}
var usedElement = findUsedElement(card);
if (!usedElement) {
return;
}
var remainingPercent = getRemainingPercent(usedElement);
if (remainingPercent === null) {
return;
}
var resetText = findResetText(card);
if (!resetText) {
return;
}
var resetDate = parseResetDate(resetText);
if (!resetDate) {
return;
}
var history = recordSnapshot(remainingPercent, resetDate);
var currentCycleHistory = getCurrentCycleHistory(history, resetDate);
var metrics = getMetrics(remainingPercent, resetDate);
upsertBadge(usedElement, metrics);
upsertSummary(metrics, currentCycleHistory, remainingPercent);
}
var refreshTimer = null;
function scheduleRefresh() {
if (refreshTimer !== null) {
clearTimeout(refreshTimer);
}
refreshTimer = setTimeout(function () {
refreshTimer = null;
refreshUsage();
}, 150);
}
function init() {
injectStyles();
refreshUsage();
var observer = new MutationObserver(function () {
scheduleRefresh();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
characterData: true,
});
window.addEventListener("popstate", scheduleRefresh);
window.addEventListener("hashchange", scheduleRefresh);
}
init();
})();
@cozuya
Copy link
Author

cozuya commented Mar 12, 2026

Untitled

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