-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add /codex:usage command to show rate limits and usage #152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
abd9131
37e13f1
4320482
f3f0b19
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| --- | ||
| description: Show Codex rate limits and usage for your current plan | ||
| argument-hint: '[--json]' | ||
| disable-model-invocation: true | ||
| allowed-tools: Bash(node:*) | ||
| --- | ||
|
|
||
| !`node "${CLAUDE_PLUGIN_ROOT}/scripts/codex-companion.mjs" usage $ARGUMENTS` | ||
|
|
||
| Present the command output to the user as-is. Do not summarize or condense it. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -445,6 +445,97 @@ export function renderStoredJobResult(job, storedJob) { | |
| return `${lines.join("\n").trimEnd()}\n`; | ||
| } | ||
|
|
||
| function formatPercent(value) { | ||
| if (typeof value !== "number" || Number.isNaN(value)) { | ||
| return "unknown"; | ||
| } | ||
| return `${Math.round(value)}%`; | ||
| } | ||
|
|
||
| function formatResetTime(resetAt) { | ||
| if (!resetAt) { | ||
| return ""; | ||
| } | ||
| try { | ||
| const date = new Date(resetAt); | ||
| return ` (resets ${date.toLocaleDateString("en-US", { day: "numeric", month: "short" })}, ${date.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", hour12: false })})`; | ||
| } catch { | ||
| return ""; | ||
| } | ||
| } | ||
|
|
||
| function renderRateLimitWindow(label, window) { | ||
| if (!window) { | ||
| return null; | ||
| } | ||
| const usedPercent = typeof window.used_percent === "number" ? window.used_percent : null; | ||
| const remaining = usedPercent != null ? formatPercent(100 - usedPercent) : "unknown"; | ||
| const reset = formatResetTime(window.reset_at); | ||
| return `- ${label}: ${remaining} left${reset}`; | ||
| } | ||
|
|
||
| export function renderUsageReport(report) { | ||
| if (!report.ok) { | ||
| return `# Codex Usage\n\nError: ${report.error}\n`; | ||
| } | ||
|
|
||
| const data = report.data ?? {}; | ||
| const planType = report.planType ?? data.plan_type ?? "unknown"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| const lines = [ | ||
| "# Codex Usage", | ||
| "", | ||
| `Plan: ${planType}` | ||
| ]; | ||
|
|
||
| const rateLimit = data.rate_limit; | ||
| const codeReviewLimit = data.code_review_rate_limit; | ||
| const credits = data.credits; | ||
|
|
||
| const limitLines = []; | ||
|
|
||
| if (rateLimit) { | ||
| const primary = renderRateLimitWindow("Primary limit", rateLimit.primary_window); | ||
| if (primary) { | ||
| limitLines.push(primary); | ||
| } | ||
| const secondary = renderRateLimitWindow("Weekly limit", rateLimit.secondary_window); | ||
| if (secondary) { | ||
| limitLines.push(secondary); | ||
| } | ||
| } | ||
|
|
||
| if (codeReviewLimit) { | ||
| const reviewPrimary = renderRateLimitWindow("Code review limit", codeReviewLimit.primary_window); | ||
| if (reviewPrimary) { | ||
| limitLines.push(reviewPrimary); | ||
| } | ||
| const reviewSecondary = renderRateLimitWindow("Code review weekly limit", codeReviewLimit.secondary_window); | ||
| if (reviewSecondary) { | ||
| limitLines.push(reviewSecondary); | ||
| } | ||
| } | ||
|
|
||
| if (limitLines.length > 0) { | ||
| lines.push("", "Limits:"); | ||
| lines.push(...limitLines); | ||
| } | ||
|
|
||
| if (credits) { | ||
| lines.push(""); | ||
| if (credits.unlimited) { | ||
| lines.push("Credits: unlimited"); | ||
| } else if (credits.has_credits && credits.balance != null) { | ||
| lines.push(`Credits: $${credits.balance.toFixed(2)} remaining`); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||
| } else if (credits.has_credits) { | ||
| lines.push("Credits: available"); | ||
| } else { | ||
| lines.push("Credits: none"); | ||
| } | ||
| } | ||
|
|
||
| return `${lines.join("\n").trimEnd()}\n`; | ||
| } | ||
|
|
||
| export function renderCancelReport(job) { | ||
| const lines = [ | ||
| "# Codex Cancel", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetchCodexUsagetreats a missingauth.jsonfile as unauthenticated, but Codex logins are not guaranteed to live at that path (e.g., keychain-backed auth in default auto mode, or relocated state viaCODEX_HOME). In those setups users are genuinely logged in, yet/codex:usagewill always return the “auth.json not found” error and never reach the API call. Please load auth from Codex’s configured credential store (or obtain a token via Codex itself) instead of hard-failing on file absence.Useful? React with 👍 / 👎.