Last active
January 24, 2026 02:37
-
-
Save benhook1013/9c2d27db1693ea5e5428e457a30f26b0 to your computer and use it in GitHub Desktop.
Tampermonkey script to show NZD cost and burn-rate stats on ChatGPT Codex usage page
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 Codex Credits → Cost + Stats (Correct Cards) | |
| // @namespace ben.codex.credit.cost | |
| // @version 8.0 | |
| // @description Show credit cost and usage stats on the Codex settings usage page. | |
| // @match https://chatgpt.com/* | |
| // @match https://chat.openai.com/* | |
| // @run-at document-end | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| "use strict"; | |
| if (!location.pathname.includes("/codex/settings/usage")) return; | |
| // ================= CONFIG ================= | |
| // 1000 credits = 70 NZD | |
| const CREDIT_COST = 0.07; // NZD per credit | |
| const CURRENCY_CODE = "NZD"; | |
| // ========================================== | |
| const moneyFmt = new Intl.NumberFormat("en-US", { | |
| style: "currency", | |
| currency: CURRENCY_CODE, | |
| minimumFractionDigits: 2, | |
| }); | |
| const numFmt = new Intl.NumberFormat("en-US", { | |
| maximumFractionDigits: 2, | |
| }); | |
| const money = (v) => moneyFmt.format(v); | |
| const num = (v) => numFmt.format(v); | |
| function parseCredits(text) { | |
| const m = text?.match(/([\d.,]+)\s*credits?/i); | |
| return m ? Number(m[1].replace(/,/g, "")) : null; | |
| } | |
| // ---------------- TABLE ROW COSTS ---------------- | |
| function injectRowCosts() { | |
| document.querySelectorAll("table tbody tr").forEach((row) => { | |
| const span = row.querySelector("td:last-child span"); | |
| if (!span || span.dataset.costDone) return; | |
| const credits = parseCredits(span.textContent); | |
| if (!credits) return; | |
| const cost = document.createElement("span"); | |
| cost.textContent = ` (${money(credits * CREDIT_COST)})`; | |
| cost.style.marginLeft = "4px"; | |
| cost.style.opacity = "0.7"; | |
| cost.style.fontSize = "0.95em"; | |
| span.appendChild(cost); | |
| span.dataset.costDone = "true"; | |
| }); | |
| } | |
| function getTableStats() { | |
| const vals = []; | |
| document.querySelectorAll("table tbody tr").forEach((row) => { | |
| const span = row.querySelector("td:last-child span"); | |
| const c = parseCredits(span?.textContent); | |
| if (c) vals.push(c); | |
| }); | |
| if (!vals.length) return null; | |
| const total = vals.reduce((a, b) => a + b, 0); | |
| return { | |
| avg: total / vals.length, | |
| }; | |
| } | |
| // ---------------- TABLE HEADER CARD ---------------- | |
| function injectTableHeaderStats() { | |
| document.querySelectorAll("div.flex.flex-col.items-start").forEach((col) => { | |
| const label = col.querySelector("p"); | |
| if (label?.textContent.trim() !== "Credits remaining") return; | |
| const numberP = col.querySelectorAll("p")[1]; | |
| if (!numberP) return; | |
| const raw = numberP.firstChild?.nodeValue; | |
| const remaining = Number(raw?.replace(/[^0-9.]/g, "")); | |
| if (!remaining) return; | |
| if (!numberP.querySelector(".codex-cost")) { | |
| const cost = document.createElement("span"); | |
| cost.className = "codex-cost"; | |
| cost.textContent = ` (${money(remaining * CREDIT_COST)})`; | |
| cost.style.marginLeft = "6px"; | |
| cost.style.opacity = "0.7"; | |
| numberP.appendChild(cost); | |
| } | |
| const stats = getTableStats(); | |
| if (!stats) return; | |
| let statsDiv = col.querySelector(".codex-stats"); | |
| if (!statsDiv) { | |
| statsDiv = document.createElement("div"); | |
| statsDiv.className = "codex-stats"; | |
| statsDiv.style.marginTop = "4px"; | |
| statsDiv.style.fontSize = "0.85rem"; | |
| statsDiv.style.opacity = "0.85"; | |
| col.appendChild(statsDiv); | |
| } | |
| statsDiv.innerHTML = | |
| `<div>Avg credits / usage day: ${num(stats.avg)}</div>` + | |
| `<div>Approx days remaining: ${num(remaining / stats.avg)}</div>`; | |
| }); | |
| } | |
| // ---------------- TOP BALANCE CARD ---------------- | |
| function injectTopCreditsCardCost() { | |
| document.querySelectorAll("article").forEach((article) => { | |
| const label = article.querySelector("p"); | |
| if (label?.textContent.trim() !== "Credits remaining") return; | |
| const span = article.querySelector("span.text-2xl.font-semibold"); | |
| if (!span || span.querySelector(".codex-top-cost")) return; | |
| const raw = span.firstChild?.nodeValue; | |
| const remaining = Number(raw?.replace(/[^0-9.]/g, "")); | |
| if (!remaining) return; | |
| const cost = document.createElement("span"); | |
| cost.className = "codex-top-cost"; | |
| cost.textContent = ` (${money(remaining * CREDIT_COST)})`; | |
| cost.style.marginLeft = "6px"; | |
| cost.style.opacity = "0.7"; | |
| cost.style.fontSize = "0.9em"; | |
| cost.style.fontWeight = "normal"; | |
| span.appendChild(cost); | |
| }); | |
| } | |
| // ---------------- MAIN LOOP ---------------- | |
| function tick() { | |
| injectRowCosts(); | |
| injectTableHeaderStats(); | |
| injectTopCreditsCardCost(); | |
| } | |
| tick(); | |
| setInterval(tick, 2000); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment