-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcodex-reset-dates.js
More file actions
executable file
·89 lines (76 loc) · 2.92 KB
/
Copy pathcodex-reset-dates.js
File metadata and controls
executable file
·89 lines (76 loc) · 2.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env node
const fs = require("node:fs/promises");
const os = require("node:os");
const path = require("node:path");
const ENDPOINT = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
const AUTH_CLAIM = "https://api.openai.com/auth";
const expandHome = (value) => value.replace(/^~(?=$|\/)/, os.homedir());
const authPath = path.join(expandHome(process.env.CODEX_HOME || "~/.codex"), "auth.json");
const dateFormat = new Intl.DateTimeFormat(undefined, {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
function accountIdFromJwt(token) {
try {
const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString("utf8"));
return payload[AUTH_CLAIM]?.chatgpt_account_id;
} catch {
return undefined;
}
}
async function authHeaders() {
try {
const { tokens = {} } = JSON.parse(await fs.readFile(authPath, "utf8"));
const accessToken = tokens.access_token || tokens.accessToken;
const accountId =
accountIdFromJwt(tokens.id_token || tokens.idToken) ||
accountIdFromJwt(accessToken) ||
tokens.account_id ||
tokens.accountId;
if (!accessToken) throw new Error("missing_access_token");
return {
Authorization: `Bearer ${accessToken}`,
originator: "Codex Desktop",
"OAI-Product-Sku": "CODEX",
Accept: "application/json",
...(accountId ? { "ChatGPT-Account-Id": accountId } : {}),
};
} catch (error) {
if (error.code === "ENOENT") {
throw new Error(`Could not find Codex login at ${authPath}. Open Codex Desktop and sign in first.`);
}
throw new Error(`Could not read Codex login at ${authPath}. Open Codex Desktop and sign in again.`);
}
}
function formatDate(value) {
if (!value) return "no expiry date returned";
const date = new Date(value);
return Number.isNaN(date.getTime()) ? value : dateFormat.format(date);
}
async function main() {
const response = await fetch(ENDPOINT, { headers: await authHeaders() });
if (!response.ok) throw new Error(`Codex endpoint returned HTTP ${response.status}.`);
const body = await response.json();
const credits = Array.isArray(body.credits) ? body.credits : [];
const available = credits.filter((credit) => String(credit.status).toLowerCase() === "available");
const availableCount = Number.isInteger(body.available_count) ? body.available_count : available.length;
if (available.length === 0) {
console.log(`No banked reset credits found. Available count: ${availableCount}`);
return;
}
for (const [index, credit] of available.entries()) {
console.log(`Reset ${index + 1}: ${formatDate(credit.expires_at || credit.expiresAt)}`);
}
if (availableCount !== available.length) {
console.log(`Available count reported by Codex: ${availableCount}`);
}
}
main().catch((error) => {
console.error(error.message || error);
process.exitCode = 1;
});