Last active
June 2, 2026 03:55
-
-
Save 2u841r/f02adeb2ad82c11c56ea9e911d212593 to your computer and use it in GitHub Desktop.
Claude Usage – Show Local Reset Time
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Claude Usage – Show Local Reset Time | |
| // @namespace https://gist.github.com/2u841r/f02adeb2ad82c11c56ea9e911d212593 | |
| // @version 1.3 | |
| // @description On claude.ai/settings/usage, appends the actual local clock time after the "Resets in 1h 23 min" countdown, e.g. "Resets in 1h 23 min (at 4:23 PM)". | |
| // @author Zubair Ibn Zamir | |
| // @homepageURL https://gist.github.com/2u841r/f02adeb2ad82c11c56ea9e911d212593 | |
| // @supportURL https://gist.github.com/2u841r/f02adeb2ad82c11c56ea9e911d212593 | |
| // @match https://claude.ai/* | |
| // @license MIT | |
| // @run-at document-start | |
| // @grant GM_addStyle | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| // Note: @grant GM_addStyle is declared (even though unused) so Tampermonkey | |
| // runs this script in its sandbox instead of injecting into the page context. | |
| // claude.ai's CSP blocks page-context inline scripts, which is why @grant none | |
| // silently failed to run. The color below uses CSSOM (element.style), which is | |
| // not affected by CSP, so it works fine in the sandbox. | |
| const MARK = 'usl-local-reset-time'; // class for the element we inject | |
| // Parse a remaining-time string like "1h 23 min", "23 min", "Resets in 2 hours 5 minutes" | |
| // Returns total seconds, or null if no duration found. | |
| function parseDuration(text) { | |
| const hMatch = text.match(/(\d+)\s*h(?:ours?|rs?)?\b/i); | |
| const mMatch = text.match(/(\d+)\s*m(?:in(?:ute)?s?)?\b/i); | |
| const sMatch = text.match(/(\d+)\s*s(?:ec(?:ond)?s?)?\b/i); | |
| if (!hMatch && !mMatch && !sMatch) return null; | |
| const h = hMatch ? parseInt(hMatch[1], 10) : 0; | |
| const m = mMatch ? parseInt(mMatch[1], 10) : 0; | |
| const s = sMatch ? parseInt(sMatch[1], 10) : 0; | |
| return h * 3600 + m * 60 + s; | |
| } | |
| // A span is a target only if it actually holds the text itself | |
| // (no child elements other than our own injected marker). | |
| function isLeafish(span) { | |
| for (const child of span.children) { | |
| if (!child.classList.contains(MARK)) return false; | |
| } | |
| return true; | |
| } | |
| function processSpan(span) { | |
| let myEl = span.querySelector(':scope > .' + MARK); | |
| // Base text = the span's text WITHOUT our injected suffix. | |
| let baseText = span.textContent; | |
| if (myEl) baseText = baseText.replace(myEl.textContent, ''); | |
| const secs = parseDuration(baseText); | |
| if (secs == null) { | |
| if (myEl) myEl.remove(); | |
| return; | |
| } | |
| const target = new Date(Date.now() + secs * 1000); | |
| const timeStr = target.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); | |
| const suffix = ' (at ' + timeStr + ')'; | |
| if (!myEl) { | |
| myEl = document.createElement('span'); | |
| myEl.className = MARK; | |
| myEl.style.color = 'rgb(244, 169, 169)'; | |
| span.appendChild(myEl); | |
| } | |
| if (myEl.textContent !== suffix) myEl.textContent = suffix; | |
| } | |
| function scan() { | |
| // No URL/pathname guard: in the TanStack Start rewrite, Settings opens as a | |
| // modal dialog and the URL may not change to /settings/usage. We instead rely | |
| // on the text itself ("Resets in <duration>"), which only appears in this panel. | |
| // parseDuration() inside processSpan() rejects non-countdown text like | |
| // "Resets Jul 1", so matching on "reset" + a digit is safe. | |
| document.querySelectorAll('span').forEach((span) => { | |
| const t = span.textContent || ''; | |
| // Must look like a reset countdown: mentions "reset" and contains a number. | |
| if (!/reset/i.test(t)) return; | |
| if (!/\d/.test(t)) return; | |
| if (!isLeafish(span)) return; | |
| processSpan(span); | |
| }); | |
| } | |
| // Re-run periodically: the countdown re-renders and the SPA can swap views. | |
| setInterval(scan, 1000); | |
| scan(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment