Skip to content

Instantly share code, notes, and snippets.

@PatrickJS
Last active June 19, 2026 17:32
Show Gist options
  • Select an option

  • Save PatrickJS/b9e975a37a7a3ddf2ea463ef4582249f to your computer and use it in GitHub Desktop.

Select an option

Save PatrickJS/b9e975a37a7a3ddf2ea463ef4582249f to your computer and use it in GitHub Desktop.
Pretty-print Codex rate-limit reset credits from Codex Desktop auth

Codex usage helpers

Small shell snippets for checking local Codex usage from the terminal. Each script is self-contained so you can review exactly what it reads, which endpoint it calls, and what it prints before running it.

Requires Node.js v24+.

Scripts

bash codex-usage-reset.sh   # reset credits only
bash codex-usage-usage.sh   # 5-hour and weekly usage only
bash codex-usage.sh         # combined usage + reset credits

All scripts:

  • read ~/.codex/auth.json
  • use JSON import attributes: with: { type: "json" }
  • call ChatGPT Codex backend endpoints with your local Codex auth token
  • do not print the bearer token

Reset Credits View

The reset-credit view is optimized for the quick human question: do I have any available, and how soon do I need to use them?

Codex reset credits
Available now: 3
Use soonest: 23 days left - Jul 11, 2026, 6:22 PM
Available expiries: 23d, 25d, 29d
Other: 0 redeemed, 3 total listed
Fetch: HTTP 200 OK

Full reset-credit details are printed below the summary.

Usage Limits View

The usage view shows the current 5-hour and weekly windows first.

Codex usage limits
Plan: pro

