A compact floating widget that displays your Claude AI usage limits in real time on chat pages
// ==UserScript==
// @name CLAUSEAGE
// @namespace https://github.com/Yorkian/CLAUSEAGE
// @version 1.0.0
// @description A compact floating widget that displays your Claude AI usage limits in real time on chat pages
// @author York
// @match https://claude.ai/*
// @icon https://claude.ai/favicon.ico
// @grant GM_addStyle
// @license MIT
// @homepageURL https://github.com/Yorkian/CLAUSEAGE
// @supportURL https://github.com/Yorkian/CLAUSEAGE/issues
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
// ══════════════════════════════════════════
// Styles
// ══════════════════════════════════════════
GM_addStyle(`
/* ── Claude Usage Widget ── */
#claude-usage-widget {
position: fixed;
right: 16px;
bottom: 16px;
z-index: 2147483647;
width: 252px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 12px;
color: #1a1a1a;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 10px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.10), 0 1px 4px rgba(0, 0, 0, 0.06);
overflow: hidden;
user-select: none;
transition: box-shadow 0.2s, opacity 0.2s;
}
#claude-usage-widget.cuw-dragging {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);
opacity: 0.92;
}
#claude-usage-widget.cuw-collapsed .cuw-body-wrap,
#claude-usage-widget.cuw-collapsed #cuw-body {
display: none;
}
.cuw-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 10px;
cursor: grab;
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
background: rgba(0, 0, 0, 0.02);
}
.cuw-header:active { cursor: grabbing; }
.cuw-title {
font-weight: 600;
font-size: 11px;
letter-spacing: 0.3px;
text-transform: uppercase;
color: #666;
}
.cuw-plan {
text-transform: none;
font-weight: 500;
opacity: 0.75;
}
.cuw-actions { display: flex; gap: 2px; }
.cuw-btn {
display: flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border: none;
border-radius: 5px;
background: transparent;
color: #888;
cursor: pointer;
padding: 0;
transition: background 0.15s, color 0.15s;
}
.cuw-btn:hover { background: rgba(0, 0, 0, 0.07); color: #333; }
#cuw-refresh:active svg { transform: rotate(-180deg); transition: transform 0.3s ease; }
#cuw-body { padding: 8px 10px 6px; }
.cuw-loading { display: flex; align-items: center; gap: 6px; color: #999; font-size: 11px; padding: 4px 0; }
.cuw-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 1.5px solid rgba(0,0,0,0.1);
border-top-color: #6b8afd;
border-radius: 50%;
animation: cuw-spin 0.7s linear infinite;
}
@keyframes cuw-spin { to { transform: rotate(360deg); } }
.cuw-empty { color: #aaa; font-size: 11px; padding: 4px 0; }
.cuw-row { margin-bottom: 6px; }
.cuw-row:last-child, .cuw-row:last-of-type { margin-bottom: 2px; }
.cuw-row-top { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 3px; }
.cuw-label { font-weight: 500; font-size: 11.5px; color: #333; }
.cuw-pct { font-weight: 600; font-size: 11.5px; color: #333; font-variant-numeric: tabular-nums; }
.cuw-bar-track { height: 5px; background: rgba(0, 0, 0, 0.06); border-radius: 3px; overflow: hidden; }
.cuw-bar-fill { height: 100%; border-radius: 3px; transition: width 0.4s ease; }
.cuw-reset { font-size: 10px; color: #999; margin-top: 2px; }
.cuw-balance { color: #22c55e; }
.cuw-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 4px;
padding-top: 4px;
border-top: 1px solid rgba(0, 0, 0, 0.04);
}
.cuw-updated { font-size: 10px; color: #bbb; }
.cuw-github { display: flex; align-items: center; color: #ccc; text-decoration: none; transition: color 0.15s; line-height: 1; }
.cuw-github:hover { color: #888; }
/* ── Dark mode ── */
@media (prefers-color-scheme: dark) {
#claude-usage-widget { background: rgba(32, 33, 36, 0.92); border-color: rgba(255, 255, 255, 0.08); color: #e0e0e0; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2); }
.cuw-header { border-bottom-color: rgba(255, 255, 255, 0.06); background: rgba(255, 255, 255, 0.02); }
.cuw-title { color: #aaa; }
.cuw-btn { color: #888; }
.cuw-btn:hover { background: rgba(255, 255, 255, 0.08); color: #ddd; }
.cuw-label, .cuw-pct { color: #ddd; }
.cuw-bar-track { background: rgba(255, 255, 255, 0.08); }
.cuw-reset { color: #777; }
.cuw-balance { color: #4ade80; }
.cuw-footer { border-top-color: rgba(255, 255, 255, 0.06); }
.cuw-updated { color: #666; }
.cuw-github { color: #555; }
.cuw-github:hover { color: #aaa; }
.cuw-loading { color: #777; }
.cuw-empty { color: #666; }
}
html[data-theme="dark"] #claude-usage-widget, body.dark #claude-usage-widget, [data-mode="dark"] #claude-usage-widget { background: rgba(32, 33, 36, 0.92); border-color: rgba(255, 255, 255, 0.08); color: #e0e0e0; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3), 0 1px 4px rgba(0, 0, 0, 0.2); }
html[data-theme="dark"] .cuw-header, body.dark .cuw-header, [data-mode="dark"] .cuw-header { border-bottom-color: rgba(255, 255, 255, 0.06); background: rgba(255, 255, 255, 0.02); }
html[data-theme="dark"] .cuw-title, body.dark .cuw-title, [data-mode="dark"] .cuw-title { color: #aaa; }
html[data-theme="dark"] .cuw-btn, body.dark .cuw-btn, [data-mode="dark"] .cuw-btn { color: #888; }
html[data-theme="dark"] .cuw-label, body.dark .cuw-label, [data-mode="dark"] .cuw-label, html[data-theme="dark"] .cuw-pct, body.dark .cuw-pct, [data-mode="dark"] .cuw-pct { color: #ddd; }
html[data-theme="dark"] .cuw-bar-track, body.dark .cuw-bar-track, [data-mode="dark"] .cuw-bar-track { background: rgba(255, 255, 255, 0.08); }
html[data-theme="dark"] .cuw-reset, body.dark .cuw-reset, [data-mode="dark"] .cuw-reset { color: #777; }
html[data-theme="dark"] .cuw-balance, body.dark .cuw-balance, [data-mode="dark"] .cuw-balance { color: #4ade80; }
html[data-theme="dark"] .cuw-footer, body.dark .cuw-footer, [data-mode="dark"] .cuw-footer { border-top-color: rgba(255, 255, 255, 0.06); }
html[data-theme="dark"] .cuw-updated, body.dark .cuw-updated, [data-mode="dark"] .cuw-updated { color: #666; }
html[data-theme="dark"] .cuw-github, body.dark .cuw-github, [data-mode="dark"] .cuw-github { color: #555; }
html[data-theme="dark"] .cuw-github:hover, body.dark .cuw-github:hover, [data-mode="dark"] .cuw-github:hover { color: #aaa; }
`);
// ══════════════════════════════════════════
// Script
// ══════════════════════════════════════════
const WIDGET_ID = 'claude-usage-widget';
const IFRAME_ID = 'claude-usage-iframe';
const STORAGE_KEY = 'claude-usage-widget-pos';
const MARGIN_RIGHT = 16;
const MARGIN_BOTTOM = 16;
const POLL_INTERVAL = 500;
const MAX_POLLS = 40;
const AUTO_REFRESH_MS = 5 * 60 * 1000;
let autoRefreshTimer = null;
let lastRefreshTime = null;
let updatedTickerTimer = null;
let wasDragged = false;
let savedTop = null;
const GITHUB_SVG = '<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33c.85 0 1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2Z"/></svg>';
// ── SPA Navigation ──
function isOnChatPage() {
return location.pathname.startsWith('/chat/');
}
function showWidget() {
var w = document.getElementById(WIDGET_ID);
if (w) w.style.display = '';
}
function hideWidget() {
var w = document.getElementById(WIDGET_ID);
if (w) w.style.display = 'none';
}
function monitorNavigation() {
var lastPath = location.pathname;
var check = function () {
var curPath = location.pathname;
if (curPath === lastPath) return;
lastPath = curPath;
onRouteChange();
};
var origPush = history.pushState;
history.pushState = function () {
origPush.apply(this, arguments);
setTimeout(check, 0);
};
var origReplace = history.replaceState;
history.replaceState = function () {
origReplace.apply(this, arguments);
setTimeout(check, 0);
};
window.addEventListener('popstate', check);
setInterval(check, 1000);
}
function onRouteChange() {
if (isOnChatPage()) {
ensureWidget();
showWidget();
} else {
hideWidget();
}
}
// ── Widget creation ──
function ensureWidget() {
if (document.getElementById(WIDGET_ID)) return;
createWidget();
fetchUsageData();
scheduleAutoRefresh();
}
function createWidget() {
var wrap = document.createElement('div');
wrap.id = WIDGET_ID;
wrap.innerHTML =
'<div class="cuw-header" id="cuw-drag-handle">' +
'<span class="cuw-title">CLAUSEAGE</span>' +
'<div class="cuw-actions">' +
'<button class="cuw-btn" id="cuw-refresh" title="Refresh">' +
'<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
'<path d="M1.5 2v4.5H6"/>' +
'<path d="M2.2 10.2a6 6 0 1 0 .6-6.7L1.5 6.5"/>' +
'</svg>' +
'</button>' +
'<button class="cuw-btn" id="cuw-minimize" title="Minimize">' +
'<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">' +
'<line x1="3" y1="8" x2="13" y2="8"/>' +
'</svg>' +
'</button>' +
'</div>' +
'</div>' +
'<div class="cuw-body" id="cuw-body">' +
'<div class="cuw-loading">Loading…</div>' +
'</div>';
document.body.appendChild(wrap);
try {
var saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
if (saved && saved.wasDragged) {
wasDragged = true;
savedTop = saved.top;
}
} catch (_) {}
applyPosition(wrap);
initDrag(wrap, document.getElementById('cuw-drag-handle'));
window.addEventListener('resize', function () {
var w = document.getElementById(WIDGET_ID);
if (w) applyPosition(w);
});
var minBtn = document.getElementById('cuw-minimize');
minBtn.addEventListener('click', function (e) {
e.stopPropagation();
var collapsed = wrap.classList.toggle('cuw-collapsed');
minBtn.title = collapsed ? 'Expand' : 'Minimize';
minBtn.innerHTML = collapsed
? '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="8" y1="3" x2="8" y2="13"/><line x1="3" y1="8" x2="13" y2="8"/></svg>'
: '<svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="8" x2="13" y2="8"/></svg>';
});
document.getElementById('cuw-refresh').addEventListener('click', function (e) {
e.stopPropagation();
fetchUsageData();
scheduleAutoRefresh();
});
return wrap;
}
// ── Position logic ──
function applyPosition(el) {
if (!wasDragged) {
el.style.right = MARGIN_RIGHT + 'px';
el.style.bottom = MARGIN_BOTTOM + 'px';
el.style.left = 'auto';
el.style.top = 'auto';
return;
}
var rect = el.getBoundingClientRect();
var left = window.innerWidth - rect.width - MARGIN_RIGHT;
el.style.right = 'auto';
el.style.bottom = 'auto';
el.style.left = left + 'px';
var t = savedTop;
if (t < 0) t = 0;
if (t + rect.height > window.innerHeight) t = window.innerHeight - rect.height;
el.style.top = t + 'px';
}
// ── Drag logic ──
function initDrag(el, handle) {
var startX, startY, startLeft, startTop, dragging = false;
handle.addEventListener('mousedown', function (e) {
if (e.target.closest('.cuw-btn')) return;
e.preventDefault();
dragging = true;
var rect = el.getBoundingClientRect();
startX = e.clientX;
startY = e.clientY;
startLeft = rect.left;
startTop = rect.top;
el.style.right = 'auto';
el.style.bottom = 'auto';
el.style.left = rect.left + 'px';
el.style.top = rect.top + 'px';
el.classList.add('cuw-dragging');
});
document.addEventListener('mousemove', function (e) {
if (!dragging) return;
el.style.left = (startLeft + (e.clientX - startX)) + 'px';
el.style.top = (startTop + (e.clientY - startY)) + 'px';
});
document.addEventListener('mouseup', function () {
if (!dragging) return;
dragging = false;
el.classList.remove('cuw-dragging');
clampToViewport(el);
var rect = el.getBoundingClientRect();
wasDragged = true;
savedTop = rect.top;
localStorage.setItem(STORAGE_KEY, JSON.stringify({ top: savedTop, wasDragged: true }));
applyPosition(el);
});
}
function clampToViewport(el) {
var rect = el.getBoundingClientRect();
var vw = window.innerWidth, vh = window.innerHeight;
var l = rect.left, t = rect.top;
if (l < 0) l = 0;
if (t < 0) t = 0;
if (l + rect.width > vw) l = vw - rect.width;
if (t + rect.height > vh) t = vh - rect.height;
el.style.left = l + 'px';
el.style.top = t + 'px';
}
// ── Parse usage data ──
function parseUsage(doc) {
var data = { sections: [], plan: '' };
var allText = doc.body.innerText;
if (!allText || allText.includes('Sign in') || allText.includes('Log in')) return null;
var planWalker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT, null);
while (planWalker.nextNode()) {
var ptxt = planWalker.currentNode.textContent.trim();
if (/^(Free|Pro|Team|Enterprise|Max)$/i.test(ptxt)) {
data.plan = ptxt;
break;
}
}
var currentBalance = null;
var balWalker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT, null);
while (balWalker.nextNode()) {
if (/current balance/i.test(balWalker.currentNode.textContent.trim())) {
var balEl = balWalker.currentNode.parentElement;
for (var d = 0; d < 5 && balEl; d++) {
var balText = balEl.innerText || '';
var balMatch = balText.match(/\$\s*([\d,]+(?:\.\d{1,2})?)/);
if (balMatch) {
currentBalance = parseFloat(balMatch[1].replace(/,/g, ''));
break;
}
balEl = balEl.parentElement;
}
break;
}
}
var hideExtraUsage = (currentBalance === null || currentBalance === 0);
var walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT, null);
var usageTexts = [];
while (walker.nextNode()) {
if (walker.currentNode.textContent.trim().includes('% used')) usageTexts.push(walker.currentNode);
}
usageTexts.forEach(function (node) {
var pctMatch = node.textContent.match(/(\d+)%\s*used/);
if (!pctMatch) return;
var pct = parseInt(pctMatch[1], 10);
var container = findAncestorBlock(node);
if (!container) return;
var blockText = container.innerText || '';
var lines = blockText.split('\n').map(function (l) { return l.trim(); }).filter(Boolean);
var label = '', resetInfo = '';
lines.forEach(function (line) {
if (/current session|all models/i.test(line)) label = line;
if (/resets?\s/i.test(line)) resetInfo = line;
});
if (!label) {
label = lines.find(function (l) { return !l.includes('% used') && !l.includes('Resets') && !l.includes('Learn more'); }) || 'Unknown';
}
if (/\$/.test(label) && hideExtraUsage) return;
data.sections.push({ type: 'pct', label: label, pct: pct, resetInfo: resetInfo });
});
return data.sections.length > 0 ? data : null;
}
function findAncestorBlock(node) {
var el = node.parentElement;
var depth = 0;
while (el && depth < 15) {
var text = el.innerText || '';
if ((text.includes('Resets') || text.includes('Reset')) && text.includes('% used')) return el;
el = el.parentElement;
depth++;
}
return node.parentElement && node.parentElement.parentElement && node.parentElement.parentElement.parentElement || null;
}
// ── Relative time + ticker ──
function getRelativeTimeText() {
if (!lastRefreshTime) return '';
var elapsed = Math.floor((Date.now() - lastRefreshTime) / 1000);
if (elapsed < 30) return 'Updated: just now';
if (elapsed < 60) return 'Updated: half a minute ago';
if (elapsed < 120) return 'Updated: a minute ago';
if (elapsed < 180) return 'Updated: 2 minutes ago';
if (elapsed < 240) return 'Updated: 3 minutes ago';
if (elapsed < 290) return 'Updated: 4 minutes ago';
return 'Refreshing soon';
}
function stopUpdatedTicker() {
if (updatedTickerTimer) { clearInterval(updatedTickerTimer); updatedTickerTimer = null; }
}
function startUpdatedTicker() {
stopUpdatedTicker();
updatedTickerTimer = setInterval(function () {
var el = document.getElementById('cuw-updated-line');
if (el) el.textContent = getRelativeTimeText();
}, 1000);
}
// ── Render data ──
function renderData(data) {
var body = document.getElementById('cuw-body');
if (!data) {
body.innerHTML = '<div class="cuw-empty">Unable to load</div>';
return;
}
lastRefreshTime = Date.now();
var titleEl = document.querySelector('#' + WIDGET_ID + ' .cuw-title');
if (titleEl) {
if (data.plan) {
titleEl.innerHTML = 'CLAUSEAGE <span class="cuw-plan">| ' + escapeHtml(data.plan) + '</span>';
} else {
titleEl.textContent = 'CLAUSEAGE';
}
}
var html = '';
data.sections.forEach(function (s) {
var barColor = s.pct >= 90 ? '#ef4444' : s.pct >= 70 ? '#f59e0b' : '#6b8afd';
html +=
'<div class="cuw-row">' +
'<div class="cuw-row-top">' +
'<span class="cuw-label">' + escapeHtml(s.label) + '</span>' +
'<span class="cuw-pct">' + s.pct + '%</span>' +
'</div>' +
'<div class="cuw-bar-track">' +
'<div class="cuw-bar-fill" style="width:' + s.pct + '%;background:' + barColor + '"></div>' +
'</div>' +
(s.resetInfo ? '<div class="cuw-reset">' + escapeHtml(s.resetInfo) + '</div>' : '') +
'</div>';
});
html +=
'<div class="cuw-footer">' +
'<span class="cuw-updated" id="cuw-updated-line">' + getRelativeTimeText() + '</span>' +
'<a class="cuw-github" href="https://github.com/Yorkian/CLAUSEAGE" target="_blank" title="GitHub">' + GITHUB_SVG + '</a>' +
'</div>';
body.innerHTML = html;
startUpdatedTicker();
}
function escapeHtml(s) {
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
// ── Fetch data via hidden iframe ──
function fetchUsageData() {
stopUpdatedTicker();
var body = document.getElementById('cuw-body');
if (body) body.innerHTML = '<div class="cuw-loading"><span class="cuw-spinner"></span> Loading…</div>';
var old = document.getElementById(IFRAME_ID);
if (old) old.remove();
var iframe = document.createElement('iframe');
iframe.id = IFRAME_ID;
iframe.src = 'https://claude.ai/settings/usage';
iframe.style.cssText = 'position:fixed;width:0;height:0;border:none;opacity:0;pointer-events:none;top:-9999px;left:-9999px;';
document.body.appendChild(iframe);
var polls = 0;
var timer = setInterval(function () {
polls++;
try {
var doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
if (!doc || !doc.body) {
if (polls >= MAX_POLLS) { clearInterval(timer); iframe.remove(); renderData(null); }
return;
}
var currentUrl = (iframe.contentWindow && iframe.contentWindow.location && iframe.contentWindow.location.href) || '';
if (currentUrl.includes('/login') || currentUrl.includes('/signin')) {
clearInterval(timer); iframe.remove(); hideWidget(); return;
}
var text = doc.body.innerText || '';
if (text.includes('% used')) {
clearInterval(timer);
var data = parseUsage(doc);
renderData(data);
iframe.remove();
return;
}
if (text.includes('Sign in') || text.includes('Log in') || text.includes('Continue with')) {
clearInterval(timer); iframe.remove(); hideWidget(); return;
}
} catch (e) {
if (e.name === 'SecurityError' || (e.message && e.message.includes('cross-origin'))) {
clearInterval(timer); iframe.remove(); hideWidget(); return;
}
}
if (polls >= MAX_POLLS) { clearInterval(timer); iframe.remove(); renderData(null); }
}, POLL_INTERVAL);
}
// ── Auto-refresh ──
function scheduleAutoRefresh() {
if (autoRefreshTimer) clearInterval(autoRefreshTimer);
autoRefreshTimer = setInterval(function () {
fetchUsageData();
}, AUTO_REFRESH_MS);
}
// ── Init ──
monitorNavigation();
if (isOnChatPage()) {
ensureWidget();
}
})();