Monitors the Claude usage page and notifies on every 5% increase in plan consumption.
// ==UserScript== // @name Claude - Plan usage limit // @namespace https://claude.ai/ // @version 1.6.3 // @description Monitors the Claude usage page and notifies on every 5% increase in plan consumption. // @license MIT // @licence MIT // @author [email protected] // @match https://claude.ai/* // @run-at document-idle // @grant GM_notification // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // ==/UserScript== /* ============================================================================= * Greasy Fork description (paste the block below into the script page on * https://greasyfork.org/fr/scripts/576225-claude-plan-usage-limit): * ----------------------------------------------------------------------------- * * Monitors the Claude usage page (claude.ai/settings/usage) and sends a * notification each time plan consumption crosses a 5 % threshold * (5, 10, 15, 20 ... %). * * ## Features * * - Auto-detects French and English UI on the Claude page; notification text * follows `navigator.language`. * - Uses `GM_notification` with a unique `tag` so a new threshold replaces the * previous notification instead of stacking up. * - Stays armed even when the browser window is minimized: polling runs inside * a Web Worker to bypass background-tab timer throttling. * - Detects SPA navigation (`pushState` / `replaceState` / `popstate`), so the * notification fires as soon as you land on `/settings/usage`, even from * another route on claude.ai. * - Optional sound cue (880 Hz, ~400 ms, generated via the Web Audio API), * toggled from the Violentmonkey / Tampermonkey extension menu - useful on * Firefox where OS notifications are short-lived. * - Persistent settings via `GM_setValue` (no `localStorage` pollution). * * ## Permissions * * - `GM_notification` - desktop notifications * - `GM_registerMenuCommand` - sound toggle in the extension menu * - `GM_setValue` / `GM_getValue` - persistent settings * * ## Privacy * * The script reads the percentage and reset countdown directly from the DOM of * the official Claude usage page. No network request, no third-party service, * no telemetry. * * ## Compatibility * * Tested on Firefox + Violentmonkey and Chrome + Tampermonkey. * * - Chrome + Tampermonkey: fully persistent OS notifications (kept until * clicked). * - Chrome + Violentmonkey: persistent thanks to the `onclick` parameter. * - Firefox + Violentmonkey: notifications fire but are short-lived (Firefox * limitation, Bugzilla #1187270). Enable the sound option for a reliable * signal when the window is minimized. * * ============================================================================= */ (function () { 'use strict'; const USAGE_PATH_REGEX = /^\/settings\/usage(?:\/|$)/; const LABELS = ['Plan usage limits', "Limites d'utilisation du forfait"]; const PERCENT_REGEX = /(\d+(?:[.,]\d+)?)\s*%/; const RESET_REGEX = /(?:Resets(?:\s+in)?|Réinitialisation(?:\s+dans)?)\s+(.+)/; const TEST_MODE = false; const THRESHOLD = 5; const POLL_INTERVAL_MS = TEST_MODE ? 15000 : 30000; const INITIAL_TIMEOUT_MS = 15000; const INITIAL_POLL_MS = 3000; const STORAGE_KEY = 'claude-usage-last-notified-percent'; const BEEP_STORAGE_KEY = 'claude-usage-beep-enabled'; const LANG = (navigator.language || 'en').toLowerCase().startsWith('fr') ? 'fr' : 'en'; const I18N = { en: { timeLocale: 'en-US', usedTitle: pct => `Claude - ${pct}% used`, usedTitleTest: pct => `Claude - ${pct}% used [TEST]`, resetsIn: time => `Resets in ${time}`, planLimit: 'Plan usage limit', initialState: 'Initial state', thresholdReached: pct => `Threshold reached: ${pct}%`, resetDetected: 'Reset detected', belowNext: 'Below next threshold', testModePrefix: reason => `TEST_MODE (${reason})`, notFoundWarn: (labels, sec) => `[Claude Usage] Could not find ${labels} on the page after ${sec}s.`, notifyFired: (title, body) => `Notification fired: "${title}" - ${body}`, beepLabel: 'Sound on notification' }, fr: { timeLocale: 'fr-FR', usedTitle: pct => `Claude - ${pct} % utilisé`, usedTitleTest: pct => `Claude - ${pct} % utilisé [TEST]`, resetsIn: time => `Réinitialisation dans ${time}`, planLimit: "Limite d'utilisation du forfait", initialState: 'État initial', thresholdReached: pct => `Seuil atteint : ${pct} %`, resetDetected: 'Réinitialisation détectée', belowNext: 'Sous le prochain seuil', testModePrefix: reason => `MODE TEST (${reason})`, notFoundWarn: (labels, sec) => `[Claude Usage] Impossible de trouver ${labels} sur la page après ${sec} s.`, notifyFired: (title, body) => `Notification déclenchée : "${title}" - ${body}`, beepLabel: 'Son à la notification' } }; const t = I18N[LANG]; const LOGO_DATA_URI = 'data:image/png;base64,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'; function findSection() { const span = [...document.querySelectorAll('span')] .find(s => LABELS.includes(s.textContent.trim())); return span ? span.closest('section') : null; } function findResetText(section) { const walker = document.createTreeWalker(section, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { const txt = (node.nodeValue || '').trim(); const match = txt.match(RESET_REGEX); if (match) return match[1].trim(); } return null; } function extract() { const section = findSection(); if (!section) return null; const percentMatch = section.textContent.match(PERCENT_REGEX); if (!percentMatch) return null; return { percent: parseFloat(percentMatch[1].replace(',', '.')), reset: findResetText(section) }; } function readLastNotified() { const value = GM_getValue(STORAGE_KEY, null); return Number.isFinite(value) ? value : null; } function writeLastNotified(value) { GM_setValue(STORAGE_KEY, value); } function logResult({ percent, reset }, prefix) { const stamp = new Date().toLocaleTimeString(t.timeLocale); const resetPart = reset ? ` - ${t.resetsIn(reset)}` : ''; console.log( `%c[Claude Usage] ${stamp} - ${prefix}: ${percent}%${resetPart}`, 'color:#c96442;font-weight:bold;' ); } function isBeepEnabled() { return GM_getValue(BEEP_STORAGE_KEY, false); } function setBeepEnabled(enabled) { GM_setValue(BEEP_STORAGE_KEY, !!enabled); } const BEEP_MENU_ID = 'claude-usage-beep-toggle'; function refreshBeepMenu() { const caption = `${t.beepLabel} : ${isBeepEnabled() ? '✓' : '✗'}`; GM_registerMenuCommand(caption, () => { setBeepEnabled(!isBeepEnabled()); refreshBeepMenu(); }, { id: BEEP_MENU_ID }); } function playBeep() { if (!isBeepEnabled()) return; try { const Ctx = window.AudioContext || window.webkitAudioContext; if (!Ctx) return; const ctx = new Ctx(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.frequency.value = 880; osc.connect(gain).connect(ctx.destination); const t0 = ctx.currentTime; gain.gain.setValueAtTime(0.001, t0); gain.gain.exponentialRampToValueAtTime(0.25, t0 + 0.02); gain.gain.exponentialRampToValueAtTime(0.001, t0 + 0.4); osc.start(t0); osc.stop(t0 + 0.45); osc.onended = () => ctx.close(); } catch (e) { console.warn('[Claude Usage] Beep failed:', e); } } function notify({ percent, reset }, title) { const body = reset ? t.resetsIn(reset) : t.planLimit; console.log( `%c[Claude Usage] ${t.notifyFired(title, body)}`, 'color:#c96442;font-weight:bold;' ); playBeep(); if (typeof GM_notification === 'function') { GM_notification({ title, text: body, image: LOGO_DATA_URI, tag: 'claude-usage', silent: false, zombieTimeout: 60000, onclick: () => { try { window.focus(); } catch (e) { /* noop */ } } }); return; } if (typeof Notification === 'undefined') return; const show = () => new Notification(title, { body, icon: LOGO_DATA_URI }); if (Notification.permission === 'granted') { show(); } else if (Notification.permission !== 'denied') { Notification.requestPermission().then(p => { if (p === 'granted') show(); }); } } function bucket(percent) { return Math.floor(percent / THRESHOLD) * THRESHOLD; } function check(reason) { const result = extract(); if (!result) return false; const currentBucket = bucket(result.percent); const last = readLastNotified(); if (TEST_MODE) { logResult(result, t.testModePrefix(reason)); notify(result, t.usedTitleTest(result.percent)); writeLastNotified(currentBucket); return true; } if (reason === 'initial') { logResult(result, t.initialState); notify(result, t.usedTitle(result.percent)); writeLastNotified(currentBucket); return true; } if (last === null || currentBucket > last) { logResult(result, t.thresholdReached(currentBucket)); notify(result, t.usedTitle(result.percent)); writeLastNotified(currentBucket); return true; } if (currentBucket < last) { logResult(result, t.resetDetected); notify(result, t.usedTitle(result.percent)); writeLastNotified(currentBucket); return true; } if (reason === 'manual') { logResult(result, t.belowNext); } return true; } let pollWorker = null; let bootstrapTimer = null; function stopTimers() { if (pollWorker) { pollWorker.terminate(); pollWorker = null; } if (bootstrapTimer) { clearInterval(bootstrapTimer); bootstrapTimer = null; } } function startMonitoring() { const workerCode = 'setInterval(() => self.postMessage("tick"), ' + POLL_INTERVAL_MS + ');'; const blobUrl = URL.createObjectURL(new Blob([workerCode], { type: 'application/javascript' })); pollWorker = new Worker(blobUrl); URL.revokeObjectURL(blobUrl); pollWorker.onmessage = () => { if (!isOnUsagePage()) { stopTimers(); return; } check('poll'); }; } function isOnUsagePage() { return USAGE_PATH_REGEX.test(location.pathname); } function bootstrap() { stopTimers(); if (check('initial')) { startMonitoring(); return; } const start = Date.now(); bootstrapTimer = setInterval(() => { if (!isOnUsagePage()) { stopTimers(); return; } if (check('initial')) { clearInterval(bootstrapTimer); bootstrapTimer = null; startMonitoring(); } else if (Date.now() - start > INITIAL_TIMEOUT_MS) { clearInterval(bootstrapTimer); bootstrapTimer = null; console.warn(t.notFoundWarn(LABELS.map(l => `"${l}"`).join(' / '), INITIAL_TIMEOUT_MS / 1000)); } }, INITIAL_POLL_MS); } function onUrlChange() { if (isOnUsagePage()) { bootstrap(); } else { stopTimers(); } } ['pushState', 'replaceState'].forEach(method => { const original = history[method]; history[method] = function (...args) { const result = original.apply(this, args); window.dispatchEvent(new Event('claude-usage-locationchange')); return result; }; }); window.addEventListener('popstate', () => window.dispatchEvent(new Event('claude-usage-locationchange'))); window.addEventListener('claude-usage-locationchange', onUrlChange); refreshBeepMenu(); onUrlChange(); })();