5-hour    98% left  [####################]    2% used  resets in 3h 58m (Jun 19, 2026, 2:00 PM)
Weekly    68% left  [##############......]   32% used  resets in 5d 4h (Jun 24, 2026, 2:18 PM)
#!/usr/bin/env bash
set -euo pipefail
node --input-type=module <<'JS'
import { homedir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
// Read the same local auth file used by Codex Desktop.
const authPath = join(homedir(), ".codex", "auth.json");
const { default: auth } = await import(pathToFileURL(authPath).href, {
with: { type: "json" },
});
const token = auth.tokens?.access_token;
const account = auth.tokens?.account_id;
if (!token || !account) {
throw new Error("Missing tokens.access_token or tokens.account_id in ~/.codex/auth.json");
}
const headers = {
Authorization: `Bearer ${token}`,
"ChatGPT-Account-ID": account,
"OpenAI-Beta": "codex-1",
originator: "Codex Desktop",
};
const fetchJson = async (url) => {
const response = await fetch(url, { headers });
const fallback = response.clone();
try {
return {
data: await response.json(),
response,
};
} catch {
console.log(`Reset credits: HTTP ${response.status} ${response.statusText}`);
console.log(await fallback.text());
process.exit(response.ok ? 0 : 1);
}
};
const formatDate = (value) => {
if (!value) return "None";
return new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
}).format(new Date(value));
};
const parseDate = (value) => {
if (!value) return null;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
};
const daysUntil = (value) => {
const date = parseDate(value);
if (!date) return "";
const ms = date.getTime() - Date.now();
if (ms < 0) return "expired";
const days = Math.ceil(ms / (1000 * 60 * 60 * 24));
if (days === 0) return "expires today";
if (days === 1) return "expires tomorrow";
return `${days} days left`;
};
const daysUntilShort = (value) => {
const label = daysUntil(value);
if (label === "expires today") return "today";
if (label === "expires tomorrow") return "tomorrow";
if (label === "expired") return label;
return label.replace(" days left", "d");
};
const shortCreditId = (credit) =>
credit.id?.replace(/^RateLimitResetCredit_/, "").slice(-8) ?? "unknown";
const renderCreditDetails = (credit, index) => {
console.log(`${index + 1}. ${credit.title ?? "Rate limit reset"}`);
console.log(` Status: ${credit.status ?? "unknown"}`);
console.log(` Source: ${credit.profile_user_id ?? "unknown"}`);
console.log(` Granted: ${formatDate(credit.granted_at)}`);
console.log(` Expires: ${formatDate(credit.expires_at)} (${daysUntil(credit.expires_at)})`);
console.log(` Redeemed: ${formatDate(credit.redeemed_at)}`);
console.log(` Credit ID: ...${shortCreditId(credit)}`);
if (credit.description) {
console.log(` Note: ${credit.description}`);
}
console.log("");
};
const { data, response } = await fetchJson(RESET_CREDITS_URL);
if (!Array.isArray(data.credits)) {
console.log(JSON.stringify(data, null, 2));
process.exit(response.ok ? 0 : 1);
}
const credits = data.credits;
const availableCredits = credits
.filter((credit) => credit.status === "available")
.sort((a, b) => {
const aExpires = parseDate(a.expires_at)?.getTime() ?? Number.POSITIVE_INFINITY;
const bExpires = parseDate(b.expires_at)?.getTime() ?? Number.POSITIVE_INFINITY;
return aExpires - bExpires;
});
const redeemedCredits = credits.filter((credit) => credit.redeemed_at || credit.status === "redeemed");
const availableCount = data.available_count ?? availableCredits.length;
const soonestAvailable = availableCredits[0];
const expirySummary = availableCredits.map((credit) => daysUntilShort(credit.expires_at)).filter(Boolean);
console.log("Codex reset credits");
console.log(`Available now: ${availableCount}`);
if (soonestAvailable) {
console.log(`Use soonest: ${daysUntil(soonestAvailable.expires_at)} - ${formatDate(soonestAvailable.expires_at)}`);
console.log(`Available expiries: ${expirySummary.join(", ")}`);
} else {
console.log("Use soonest: none available");
}
console.log(`Other: ${redeemedCredits.length} redeemed, ${credits.length} total listed`);
console.log(`Fetch: HTTP ${response.status} ${response.statusText}`);
console.log("");
console.log("Reset credit details");
console.log("");
[...availableCredits, ...credits.filter((credit) => credit.status !== "available")].forEach(renderCreditDetails);
if (!response.ok) {
process.exit(1);
}
JS
#!/usr/bin/env bash
set -euo pipefail
node --input-type=module <<'JS'
import { homedir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
// Read the same local auth file used by Codex Desktop.
const authPath = join(homedir(), ".codex", "auth.json");
const { default: auth } = await import(pathToFileURL(authPath).href, {
with: { type: "json" },
});
const token = auth.tokens?.access_token;
const account = auth.tokens?.account_id;
if (!token || !account) {
throw new Error("Missing tokens.access_token or tokens.account_id in ~/.codex/auth.json");
}
const headers = {
Authorization: `Bearer ${token}`,
"ChatGPT-Account-ID": account,
"OpenAI-Beta": "codex-1",
originator: "Codex Desktop",
};
const fetchJson = async (url) => {
const response = await fetch(url, { headers });
const fallback = response.clone();
try {
return {
data: await response.json(),
response,
};
} catch {
console.log(`Usage: HTTP ${response.status} ${response.statusText}`);
console.log(await fallback.text());
process.exit(response.ok ? 0 : 1);
}
};
const formatDate = (value) => {
if (!value) return "unknown";
return new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
}).format(new Date(value));
};
const clampPercent = (value) => Math.max(0, Math.min(100, Math.round(Number(value) || 0)));
const formatDuration = (seconds) => {
if (typeof seconds !== "number" || !Number.isFinite(seconds)) return "unknown";
const rounded = Math.max(0, Math.round(seconds));
const days = Math.floor(rounded / 86400);
const hours = Math.floor((rounded % 86400) / 3600);
const minutes = Math.floor((rounded % 3600) / 60);
if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
if (minutes > 0) return `${minutes}m`;
return `${rounded}s`;
};
const formatUnixSeconds = (value) => {
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
return formatDate(new Date(value * 1000));
};
const limitName = (window, fallback) => {
const seconds = window?.limit_window_seconds;
if (seconds === 5 * 60 * 60) return "5-hour";
if (seconds === 7 * 24 * 60 * 60) return "Weekly";
if (typeof seconds !== "number" || !Number.isFinite(seconds)) return fallback;
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
if (seconds < 86400) return `${Math.round(seconds / 3600)}-hour`;
return `${Math.round(seconds / 86400)}-day`;
};
const usageBar = (leftPercent) => {
const width = 20;
const filled = Math.round((leftPercent / 100) * width);
return `[${"#".repeat(filled)}${".".repeat(width - filled)}]`;
};
const renderLimit = (label, window) => {
if (!window) {
console.log(`${label.padEnd(8)} unavailable`);
return;
}
const usedPercent = clampPercent(window.used_percent);
const leftPercent = 100 - usedPercent;
const resetIn = formatDuration(window.reset_after_seconds);
const resetAt = formatUnixSeconds(window.reset_at);
console.log(
`${label.padEnd(8)} ${String(leftPercent).padStart(3)}% left ${usageBar(leftPercent)} ${String(usedPercent).padStart(3)}% used resets in ${resetIn} (${resetAt})`
);
};
const { data, response } = await fetchJson(USAGE_URL);
const rateLimit = data.rate_limit;
console.log("Codex usage limits");
if (data.plan_type) {
console.log(`Plan: ${data.plan_type}`);
}
console.log("");
renderLimit(limitName(rateLimit?.primary_window, "Primary"), rateLimit?.primary_window);
renderLimit(limitName(rateLimit?.secondary_window, "Secondary"), rateLimit?.secondary_window);
console.log("");
console.log(`Fetch: HTTP ${response.status} ${response.statusText}`);
if (!response.ok) {
process.exit(1);
}
JS
#!/usr/bin/env bash
set -euo pipefail
node --input-type=module <<'JS'
import { homedir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
const RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
// Read the same local auth file used by Codex Desktop.
const authPath = join(homedir(), ".codex", "auth.json");
const { default: auth } = await import(pathToFileURL(authPath).href, {
with: { type: "json" },
});
const token = auth.tokens?.access_token;
const account = auth.tokens?.account_id;
if (!token || !account) {
throw new Error("Missing tokens.access_token or tokens.account_id in ~/.codex/auth.json");
}
const headers = {
Authorization: `Bearer ${token}`,
"ChatGPT-Account-ID": account,
"OpenAI-Beta": "codex-1",
originator: "Codex Desktop",
};
const fetchJson = async (label, url) => {
const response = await fetch(url, { headers });
const fallback = response.clone();
try {
return {
data: await response.json(),
response,
};
} catch {
console.log(`${label}: HTTP ${response.status} ${response.statusText}`);
console.log(await fallback.text());
process.exit(response.ok ? 0 : 1);
}
};
const formatDate = (value) => {
if (!value) return "None";
return new Intl.DateTimeFormat(undefined, {
dateStyle: "medium",
timeStyle: "short",
}).format(new Date(value));
};
const parseDate = (value) => {
if (!value) return null;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
};
const clampPercent = (value) => Math.max(0, Math.min(100, Math.round(Number(value) || 0)));
const formatDuration = (seconds) => {
if (typeof seconds !== "number" || !Number.isFinite(seconds)) return "unknown";
const rounded = Math.max(0, Math.round(seconds));
const days = Math.floor(rounded / 86400);
const hours = Math.floor((rounded % 86400) / 3600);
const minutes = Math.floor((rounded % 3600) / 60);
if (days > 0) return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
if (hours > 0) return minutes > 0 ? `${hours}h ${minutes}m` : `${hours}h`;
if (minutes > 0) return `${minutes}m`;
return `${rounded}s`;
};
const formatUnixSeconds = (value) => {
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown";
return formatDate(new Date(value * 1000));
};
const limitName = (window, fallback) => {
const seconds = window?.limit_window_seconds;
if (seconds === 5 * 60 * 60) return "5-hour";
if (seconds === 7 * 24 * 60 * 60) return "Weekly";
if (typeof seconds !== "number" || !Number.isFinite(seconds)) return fallback;
if (seconds < 3600) return `${Math.round(seconds / 60)}m`;
if (seconds < 86400) return `${Math.round(seconds / 3600)}-hour`;
return `${Math.round(seconds / 86400)}-day`;
};
const usageBar = (leftPercent) => {
const width = 20;
const filled = Math.round((leftPercent / 100) * width);
return `[${"#".repeat(filled)}${".".repeat(width - filled)}]`;
};
const daysUntil = (value) => {
const date = parseDate(value);
if (!date) return "";
const ms = date.getTime() - Date.now();
if (ms < 0) return "expired";
const days = Math.ceil(ms / (1000 * 60 * 60 * 24));
if (days === 0) return "expires today";
if (days === 1) return "expires tomorrow";
return `${days} days left`;
};
const daysUntilShort = (value) => {
const label = daysUntil(value);
if (label === "expires today") return "today";
if (label === "expires tomorrow") return "tomorrow";
if (label === "expired") return label;
return label.replace(" days left", "d");
};
const renderLimit = (label, window) => {
if (!window) {
console.log(`${label.padEnd(8)} unavailable`);
return;
}
const usedPercent = clampPercent(window.used_percent);
const leftPercent = 100 - usedPercent;
const resetIn = formatDuration(window.reset_after_seconds);
const resetAt = formatUnixSeconds(window.reset_at);
console.log(
`${label.padEnd(8)} ${String(leftPercent).padStart(3)}% left ${usageBar(leftPercent)} ${String(usedPercent).padStart(3)}% used resets in ${resetIn} (${resetAt})`
);
};
const renderResetCreditSummary = (resetCredits) => {
if (!Array.isArray(resetCredits.credits)) {
console.log(JSON.stringify(resetCredits, null, 2));
return [];
}
const credits = resetCredits.credits;
const availableCredits = credits
.filter((credit) => credit.status === "available")
.sort((a, b) => {
const aExpires = parseDate(a.expires_at)?.getTime() ?? Number.POSITIVE_INFINITY;
const bExpires = parseDate(b.expires_at)?.getTime() ?? Number.POSITIVE_INFINITY;
return aExpires - bExpires;
});
const redeemedCredits = credits.filter((credit) => credit.redeemed_at || credit.status === "redeemed");
const availableCount = resetCredits.available_count ?? availableCredits.length;
const soonestAvailable = availableCredits[0];
const expirySummary = availableCredits.map((credit) => daysUntilShort(credit.expires_at)).filter(Boolean);
console.log(`Available now: ${availableCount}`);
if (soonestAvailable) {
console.log(`Use soonest: ${daysUntil(soonestAvailable.expires_at)} - ${formatDate(soonestAvailable.expires_at)}`);
console.log(`Available expiries: ${expirySummary.join(", ")}`);
} else {
console.log("Use soonest: none available");
}
console.log(`Other: ${redeemedCredits.length} redeemed, ${credits.length} total listed`);
return [...availableCredits, ...credits.filter((credit) => credit.status !== "available")];
};
const shortCreditId = (credit) =>
credit.id?.replace(/^RateLimitResetCredit_/, "").slice(-8) ?? "unknown";
const renderCreditDetails = (credit, index) => {
console.log(`${index + 1}. ${credit.title ?? "Rate limit reset"}`);
console.log(` Status: ${credit.status ?? "unknown"}`);
console.log(` Source: ${credit.profile_user_id ?? "unknown"}`);
console.log(` Granted: ${formatDate(credit.granted_at)}`);
console.log(` Expires: ${formatDate(credit.expires_at)} (${daysUntil(credit.expires_at)})`);
console.log(` Redeemed: ${formatDate(credit.redeemed_at)}`);
console.log(` Credit ID: ...${shortCreditId(credit)}`);
if (credit.description) {
console.log(` Note: ${credit.description}`);
}
console.log("");
};
const [usageResult, resetCreditsResult] = await Promise.all([
fetchJson("Usage", USAGE_URL),
fetchJson("Reset credits", RESET_CREDITS_URL),
]);
const usage = usageResult.data;
const rateLimit = usage.rate_limit;
console.log("Codex status");
if (usage.plan_type) {
console.log(`Plan: ${usage.plan_type}`);
}
console.log("");
console.log("Usage limits");
renderLimit(limitName(rateLimit?.primary_window, "Primary"), rateLimit?.primary_window);
renderLimit(limitName(rateLimit?.secondary_window, "Secondary"), rateLimit?.secondary_window);
console.log("");
console.log("Reset credits");
const orderedCredits = renderResetCreditSummary(resetCreditsResult.data);
console.log("");
console.log(
`Fetch: usage HTTP ${usageResult.response.status} ${usageResult.response.statusText}; reset credits HTTP ${resetCreditsResult.response.status} ${resetCreditsResult.response.statusText}`
);
if (orderedCredits.length > 0) {
console.log("");
console.log("Reset credit details");
console.log("");
orderedCredits.forEach(renderCreditDetails);
}
if (!usageResult.response.ok || !resetCreditsResult.response.ok) {
process.exit(1);
}
JS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment