()
| 90 | } |
| 91 | |
| 92 | async function readCodexUsage(): Promise<HarnessUsage> { |
| 93 | const authFile = path.join(os.homedir(), ".codex", "auth.json"); |
| 94 | if (!fs.existsSync(authFile)) { |
| 95 | return { kind: "codex", plan: null, email: null, rateLimit: null }; |
| 96 | } |
| 97 | const auth = JSON.parse(fs.readFileSync(authFile, "utf-8")) as { |
| 98 | tokens?: { access_token?: string; id_token?: string }; |
| 99 | }; |
| 100 | const accessToken = auth.tokens?.access_token; |
| 101 | if (!accessToken) { |
| 102 | return { kind: "codex", plan: null, email: null, rateLimit: null }; |
| 103 | } |
| 104 | |
| 105 | // Decode just to pull the email + chatgpt account id (needed as the |
| 106 | // ChatGPT-Account-Id header so the backend scopes the response to |
| 107 | // the right workspace). |
| 108 | const { email, accountId } = decodeIdToken(auth.tokens?.id_token); |
| 109 | |
| 110 | let plan: string | null = null; |
| 111 | let rateLimit: CodexUsage["rateLimit"] = null; |
| 112 | try { |
| 113 | const ctrl = new AbortController(); |
| 114 | const t = setTimeout(() => ctrl.abort(), 4000); |
| 115 | const headers: Record<string, string> = { |
| 116 | Authorization: `Bearer ${accessToken}`, |
| 117 | Accept: "application/json", |
| 118 | }; |
| 119 | if (accountId) headers["ChatGPT-Account-Id"] = accountId; |
| 120 | const res = await fetch("https://chatgpt.com/backend-api/wham/usage", { |
| 121 | headers, |
| 122 | signal: ctrl.signal, |
| 123 | }); |
| 124 | clearTimeout(t); |
| 125 | if (res.ok) { |
| 126 | const body = (await res.json()) as { |
| 127 | plan_type?: string; |
| 128 | rate_limit?: { |
| 129 | primary_window?: RateLimitWindow; |
| 130 | secondary_window?: RateLimitWindow; |
| 131 | }; |
| 132 | }; |
| 133 | if (typeof body.plan_type === "string") plan = body.plan_type; |
| 134 | const p = body.rate_limit?.primary_window; |
| 135 | const s = body.rate_limit?.secondary_window; |
| 136 | if (p && s) { |
| 137 | rateLimit = { primary: p, secondary: s }; |
| 138 | } |
| 139 | } |
| 140 | } catch { |
| 141 | // Network error / abort — leave rateLimit null; the footer falls |
| 142 | // back to the plan chip only. |
| 143 | } |
| 144 | |
| 145 | return { kind: "codex", plan, email, rateLimit }; |
| 146 | } |
| 147 | |
| 148 | function decodeIdToken(idToken: string | undefined): { |
| 149 | email: string | null; |
no test coverage detected