@simonesiega/codex-limits 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +10 -13
- package/CHANGELOG.md +60 -13
- package/LICENSE +21 -21
- package/README.md +136 -49
- package/SECURITY.md +96 -0
- package/dist/cli.js +173 -42823
- package/dist/index.js +3 -1078
- package/docs/photos/terminal/final_result_small.png +0 -0
- package/package.json +12 -8
- package/scripts/postinstall.cjs +15 -6
- package/types/index.d.ts +2 -3
- /package/docs/photos/terminal/{final_result.png → final_result_large.png} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,1078 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import { stat } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { join, normalize } from "node:path";
|
|
5
|
-
|
|
6
|
-
// src/package/core/utils/env.ts
|
|
7
|
-
function readEnvValue(env, key) {
|
|
8
|
-
const value = env[key]?.trim();
|
|
9
|
-
return value && value.length > 0 ? value : null;
|
|
10
|
-
}
|
|
11
|
-
function resolveEnvironment(env) {
|
|
12
|
-
return env ?? process.env;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// src/package/core/codex/paths.ts
|
|
16
|
-
var CODEX_LIMITS_HOME = "CODEX_LIMITS_HOME";
|
|
17
|
-
function getCodexHomeCandidatePaths(options = {}) {
|
|
18
|
-
const env = resolveEnvironment(options.env);
|
|
19
|
-
const home = options.homeDirectory ?? readEnvValue(env, "HOME") ?? readEnvValue(env, "USERPROFILE") ?? homedir();
|
|
20
|
-
const appData = options.appData ?? readEnvValue(env, "APPDATA");
|
|
21
|
-
const localAppData = options.localAppData ?? readEnvValue(env, "LOCALAPPDATA");
|
|
22
|
-
const paths = [];
|
|
23
|
-
const overrideHome = readEnvValue(env, CODEX_LIMITS_HOME);
|
|
24
|
-
const codexHome = readEnvValue(env, "CODEX_HOME");
|
|
25
|
-
appendCandidate(paths, overrideHome, "env");
|
|
26
|
-
appendCandidate(paths, codexHome, "env");
|
|
27
|
-
if (home) {
|
|
28
|
-
appendCandidate(paths, join(home, ".codex"), "default");
|
|
29
|
-
appendCandidate(paths, join(home, ".config", "codex"), "default");
|
|
30
|
-
appendCandidate(paths, join(home, "Library", "Application Support", "Codex"), "default");
|
|
31
|
-
appendCandidate(paths, join(home, "Library", "Application Support", "Parall", "Codex", ".codex"), "default");
|
|
32
|
-
}
|
|
33
|
-
appendCandidate(paths, appData ? join(appData, "Codex") : null, "default");
|
|
34
|
-
appendCandidate(paths, localAppData ? join(localAppData, "Codex") : null, "default");
|
|
35
|
-
return dedupePaths(paths);
|
|
36
|
-
}
|
|
37
|
-
async function detectCodexHome(options = {}) {
|
|
38
|
-
const env = resolveEnvironment(options.env);
|
|
39
|
-
const overrideHome = readEnvValue(env, CODEX_LIMITS_HOME);
|
|
40
|
-
const candidates = await Promise.all(getCodexHomeCandidatePaths(options).map(async (candidate) => ({
|
|
41
|
-
...candidate,
|
|
42
|
-
exists: await canReadDirectory(candidate.path)
|
|
43
|
-
})));
|
|
44
|
-
return {
|
|
45
|
-
overrideHome: overrideHome ? normalize(overrideHome) : null,
|
|
46
|
-
candidates,
|
|
47
|
-
foundHome: candidates.find((candidate) => candidate.exists)?.path ?? null
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function appendCandidate(paths, path, source) {
|
|
51
|
-
if (!path) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
paths.push({ path: normalize(path), source });
|
|
55
|
-
}
|
|
56
|
-
async function canReadDirectory(path) {
|
|
57
|
-
try {
|
|
58
|
-
const details = await stat(path);
|
|
59
|
-
return details.isDirectory();
|
|
60
|
-
} catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function dedupePaths(paths) {
|
|
65
|
-
const seen = new Set;
|
|
66
|
-
const result = [];
|
|
67
|
-
for (const candidate of paths) {
|
|
68
|
-
const normalizedPath = normalize(candidate.path);
|
|
69
|
-
const key = normalizedPath.toLowerCase();
|
|
70
|
-
if (seen.has(key)) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
seen.add(key);
|
|
74
|
-
result.push({ path: normalizedPath, source: candidate.source });
|
|
75
|
-
}
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// src/package/core/codex/session-reader.ts
|
|
80
|
-
import { createReadStream } from "node:fs";
|
|
81
|
-
import { readdir, stat as stat2 } from "node:fs/promises";
|
|
82
|
-
import { join as join2, relative } from "node:path";
|
|
83
|
-
import { createInterface } from "node:readline";
|
|
84
|
-
var MAX_SESSION_DEPTH = 8;
|
|
85
|
-
var MAX_SESSION_FILES_TO_PARSE = 20;
|
|
86
|
-
var MAX_SESSION_FILE_BYTES = 25000000;
|
|
87
|
-
var ROLLOUT_FILE_PATTERN = /^rollout-.*\.jsonl$/i;
|
|
88
|
-
async function readCodexSessions(homePath) {
|
|
89
|
-
const sessionsRoot = join2(homePath, "sessions");
|
|
90
|
-
const warnings = [];
|
|
91
|
-
const candidates = await findSessionFiles(homePath, sessionsRoot, warnings);
|
|
92
|
-
const files = [];
|
|
93
|
-
let latestSnapshot = null;
|
|
94
|
-
for (const candidate of candidates.slice(0, MAX_SESSION_FILES_TO_PARSE)) {
|
|
95
|
-
const relativePath = relative(homePath, candidate.path);
|
|
96
|
-
if (candidate.size > MAX_SESSION_FILE_BYTES) {
|
|
97
|
-
warnings.push(`Skipped ${relativePath} because it is too large to inspect safely.`);
|
|
98
|
-
files.push(toSessionFile(candidate.path, relativePath, candidate.modifiedAtMs, false, "too-large"));
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
try {
|
|
102
|
-
const snapshot = await extractSnapshotFromSessionFile(homePath, candidate.path);
|
|
103
|
-
files.push(toSessionFile(candidate.path, relativePath, candidate.modifiedAtMs, snapshot !== null, null));
|
|
104
|
-
if (snapshot && !latestSnapshot) {
|
|
105
|
-
latestSnapshot = snapshot;
|
|
106
|
-
}
|
|
107
|
-
} catch {
|
|
108
|
-
warnings.push(`Could not inspect ${relativePath}.`);
|
|
109
|
-
files.push(toSessionFile(candidate.path, relativePath, candidate.modifiedAtMs, false, "read-error"));
|
|
110
|
-
}
|
|
111
|
-
if (latestSnapshot) {
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
if (candidates.length > MAX_SESSION_FILES_TO_PARSE) {
|
|
116
|
-
warnings.push(`Skipped ${candidates.length - MAX_SESSION_FILES_TO_PARSE} older session files to keep inspection small.`);
|
|
117
|
-
}
|
|
118
|
-
if (candidates.length > 0 && !latestSnapshot) {
|
|
119
|
-
warnings.push("No token-count rate-limit snapshot was found in local Codex session logs.");
|
|
120
|
-
}
|
|
121
|
-
return { homePath, sessionsRoot, files, latestSnapshot, warnings };
|
|
122
|
-
}
|
|
123
|
-
async function findSessionFiles(homePath, sessionsRoot, warnings) {
|
|
124
|
-
const files = [];
|
|
125
|
-
await walkSessions(homePath, sessionsRoot, 0, files, warnings);
|
|
126
|
-
const candidates = (await Promise.all(files.map((path) => statSessionFile(homePath, path, warnings)))).filter(isSessionCandidate);
|
|
127
|
-
return candidates.sort((left, right) => right.modifiedAtMs - left.modifiedAtMs);
|
|
128
|
-
}
|
|
129
|
-
async function statSessionFile(homePath, path, warnings) {
|
|
130
|
-
try {
|
|
131
|
-
const details = await stat2(path);
|
|
132
|
-
return { path, modifiedAtMs: details.mtimeMs, size: details.size };
|
|
133
|
-
} catch {
|
|
134
|
-
warnings.push(`Could not inspect ${relative(homePath, path)}.`);
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
function isSessionCandidate(value) {
|
|
139
|
-
return value !== null;
|
|
140
|
-
}
|
|
141
|
-
async function walkSessions(homePath, currentPath, depth, files, warnings) {
|
|
142
|
-
if (depth > MAX_SESSION_DEPTH) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
let entries;
|
|
146
|
-
try {
|
|
147
|
-
entries = await readdir(currentPath, { withFileTypes: true });
|
|
148
|
-
} catch {
|
|
149
|
-
if (depth > 0) {
|
|
150
|
-
warnings.push(`Could not inspect ${relative(homePath, currentPath)}.`);
|
|
151
|
-
}
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
for (const entry of entries) {
|
|
155
|
-
const entryPath = join2(currentPath, entry.name);
|
|
156
|
-
if (entry.isDirectory()) {
|
|
157
|
-
await walkSessions(homePath, entryPath, depth + 1, files, warnings);
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (entry.isFile() && ROLLOUT_FILE_PATTERN.test(entry.name)) {
|
|
161
|
-
files.push(entryPath);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
async function extractSnapshotFromSessionFile(homePath, sessionFile) {
|
|
166
|
-
const relativePath = relative(homePath, sessionFile);
|
|
167
|
-
const reader = createInterface({
|
|
168
|
-
input: createReadStream(sessionFile, { encoding: "utf8" }),
|
|
169
|
-
crlfDelay: Infinity
|
|
170
|
-
});
|
|
171
|
-
let threadId = null;
|
|
172
|
-
let latest = null;
|
|
173
|
-
for await (const rawLine of reader) {
|
|
174
|
-
const entry = parseJsonLine(rawLine);
|
|
175
|
-
if (!entry) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
const metadataThreadId = readSessionThreadId(entry);
|
|
179
|
-
if (metadataThreadId) {
|
|
180
|
-
threadId = metadataThreadId;
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const rateLimits = readRateLimits(entry);
|
|
184
|
-
if (!rateLimits) {
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
latest = {
|
|
188
|
-
sessionFile,
|
|
189
|
-
relativePath,
|
|
190
|
-
threadId,
|
|
191
|
-
eventTimestamp: readString(entry, "timestamp"),
|
|
192
|
-
rateLimits
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
return latest;
|
|
196
|
-
}
|
|
197
|
-
function parseJsonLine(rawLine) {
|
|
198
|
-
const line = rawLine.trim();
|
|
199
|
-
if (!line) {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
try {
|
|
203
|
-
const parsed = JSON.parse(line);
|
|
204
|
-
return isRecord(parsed) ? parsed : null;
|
|
205
|
-
} catch {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
function readSessionThreadId(entry) {
|
|
210
|
-
if (entry.type !== "session_meta" || !isRecord(entry.payload)) {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
return readString(entry.payload, "id");
|
|
214
|
-
}
|
|
215
|
-
function readRateLimits(entry) {
|
|
216
|
-
if (entry.type !== "event_msg" || !isRecord(entry.payload)) {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
if (entry.payload.type !== "token_count" || !isRecord(entry.payload.rate_limits)) {
|
|
220
|
-
return null;
|
|
221
|
-
}
|
|
222
|
-
return entry.payload.rate_limits;
|
|
223
|
-
}
|
|
224
|
-
function toSessionFile(path, relativePath, modifiedAtMs, hasSnapshot, error) {
|
|
225
|
-
return { path, relativePath, modifiedAtMs, hasSnapshot, error };
|
|
226
|
-
}
|
|
227
|
-
function readString(value, key) {
|
|
228
|
-
const field = value[key];
|
|
229
|
-
return typeof field === "string" && field.length > 0 ? field : null;
|
|
230
|
-
}
|
|
231
|
-
function isRecord(value) {
|
|
232
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// src/package/core/codex/state-reader.ts
|
|
236
|
-
import { readdir as readdir2, readFile, stat as stat3 } from "node:fs/promises";
|
|
237
|
-
import { extname, join as join3, relative as relative2 } from "node:path";
|
|
238
|
-
var MAX_DEPTH = 2;
|
|
239
|
-
var MAX_FILES = 25;
|
|
240
|
-
var MAX_FILE_BYTES = 1e6;
|
|
241
|
-
var SENSITIVE_FILE_PATTERN = /(?:auth|token|cookie|session|secret|credential|api[-_]?key|keychain)/i;
|
|
242
|
-
async function readCodexState(homePath) {
|
|
243
|
-
const warnings = [];
|
|
244
|
-
const paths = await findReadableStateFiles(homePath, warnings);
|
|
245
|
-
const files = [];
|
|
246
|
-
for (const filePath of paths.slice(0, MAX_FILES)) {
|
|
247
|
-
const relativePath = relative2(homePath, filePath);
|
|
248
|
-
try {
|
|
249
|
-
const details = await stat3(filePath);
|
|
250
|
-
if (details.size > MAX_FILE_BYTES) {
|
|
251
|
-
warnings.push(`Skipped ${relativePath} because it is too large to inspect safely.`);
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
const content = await readFile(filePath, "utf8");
|
|
255
|
-
const json = parseJson(content, relativePath, warnings);
|
|
256
|
-
files.push({
|
|
257
|
-
path: filePath,
|
|
258
|
-
relativePath,
|
|
259
|
-
json: json.value,
|
|
260
|
-
error: json.error
|
|
261
|
-
});
|
|
262
|
-
} catch {
|
|
263
|
-
warnings.push(`Could not read ${relativePath}.`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (paths.length > MAX_FILES) {
|
|
267
|
-
warnings.push(`Skipped ${paths.length - MAX_FILES} extra files to keep inspection small.`);
|
|
268
|
-
}
|
|
269
|
-
return { homePath, files, warnings };
|
|
270
|
-
}
|
|
271
|
-
async function findReadableStateFiles(homePath, warnings) {
|
|
272
|
-
const files = [];
|
|
273
|
-
await walk(homePath, homePath, 0, files, warnings);
|
|
274
|
-
return files.sort((left, right) => left.localeCompare(right));
|
|
275
|
-
}
|
|
276
|
-
async function walk(rootPath, currentPath, depth, files, warnings) {
|
|
277
|
-
if (depth > MAX_DEPTH || files.length >= MAX_FILES) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
let entries;
|
|
281
|
-
try {
|
|
282
|
-
entries = await readdir2(currentPath, { withFileTypes: true });
|
|
283
|
-
} catch {
|
|
284
|
-
warnings.push(`Could not inspect ${relative2(rootPath, currentPath) || "."}.`);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
for (const entry of entries) {
|
|
288
|
-
if (files.length >= MAX_FILES) {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const filePath = join3(currentPath, entry.name);
|
|
292
|
-
if (isSensitiveFileName(entry.name)) {
|
|
293
|
-
warnings.push("Skipped a sensitive-looking local file.");
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
if (entry.isDirectory()) {
|
|
297
|
-
await walk(rootPath, filePath, depth + 1, files, warnings);
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
if (entry.isFile() && extname(entry.name).toLowerCase() === ".json") {
|
|
301
|
-
files.push(filePath);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function isSensitiveFileName(fileName) {
|
|
306
|
-
return SENSITIVE_FILE_PATTERN.test(fileName);
|
|
307
|
-
}
|
|
308
|
-
function parseJson(content, relativePath, warnings) {
|
|
309
|
-
try {
|
|
310
|
-
return { value: JSON.parse(content), error: null };
|
|
311
|
-
} catch {
|
|
312
|
-
warnings.push(`Could not parse JSON in ${relativePath}.`);
|
|
313
|
-
return { value: null, error: "invalid-json" };
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// src/package/core/auth/codex-auth.ts
|
|
318
|
-
import { readFile as readFile2 } from "node:fs/promises";
|
|
319
|
-
import { join as join4, normalize as normalize2 } from "node:path";
|
|
320
|
-
async function resolveCodexCredentials(options) {
|
|
321
|
-
const env = resolveEnvironment(options.env);
|
|
322
|
-
const accessToken = readEnvValue(env, "CODEX_LIMITS_ACCESS_TOKEN");
|
|
323
|
-
const accountId = readEnvValue(env, "CODEX_LIMITS_ACCOUNT_ID");
|
|
324
|
-
if (accessToken || accountId) {
|
|
325
|
-
return accessToken && accountId ? { accessToken, accountId } : null;
|
|
326
|
-
}
|
|
327
|
-
const authFile = await resolveCodexAuthFile(options);
|
|
328
|
-
return authFile ? readAuthFile(authFile) : null;
|
|
329
|
-
}
|
|
330
|
-
async function resolveCodexAuthFile(options) {
|
|
331
|
-
if (options.authFile) {
|
|
332
|
-
return normalize2(options.authFile);
|
|
333
|
-
}
|
|
334
|
-
const detection = await detectCodexHome(options);
|
|
335
|
-
return detection.foundHome ? join4(detection.foundHome, "auth.json") : null;
|
|
336
|
-
}
|
|
337
|
-
async function readAuthFile(authPath) {
|
|
338
|
-
try {
|
|
339
|
-
const parsed = JSON.parse(await readFile2(authPath, "utf8"));
|
|
340
|
-
if (!isRecord2(parsed)) {
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
const tokens = isRecord2(parsed.tokens) ? parsed.tokens : parsed;
|
|
344
|
-
const accessToken = readString2(tokens, "access_token");
|
|
345
|
-
const accountId = readString2(tokens, "account_id");
|
|
346
|
-
return accessToken && accountId ? { accessToken, accountId } : null;
|
|
347
|
-
} catch {
|
|
348
|
-
return null;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
function readString2(value, key) {
|
|
352
|
-
const field = value[key];
|
|
353
|
-
return typeof field === "string" && field.length > 0 ? field : null;
|
|
354
|
-
}
|
|
355
|
-
function isRecord2(value) {
|
|
356
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// src/package/core/utils/date-time.ts
|
|
360
|
-
var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
361
|
-
var MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
362
|
-
function formatDuration(durationMs, options = {}) {
|
|
363
|
-
const includeSeconds = options.includeSeconds ?? false;
|
|
364
|
-
let remainingSeconds = Math.max(Math.floor(durationMs / 1000), 0);
|
|
365
|
-
const days = Math.floor(remainingSeconds / 86400);
|
|
366
|
-
remainingSeconds %= 86400;
|
|
367
|
-
const hours = Math.floor(remainingSeconds / 3600);
|
|
368
|
-
remainingSeconds %= 3600;
|
|
369
|
-
const minutes = Math.floor(remainingSeconds / 60);
|
|
370
|
-
const seconds = remainingSeconds % 60;
|
|
371
|
-
const parts = [];
|
|
372
|
-
if (days > 0) {
|
|
373
|
-
parts.push(`${days}d`);
|
|
374
|
-
}
|
|
375
|
-
if (hours > 0) {
|
|
376
|
-
parts.push(`${hours}h`);
|
|
377
|
-
}
|
|
378
|
-
if (minutes > 0) {
|
|
379
|
-
parts.push(`${minutes}m`);
|
|
380
|
-
}
|
|
381
|
-
if (includeSeconds && seconds > 0) {
|
|
382
|
-
parts.push(`${seconds}s`);
|
|
383
|
-
}
|
|
384
|
-
return parts.length > 0 ? parts.join(" ") : includeSeconds ? `${seconds}s` : "0m";
|
|
385
|
-
}
|
|
386
|
-
function parseDateValue(value) {
|
|
387
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
388
|
-
const timestampMs = value < 10000000000 ? value * 1000 : value;
|
|
389
|
-
const date = new Date(timestampMs);
|
|
390
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
391
|
-
}
|
|
392
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
393
|
-
const trimmed = value.trim();
|
|
394
|
-
const numericValue = Number(trimmed);
|
|
395
|
-
if (Number.isFinite(numericValue)) {
|
|
396
|
-
return parseDateValue(numericValue);
|
|
397
|
-
}
|
|
398
|
-
const date = new Date(trimmed);
|
|
399
|
-
return Number.isNaN(date.getTime()) ? null : date;
|
|
400
|
-
}
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
function formatLongDate(date) {
|
|
404
|
-
return `${DAY_NAMES[date.getDay()]} ${date.getDate()} ${MONTH_NAMES[date.getMonth()]} ${date.getFullYear()}`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// src/package/core/coupons/reset-coupons.ts
|
|
408
|
-
var LIVE_RESET_COUPONS_ENDPOINT = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
|
|
409
|
-
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
410
|
-
async function getResetCoupons(options = {}) {
|
|
411
|
-
const endpoint = options.endpoint ?? LIVE_RESET_COUPONS_ENDPOINT;
|
|
412
|
-
const credentials = await resolveCodexCredentials(options);
|
|
413
|
-
if (!credentials) {
|
|
414
|
-
return unavailableCoupons(endpoint, [
|
|
415
|
-
"Live reset coupons require a readable Codex auth.json file or CODEX_LIMITS_ACCESS_TOKEN and CODEX_LIMITS_ACCOUNT_ID."
|
|
416
|
-
]);
|
|
417
|
-
}
|
|
418
|
-
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
419
|
-
if (!fetchImplementation) {
|
|
420
|
-
return unavailableCoupons(endpoint, ["This runtime does not provide fetch for live reset coupon lookup."]);
|
|
421
|
-
}
|
|
422
|
-
const controller = new AbortController;
|
|
423
|
-
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
424
|
-
try {
|
|
425
|
-
const response = await fetchImplementation(endpoint, {
|
|
426
|
-
method: "GET",
|
|
427
|
-
headers: {
|
|
428
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
429
|
-
"ChatGPT-Account-ID": credentials.accountId,
|
|
430
|
-
"OpenAI-Beta": "codex-1",
|
|
431
|
-
originator: "Codex Desktop"
|
|
432
|
-
},
|
|
433
|
-
signal: controller.signal
|
|
434
|
-
});
|
|
435
|
-
if (!response.ok) {
|
|
436
|
-
return unavailableCoupons(endpoint, [`Live reset coupon endpoint returned HTTP ${response.status}.`]);
|
|
437
|
-
}
|
|
438
|
-
const payload = await response.json();
|
|
439
|
-
return parseResetCouponsPayload(payload, endpoint, options.now ?? new Date);
|
|
440
|
-
} catch {
|
|
441
|
-
return unavailableCoupons(endpoint, ["Live reset coupon lookup failed."]);
|
|
442
|
-
} finally {
|
|
443
|
-
clearTimeout(timeout);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
function unavailableCoupons(endpoint = LIVE_RESET_COUPONS_ENDPOINT, warnings = []) {
|
|
447
|
-
return {
|
|
448
|
-
status: "unavailable",
|
|
449
|
-
available: null,
|
|
450
|
-
earnedThisPeriod: null,
|
|
451
|
-
nextExpirationDate: null,
|
|
452
|
-
nextExpirationIn: null,
|
|
453
|
-
items: [],
|
|
454
|
-
warnings,
|
|
455
|
-
source: {
|
|
456
|
-
live: false,
|
|
457
|
-
label: "live Codex reset-credit endpoint",
|
|
458
|
-
endpoint
|
|
459
|
-
}
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
function parseResetCouponsPayload(payload, endpoint, now) {
|
|
463
|
-
if (!isRecord3(payload)) {
|
|
464
|
-
return unavailableCoupons(endpoint, ["Live reset coupon endpoint returned an unexpected payload."]);
|
|
465
|
-
}
|
|
466
|
-
const rawCredits = readArray(payload, ["credits", "reset_credits", "items"]);
|
|
467
|
-
const items = rawCredits.map((credit, index) => parseCouponItem(credit, index + 1, now)).filter((credit) => credit !== null).sort(compareCouponsByExpiry).map((credit, index) => ({ ...credit, index: index + 1 }));
|
|
468
|
-
const nextExpiring = items.find((item) => item.status === "available") ?? items[0] ?? null;
|
|
469
|
-
return {
|
|
470
|
-
status: "available",
|
|
471
|
-
available: readNumber(payload, ["available_count", "availableCount", "available"]),
|
|
472
|
-
earnedThisPeriod: readNumber(payload, ["total_earned_count", "earned_this_period", "earnedThisPeriod", "totalEarnedCount"]),
|
|
473
|
-
nextExpirationDate: nextExpiring?.expirationDate ?? null,
|
|
474
|
-
nextExpirationIn: nextExpiring?.expiresIn ?? null,
|
|
475
|
-
items,
|
|
476
|
-
warnings: [],
|
|
477
|
-
source: {
|
|
478
|
-
live: true,
|
|
479
|
-
label: "live Codex reset-credit endpoint",
|
|
480
|
-
endpoint
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
function parseCouponItem(value, index, now) {
|
|
485
|
-
if (!isRecord3(value)) {
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
const expiresAt = readString3(value, "expires_at") ?? readString3(value, "expiresAt");
|
|
489
|
-
const grantedAt = readString3(value, "granted_at") ?? readString3(value, "grantedAt");
|
|
490
|
-
const expiresAtDate = parseDateValue(expiresAt);
|
|
491
|
-
return {
|
|
492
|
-
index,
|
|
493
|
-
status: readString3(value, "status"),
|
|
494
|
-
grantedAt,
|
|
495
|
-
expiresAt,
|
|
496
|
-
expirationDate: expiresAtDate ? formatLongDate(expiresAtDate) : null,
|
|
497
|
-
expiresIn: expiresAtDate ? formatDuration(expiresAtDate.getTime() - now.getTime()) : null
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
function compareCouponsByExpiry(left, right) {
|
|
501
|
-
return dateSortValue(left.expiresAt) - dateSortValue(right.expiresAt);
|
|
502
|
-
}
|
|
503
|
-
function dateSortValue(value) {
|
|
504
|
-
const date = parseDateValue(value);
|
|
505
|
-
return date ? date.getTime() : Number.POSITIVE_INFINITY;
|
|
506
|
-
}
|
|
507
|
-
function readArray(value, keys) {
|
|
508
|
-
for (const key of keys) {
|
|
509
|
-
const field = value[key];
|
|
510
|
-
if (Array.isArray(field)) {
|
|
511
|
-
return field;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
return [];
|
|
515
|
-
}
|
|
516
|
-
function readNumber(value, keys) {
|
|
517
|
-
for (const key of keys) {
|
|
518
|
-
const field = value[key];
|
|
519
|
-
if (typeof field === "number" && Number.isFinite(field)) {
|
|
520
|
-
return field;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
return null;
|
|
524
|
-
}
|
|
525
|
-
function readString3(value, key) {
|
|
526
|
-
const field = value[key];
|
|
527
|
-
return typeof field === "string" && field.length > 0 ? field : null;
|
|
528
|
-
}
|
|
529
|
-
function isRecord3(value) {
|
|
530
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// src/package/core/usage/live.ts
|
|
534
|
-
import { request as httpRequest } from "node:http";
|
|
535
|
-
import { request as httpsRequest } from "node:https";
|
|
536
|
-
|
|
537
|
-
// src/package/core/usage/normalizer.ts
|
|
538
|
-
var MAX_SEARCH_DEPTH = 5;
|
|
539
|
-
var FIVE_HOUR_LABEL = "5-hour usage limit";
|
|
540
|
-
var WEEKLY_LABEL = "Weekly usage limit";
|
|
541
|
-
var FIVE_HOUR_KEYS = ["fiveHour", "five_hour", "fiveHourWindow", "five_hour_window", "primary", "primaryWindow", "primary_window", "main", "mainWindow"];
|
|
542
|
-
var WEEKLY_KEYS = ["weekly", "week", "weeklyWindow", "weekly_window", "secondary", "secondaryWindow", "secondary_window", "backup", "backupWindow"];
|
|
543
|
-
var USED_KEYS = ["used_percent", "usedPercent", "used", "usage", "percentUsed", "usagePercent", "usagePercentage", "percent"];
|
|
544
|
-
var REMAINING_KEYS = ["remaining_percent", "remainingPercent", "remaining", "percentRemaining", "availablePercent", "available_percentage"];
|
|
545
|
-
var RESETS_AT_KEYS = ["resets_at", "resetsAt", "resetAt", "resetTime", "reset_at", "reset", "ends_at", "endsAt", "windowEnd"];
|
|
546
|
-
var RESETS_IN_KEYS = ["resetsIn", "resetIn", "resets_in", "reset_in", "timeUntilReset"];
|
|
547
|
-
function parseUsageFromSessions(sessions, now = new Date) {
|
|
548
|
-
const snapshot = sessions.latestSnapshot;
|
|
549
|
-
if (!snapshot) {
|
|
550
|
-
return unavailableLocalUsage(sessions.warnings);
|
|
551
|
-
}
|
|
552
|
-
return buildLocalUsageResult(parseUsageWindowsFromRateLimits(snapshot.rateLimits, now), sessions.warnings);
|
|
553
|
-
}
|
|
554
|
-
function parseUsageWindowsFromRateLimits(rateLimits, now = new Date) {
|
|
555
|
-
return {
|
|
556
|
-
fiveHour: parseUsageWindow(readRecord(rateLimits, FIVE_HOUR_KEYS), FIVE_HOUR_LABEL, now),
|
|
557
|
-
weekly: parseUsageWindow(readRecord(rateLimits, WEEKLY_KEYS), WEEKLY_LABEL, now)
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
function withUsageSource(result, source) {
|
|
561
|
-
return { ...result, source };
|
|
562
|
-
}
|
|
563
|
-
function buildUsageResult(windows, source, warnings = []) {
|
|
564
|
-
return { ...buildLocalUsageResult(windows, warnings), source };
|
|
565
|
-
}
|
|
566
|
-
function parseUsageFromState(state, now = new Date) {
|
|
567
|
-
let windows = { fiveHour: null, weekly: null };
|
|
568
|
-
for (const file of state.files) {
|
|
569
|
-
if (!file.json) {
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
const parsed = parseUsageFromUnknown(file.json, now);
|
|
573
|
-
windows = mergeUsageWindows(windows, parsed.windows);
|
|
574
|
-
}
|
|
575
|
-
return buildLocalUsageResult(windows, state.warnings);
|
|
576
|
-
}
|
|
577
|
-
function unavailableLocalUsage(warnings = []) {
|
|
578
|
-
return {
|
|
579
|
-
status: "unavailable",
|
|
580
|
-
windows: { fiveHour: null, weekly: null },
|
|
581
|
-
warnings
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
function mergeLocalUsage(primary, fallback) {
|
|
585
|
-
const windows = mergeUsageWindows(primary.windows, fallback.windows);
|
|
586
|
-
return buildLocalUsageResult(windows, [...primary.warnings, ...fallback.warnings]);
|
|
587
|
-
}
|
|
588
|
-
function parseUsageFromUnknown(value, now) {
|
|
589
|
-
if (!isRecord4(value)) {
|
|
590
|
-
return { windows: { fiveHour: null, weekly: null } };
|
|
591
|
-
}
|
|
592
|
-
const fiveHourSource = findRecord(value, FIVE_HOUR_KEYS);
|
|
593
|
-
const weeklySource = findRecord(value, WEEKLY_KEYS);
|
|
594
|
-
const fiveHour = parseUsageWindow(fiveHourSource ?? (weeklySource ? null : value), FIVE_HOUR_LABEL, now);
|
|
595
|
-
const weekly = parseUsageWindow(weeklySource, WEEKLY_LABEL, now);
|
|
596
|
-
return {
|
|
597
|
-
windows: { fiveHour, weekly }
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
function parseUsageWindow(value, label, now) {
|
|
601
|
-
if (!value) {
|
|
602
|
-
return null;
|
|
603
|
-
}
|
|
604
|
-
const used = toPercent(findValue(value, USED_KEYS, true));
|
|
605
|
-
const remaining = toPercent(findValue(value, REMAINING_KEYS, true));
|
|
606
|
-
const usedPercent = used ?? (remaining === null ? null : clampPercent(100 - remaining));
|
|
607
|
-
const remainingPercent = remaining ?? (used === null ? null : clampPercent(100 - used));
|
|
608
|
-
const resetValue = findValue(value, RESETS_AT_KEYS, true);
|
|
609
|
-
const resetDate = parseDateValue(resetValue);
|
|
610
|
-
const resetsAt = resetDate ? resetDate.toISOString() : readStringValue(resetValue);
|
|
611
|
-
const resetsIn = resetDate ? formatDuration(resetDate.getTime() - now.getTime()) : readStringValue(findValue(value, RESETS_IN_KEYS, true));
|
|
612
|
-
const window = { label, remainingPercent, usedPercent, resetsAt, resetsIn };
|
|
613
|
-
return hasWindowData(window) ? window : null;
|
|
614
|
-
}
|
|
615
|
-
function buildLocalUsageResult(windows, warnings) {
|
|
616
|
-
return {
|
|
617
|
-
status: statusForWindows(windows),
|
|
618
|
-
windows,
|
|
619
|
-
warnings
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
function statusForWindows(windows) {
|
|
623
|
-
const complete = isCompleteWindow(windows.fiveHour) && isCompleteWindow(windows.weekly);
|
|
624
|
-
if (complete) {
|
|
625
|
-
return "available";
|
|
626
|
-
}
|
|
627
|
-
if (hasWindowData(windows.fiveHour) || hasWindowData(windows.weekly)) {
|
|
628
|
-
return "partial";
|
|
629
|
-
}
|
|
630
|
-
return "unavailable";
|
|
631
|
-
}
|
|
632
|
-
function mergeUsageWindows(primary, fallback) {
|
|
633
|
-
return {
|
|
634
|
-
fiveHour: mergeUsageWindow(primary.fiveHour, fallback.fiveHour, FIVE_HOUR_LABEL),
|
|
635
|
-
weekly: mergeUsageWindow(primary.weekly, fallback.weekly, WEEKLY_LABEL)
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
function mergeUsageWindow(primary, fallback, label) {
|
|
639
|
-
if (!primary && !fallback) {
|
|
640
|
-
return null;
|
|
641
|
-
}
|
|
642
|
-
return {
|
|
643
|
-
label,
|
|
644
|
-
remainingPercent: primary?.remainingPercent ?? fallback?.remainingPercent ?? null,
|
|
645
|
-
usedPercent: primary?.usedPercent ?? fallback?.usedPercent ?? null,
|
|
646
|
-
resetsAt: primary?.resetsAt ?? fallback?.resetsAt ?? null,
|
|
647
|
-
resetsIn: primary?.resetsIn ?? fallback?.resetsIn ?? null
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
function hasWindowData(window) {
|
|
651
|
-
return window !== null && (window.remainingPercent !== null || window.usedPercent !== null || window.resetsAt !== null || window.resetsIn !== null);
|
|
652
|
-
}
|
|
653
|
-
function isCompleteWindow(window) {
|
|
654
|
-
return window !== null && window.remainingPercent !== null && window.usedPercent !== null && (window.resetsAt !== null || window.resetsIn !== null);
|
|
655
|
-
}
|
|
656
|
-
function findRecord(value, keys, depth = 0) {
|
|
657
|
-
if (depth > MAX_SEARCH_DEPTH) {
|
|
658
|
-
return null;
|
|
659
|
-
}
|
|
660
|
-
for (const key of keys) {
|
|
661
|
-
const nested = value[key];
|
|
662
|
-
if (isRecord4(nested)) {
|
|
663
|
-
return nested;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
for (const nested of Object.values(value)) {
|
|
667
|
-
if (!isRecord4(nested)) {
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
const found = findRecord(nested, keys, depth + 1);
|
|
671
|
-
if (found) {
|
|
672
|
-
return found;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
function readRecord(value, keys) {
|
|
678
|
-
for (const key of keys) {
|
|
679
|
-
const nested = value[key];
|
|
680
|
-
if (isRecord4(nested)) {
|
|
681
|
-
return nested;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
return null;
|
|
685
|
-
}
|
|
686
|
-
function findValue(value, keys, allowNested, depth = 0) {
|
|
687
|
-
if (depth > MAX_SEARCH_DEPTH) {
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
for (const key of keys) {
|
|
691
|
-
if (key in value) {
|
|
692
|
-
return value[key];
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
if (!allowNested) {
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
for (const nested of Object.values(value)) {
|
|
699
|
-
if (!isRecord4(nested)) {
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
const found = findValue(nested, keys, allowNested, depth + 1);
|
|
703
|
-
if (found !== undefined) {
|
|
704
|
-
return found;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
return;
|
|
708
|
-
}
|
|
709
|
-
function toPercent(value) {
|
|
710
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
711
|
-
return clampPercent(value);
|
|
712
|
-
}
|
|
713
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
714
|
-
const trimmed = value.trim();
|
|
715
|
-
const normalized = trimmed.endsWith("%") ? trimmed.slice(0, -1) : trimmed;
|
|
716
|
-
const parsed = Number(normalized);
|
|
717
|
-
return Number.isFinite(parsed) ? clampPercent(parsed) : null;
|
|
718
|
-
}
|
|
719
|
-
return null;
|
|
720
|
-
}
|
|
721
|
-
function clampPercent(value) {
|
|
722
|
-
return Math.round(Math.min(Math.max(value, 0), 100) * 10) / 10;
|
|
723
|
-
}
|
|
724
|
-
function readStringValue(value) {
|
|
725
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
726
|
-
return value.trim();
|
|
727
|
-
}
|
|
728
|
-
return null;
|
|
729
|
-
}
|
|
730
|
-
function isRecord4(value) {
|
|
731
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// src/package/core/usage/live.ts
|
|
735
|
-
var LIVE_USAGE_ENDPOINT = "https://chatgpt.com/backend-api/codex/usage";
|
|
736
|
-
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
737
|
-
var UNAVAILABLE_SOURCE = { kind: "unavailable", label: "Unavailable" };
|
|
738
|
-
async function getLiveUsage(options = {}) {
|
|
739
|
-
const endpoint = resolveUsageEndpoint(options);
|
|
740
|
-
const credentials = await resolveCodexCredentials(options);
|
|
741
|
-
if (!credentials) {
|
|
742
|
-
return unavailableLiveUsage(["Live usage requires Codex authentication."]);
|
|
743
|
-
}
|
|
744
|
-
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
745
|
-
if (!fetchImplementation) {
|
|
746
|
-
return unavailableLiveUsage(["This runtime does not provide fetch for live usage lookup."]);
|
|
747
|
-
}
|
|
748
|
-
const controller = new AbortController;
|
|
749
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
750
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
751
|
-
const headers = buildUsageHeaders(credentials);
|
|
752
|
-
try {
|
|
753
|
-
const response = await fetchImplementation(endpoint, {
|
|
754
|
-
method: "GET",
|
|
755
|
-
headers,
|
|
756
|
-
signal: controller.signal
|
|
757
|
-
});
|
|
758
|
-
return await parseLiveUsageResponse(response, endpoint, timeoutMs, headers, options.now ?? new Date);
|
|
759
|
-
} catch {
|
|
760
|
-
return await getLiveUsageWithNativeRequest(endpoint, timeoutMs, headers, options.now ?? new Date, "Live usage lookup failed.");
|
|
761
|
-
} finally {
|
|
762
|
-
clearTimeout(timeout);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
function buildUsageHeaders(credentials) {
|
|
766
|
-
return {
|
|
767
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
768
|
-
"ChatGPT-Account-ID": credentials.accountId,
|
|
769
|
-
"OpenAI-Beta": "codex-1",
|
|
770
|
-
originator: "Codex Desktop",
|
|
771
|
-
Accept: "application/json",
|
|
772
|
-
"User-Agent": "Codex Desktop",
|
|
773
|
-
Referer: "https://chatgpt.com/codex/cloud/settings/analytics",
|
|
774
|
-
Origin: "https://chatgpt.com"
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
|
-
async function parseLiveUsageResponse(response, endpoint, timeoutMs, headers, now) {
|
|
778
|
-
if (response.ok) {
|
|
779
|
-
return parseLiveUsagePayload(await response.json(), endpoint, now);
|
|
780
|
-
}
|
|
781
|
-
return getLiveUsageWithNativeRequest(endpoint, timeoutMs, headers, now, `Live usage endpoint returned HTTP ${response.status}.`);
|
|
782
|
-
}
|
|
783
|
-
async function getLiveUsageWithNativeRequest(endpoint, timeoutMs, headers, now, fallbackWarning) {
|
|
784
|
-
try {
|
|
785
|
-
const response = await requestJson(endpoint, headers, timeoutMs);
|
|
786
|
-
if (!response.ok) {
|
|
787
|
-
return unavailableLiveUsage([`Live usage endpoint returned HTTP ${response.status}.`]);
|
|
788
|
-
}
|
|
789
|
-
return parseLiveUsagePayload(await response.json(), endpoint, now);
|
|
790
|
-
} catch {
|
|
791
|
-
return unavailableLiveUsage([fallbackWarning]);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
function resolveUsageEndpoint(options) {
|
|
795
|
-
const env = resolveEnvironment(options.env);
|
|
796
|
-
return options.usageEndpoint ?? readEnvValue(env, "CODEX_LIMITS_USAGE_ENDPOINT") ?? LIVE_USAGE_ENDPOINT;
|
|
797
|
-
}
|
|
798
|
-
function parseLiveUsagePayload(payload, endpoint, now) {
|
|
799
|
-
const rateLimits = findRateLimits(payload) ?? buildRateLimitsFromWindowArray(payload);
|
|
800
|
-
if (!rateLimits) {
|
|
801
|
-
return unavailableLiveUsage(["Live usage endpoint returned an unexpected payload."]);
|
|
802
|
-
}
|
|
803
|
-
return buildUsageResult(parseUsageWindowsFromRateLimits(rateLimits, now), { kind: "api", label: "API", endpoint });
|
|
804
|
-
}
|
|
805
|
-
function unavailableLiveUsage(warnings) {
|
|
806
|
-
return buildUsageResult({ fiveHour: null, weekly: null }, UNAVAILABLE_SOURCE, warnings);
|
|
807
|
-
}
|
|
808
|
-
function findRateLimits(value, depth = 0) {
|
|
809
|
-
if (!isRecord5(value) || depth > 5) {
|
|
810
|
-
return null;
|
|
811
|
-
}
|
|
812
|
-
const direct = value.rate_limits ?? value.rateLimits ?? value.rate_limit ?? value.rateLimit;
|
|
813
|
-
if (isRecord5(direct)) {
|
|
814
|
-
return direct;
|
|
815
|
-
}
|
|
816
|
-
if (isRecord5(value.primary) || isRecord5(value.secondary) || isRecord5(value.primary_window) || isRecord5(value.secondary_window)) {
|
|
817
|
-
return value;
|
|
818
|
-
}
|
|
819
|
-
for (const nested of Object.values(value)) {
|
|
820
|
-
const found = findRateLimits(nested, depth + 1);
|
|
821
|
-
if (found) {
|
|
822
|
-
return found;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
return null;
|
|
826
|
-
}
|
|
827
|
-
function buildRateLimitsFromWindowArray(value, depth = 0) {
|
|
828
|
-
if (depth > 5) {
|
|
829
|
-
return null;
|
|
830
|
-
}
|
|
831
|
-
if (Array.isArray(value)) {
|
|
832
|
-
const primary = value.find((item) => isUsageWindowRecord(item, "primary"));
|
|
833
|
-
const secondary = value.find((item) => isUsageWindowRecord(item, "secondary"));
|
|
834
|
-
return primary || secondary ? { primary, secondary } : null;
|
|
835
|
-
}
|
|
836
|
-
if (!isRecord5(value)) {
|
|
837
|
-
return null;
|
|
838
|
-
}
|
|
839
|
-
for (const nested of Object.values(value)) {
|
|
840
|
-
const found = buildRateLimitsFromWindowArray(nested, depth + 1);
|
|
841
|
-
if (found) {
|
|
842
|
-
return found;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
return null;
|
|
846
|
-
}
|
|
847
|
-
function isUsageWindowRecord(value, kind) {
|
|
848
|
-
if (!isRecord5(value)) {
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
const label = String(value.type ?? value.kind ?? value.name ?? value.label ?? value.window ?? "").toLowerCase();
|
|
852
|
-
if (kind === "primary" && (label.includes("primary") || label.includes("5-hour") || label.includes("five"))) {
|
|
853
|
-
return true;
|
|
854
|
-
}
|
|
855
|
-
if (kind === "secondary" && (label.includes("secondary") || label.includes("weekly") || label.includes("week"))) {
|
|
856
|
-
return true;
|
|
857
|
-
}
|
|
858
|
-
const minutes = value.window_minutes ?? value.windowMinutes ?? value.window_length_minutes ?? value.windowLengthMinutes;
|
|
859
|
-
return kind === "primary" ? minutes === 300 : minutes === 10080;
|
|
860
|
-
}
|
|
861
|
-
function requestJson(endpoint, headers, timeoutMs) {
|
|
862
|
-
return new Promise((resolve, reject) => {
|
|
863
|
-
const url = new URL(endpoint);
|
|
864
|
-
const request = (url.protocol === "http:" ? httpRequest : httpsRequest)(url, {
|
|
865
|
-
method: "GET",
|
|
866
|
-
headers,
|
|
867
|
-
timeout: timeoutMs
|
|
868
|
-
}, (response) => {
|
|
869
|
-
const chunks = [];
|
|
870
|
-
response.on("data", (chunk) => {
|
|
871
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
872
|
-
});
|
|
873
|
-
response.on("end", () => {
|
|
874
|
-
const body = Buffer.concat(chunks).toString("utf8");
|
|
875
|
-
resolve({
|
|
876
|
-
ok: response.statusCode !== undefined && response.statusCode >= 200 && response.statusCode < 300,
|
|
877
|
-
status: response.statusCode ?? 0,
|
|
878
|
-
json: async () => JSON.parse(body)
|
|
879
|
-
});
|
|
880
|
-
});
|
|
881
|
-
});
|
|
882
|
-
request.on("timeout", () => {
|
|
883
|
-
request.destroy(new Error("Live usage request timed out."));
|
|
884
|
-
});
|
|
885
|
-
request.on("error", reject);
|
|
886
|
-
request.end();
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
function isRecord5(value) {
|
|
890
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// src/package/core/utils/redact.ts
|
|
894
|
-
var SENSITIVE_PATTERNS = [
|
|
895
|
-
/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
896
|
-
/(?:access_token|refresh_token|api_key|account_id|authorization)\s*[:=]\s*[A-Za-z0-9._~+/=-]+/gi,
|
|
897
|
-
/sk-[A-Za-z0-9]{10,}/g
|
|
898
|
-
];
|
|
899
|
-
function redactSensitiveText(value) {
|
|
900
|
-
let redacted = value;
|
|
901
|
-
for (const pattern of SENSITIVE_PATTERNS) {
|
|
902
|
-
redacted = redacted.replace(pattern, "[redacted]");
|
|
903
|
-
}
|
|
904
|
-
return redacted;
|
|
905
|
-
}
|
|
906
|
-
function redactWarnings(warnings) {
|
|
907
|
-
return warnings.map(redactSensitiveText);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// src/package/core/limits.ts
|
|
911
|
-
var LOCAL_USAGE_SOURCE = { kind: "local", label: "Local" };
|
|
912
|
-
async function getCodexLimits(options = {}) {
|
|
913
|
-
const usage = await getUsageLimits(options);
|
|
914
|
-
const coupons = options.includeCoupons === false ? null : await getResetCoupons(options);
|
|
915
|
-
const warnings = redactWarnings([...usage.warnings, ...coupons?.warnings ?? []]);
|
|
916
|
-
return {
|
|
917
|
-
windows: usage.windows,
|
|
918
|
-
usageSource: usage.source,
|
|
919
|
-
coupons,
|
|
920
|
-
warnings
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
async function getUsageLimits(options = {}) {
|
|
924
|
-
const live = await getLiveUsage(options);
|
|
925
|
-
if (live.status === "available") {
|
|
926
|
-
return live;
|
|
927
|
-
}
|
|
928
|
-
const local = withUsageSource(await getLocalUsage(options), LOCAL_USAGE_SOURCE);
|
|
929
|
-
return local.status !== "unavailable" ? local : { ...local, warnings: [...live.warnings, ...local.warnings] };
|
|
930
|
-
}
|
|
931
|
-
async function getLocalUsage(options = {}) {
|
|
932
|
-
const now = options.now ?? new Date;
|
|
933
|
-
const detection = await detectCodexHome(options);
|
|
934
|
-
if (!detection.foundHome) {
|
|
935
|
-
return unavailableLocalUsage(["No readable local Codex home directory was found."]);
|
|
936
|
-
}
|
|
937
|
-
const sessions = await readCodexSessions(detection.foundHome);
|
|
938
|
-
const sessionUsage = parseUsageFromSessions(sessions, now);
|
|
939
|
-
const state = await readCodexState(detection.foundHome);
|
|
940
|
-
const stateUsage = parseUsageFromState(state, now);
|
|
941
|
-
return mergeLocalUsage(sessionUsage, stateUsage);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
// src/agents/opencode/format.ts
|
|
945
|
-
var BAR_WIDTH = 22;
|
|
946
|
-
function formatOpencodeLimits(result) {
|
|
947
|
-
return [
|
|
948
|
-
...formatWindow("5-hour", result.windows.fiveHour),
|
|
949
|
-
"",
|
|
950
|
-
...formatWindow("Weekly", result.windows.weekly),
|
|
951
|
-
"",
|
|
952
|
-
...formatCredits(result),
|
|
953
|
-
...formatWarnings(result.warnings)
|
|
954
|
-
].join(`
|
|
955
|
-
`);
|
|
956
|
-
}
|
|
957
|
-
function formatWindow(title, window) {
|
|
958
|
-
const percent = window?.remainingPercent ?? null;
|
|
959
|
-
return [
|
|
960
|
-
`${title} ${statusLabel(percent)}`,
|
|
961
|
-
`Remaining ${remainingLabel(percent)}`,
|
|
962
|
-
progressBar(percent),
|
|
963
|
-
`Reset ${window?.resetsIn ? `in ${window.resetsIn}` : "unknown"}`
|
|
964
|
-
];
|
|
965
|
-
}
|
|
966
|
-
function remainingLabel(value) {
|
|
967
|
-
return value === null ? "Unknown" : `${Math.round(value)}% remaining`;
|
|
968
|
-
}
|
|
969
|
-
function progressBar(value) {
|
|
970
|
-
const percent = clampPercent2(value);
|
|
971
|
-
const filled = Math.round(percent / 100 * BAR_WIDTH);
|
|
972
|
-
const empty = Math.max(BAR_WIDTH - filled, 0);
|
|
973
|
-
return `[${"=".repeat(filled)}${" ".repeat(empty)}] ${Math.round(percent)}%`;
|
|
974
|
-
}
|
|
975
|
-
function formatCredits(result) {
|
|
976
|
-
const available = result.coupons?.available;
|
|
977
|
-
const expires = formatExpiration(result);
|
|
978
|
-
const lines = [`Reset credits ${available === null || available === undefined ? "Unknown" : creditLabel(available)}`];
|
|
979
|
-
if (expires !== "unknown") {
|
|
980
|
-
lines.push(`Next expires ${expires}`);
|
|
981
|
-
}
|
|
982
|
-
return lines;
|
|
983
|
-
}
|
|
984
|
-
function creditLabel(value) {
|
|
985
|
-
const suffix = value === 1 ? "credit available" : "credits available";
|
|
986
|
-
return `${value} ${suffix}`;
|
|
987
|
-
}
|
|
988
|
-
function formatWarnings(warnings) {
|
|
989
|
-
if (warnings.length === 0) {
|
|
990
|
-
return [];
|
|
991
|
-
}
|
|
992
|
-
return ["", "Warnings", ...warnings.map((warning) => `- ${warning}`)];
|
|
993
|
-
}
|
|
994
|
-
function statusLabel(value) {
|
|
995
|
-
if (value === null)
|
|
996
|
-
return "Unknown";
|
|
997
|
-
if (value >= 50)
|
|
998
|
-
return "Healthy";
|
|
999
|
-
if (value >= 15)
|
|
1000
|
-
return "Low";
|
|
1001
|
-
return "Critical";
|
|
1002
|
-
}
|
|
1003
|
-
function clampPercent2(value) {
|
|
1004
|
-
if (value === null) {
|
|
1005
|
-
return 0;
|
|
1006
|
-
}
|
|
1007
|
-
return Math.min(Math.max(value, 0), 100);
|
|
1008
|
-
}
|
|
1009
|
-
function formatExpiration(result) {
|
|
1010
|
-
const expiresIn = result.coupons?.nextExpirationIn;
|
|
1011
|
-
const expiresAt = result.coupons?.nextExpirationDate;
|
|
1012
|
-
if (expiresIn && expiresAt) {
|
|
1013
|
-
return `${expiresIn} (${expiresAt})`;
|
|
1014
|
-
}
|
|
1015
|
-
return expiresIn ?? expiresAt ?? "unknown";
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
// src/agents/opencode/plugin.tsx
|
|
1019
|
-
var TITLE = "Codex Limits";
|
|
1020
|
-
var DESCRIPTION = "Check Codex limits, resets, and credits.";
|
|
1021
|
-
var module = {
|
|
1022
|
-
id: "codex-limits",
|
|
1023
|
-
tui: async (api) => {
|
|
1024
|
-
const command = createCommand(api);
|
|
1025
|
-
const disposes = [api.command?.register(() => [command]), registerCommandLayer(api, command)].filter(isDispose);
|
|
1026
|
-
if (disposes.length > 0) {
|
|
1027
|
-
api.lifecycle.onDispose(() => {
|
|
1028
|
-
for (const dispose of disposes) {
|
|
1029
|
-
dispose();
|
|
1030
|
-
}
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
};
|
|
1035
|
-
function createCommand(api) {
|
|
1036
|
-
return {
|
|
1037
|
-
title: TITLE,
|
|
1038
|
-
value: "codex-limits.show",
|
|
1039
|
-
description: DESCRIPTION,
|
|
1040
|
-
category: "Codex",
|
|
1041
|
-
slash: { name: "codex-limits" },
|
|
1042
|
-
onSelect: async (dialog) => {
|
|
1043
|
-
dialog?.clear();
|
|
1044
|
-
api.ui.dialog.clear();
|
|
1045
|
-
await nextFrame();
|
|
1046
|
-
const target = api.ui.dialog;
|
|
1047
|
-
target.setSize("large");
|
|
1048
|
-
target.replace(() => alert(api, "Loading Codex limits..."));
|
|
1049
|
-
try {
|
|
1050
|
-
const result = await getCodexLimits();
|
|
1051
|
-
target.replace(() => alert(api, formatOpencodeLimits(result)));
|
|
1052
|
-
} catch (error) {
|
|
1053
|
-
const message = error instanceof Error ? error.message : "Could not load Codex limits.";
|
|
1054
|
-
api.ui.toast({ variant: "error", title: TITLE, message });
|
|
1055
|
-
target.replace(() => alert(api, message));
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
function registerCommandLayer(api, command) {
|
|
1061
|
-
const keymap = api.keymap;
|
|
1062
|
-
return keymap?.registerLayer?.({ commands: [command], bindings: [] });
|
|
1063
|
-
}
|
|
1064
|
-
function isDispose(value) {
|
|
1065
|
-
return typeof value === "function";
|
|
1066
|
-
}
|
|
1067
|
-
function alert(api, message) {
|
|
1068
|
-
return api.ui.DialogAlert({ title: TITLE, message });
|
|
1069
|
-
}
|
|
1070
|
-
function nextFrame() {
|
|
1071
|
-
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
1072
|
-
}
|
|
1073
|
-
var plugin_default = module;
|
|
1074
|
-
var tui = module.tui;
|
|
1075
|
-
export {
|
|
1076
|
-
tui,
|
|
1077
|
-
plugin_default as default
|
|
1078
|
-
};
|
|
1
|
+
import{createRequire as OZ}from"node:module";var XZ=Object.create;var{getPrototypeOf:WZ,defineProperty:l,getOwnPropertyNames:VZ}=Object;var qZ=Object.prototype.hasOwnProperty;function MZ(Q){return this[Q]}var NZ,UZ,K1=(Q,Z,$)=>{var G=Q!=null&&typeof Q==="object";if(G){var J=Z?NZ??=new WeakMap:UZ??=new WeakMap,K=J.get(Q);if(K)return K}$=Q!=null?XZ(WZ(Q)):{};let z=Z||!Q||!Q.__esModule?l($,"default",{value:Q,enumerable:!0}):$;for(let j of VZ(Q))if(!qZ.call(z,j))l(z,j,{get:MZ.bind(Q,j),enumerable:!0});if(G)J.set(Q,z);return z};var z1=(Q,Z)=>()=>(Z||Q((Z={exports:{}}).exports,Z),Z.exports);var BZ=(Q)=>Q;function xZ(Q,Z){this[Q]=BZ.bind(null,Z)}var j1=(Q,Z)=>{for(var $ in Z)l(Q,$,{get:Z[$],enumerable:!0,configurable:!0,set:xZ.bind(Z,$)})};var CZ=(Q,Z)=>()=>(Q&&(Z=Q(Q=0)),Z);var H1=(Q)=>Promise.all(Q),X1=OZ(import.meta.url);function c(Q,Z={}){let $=Z.includeSeconds??!1,G=Math.max(Math.floor(Q/1000),0),J=Math.floor(G/86400);G%=86400;let K=Math.floor(G/3600);G%=3600;let z=Math.floor(G/60),j=G%60,H=[];if(J>0)H.push(`${J}d`);if(K>0)H.push(`${K}h`);if(z>0)H.push(`${z}m`);if($&&j>0)H.push(`${j}s`);return H.length>0?H.join(" "):$?`${j}s`:"0m"}function I(Q){if(typeof Q==="number"&&Number.isFinite(Q)){let Z=Q<10000000000?Q*1000:Q,$=new Date(Z);return Number.isNaN($.getTime())?null:$}if(typeof Q==="string"&&Q.trim().length>0){let Z=Q.trim(),$=Number(Z);if(Number.isFinite($))return I($);let G=new Date(Z);return Number.isNaN(G.getTime())?null:G}return null}function RQ(Q){return`${F0[Q.getDay()]} ${Q.getDate()} ${I0[Q.getMonth()]} ${Q.getFullYear()}`}function a1(Q){return`${Q.getDate()} ${A0[Q.getMonth()]} ${Q.getFullYear()} ${k0(Q)}`}function k0(Q){return`${PQ(Q.getHours())}:${PQ(Q.getMinutes())}`}function e1(Q,Z){return Q.getFullYear()===Z.getFullYear()&&Q.getMonth()===Z.getMonth()&&Q.getDate()===Z.getDate()}function PQ(Q){return String(Q).padStart(2,"0")}var F0,I0,A0;var $Q=CZ(()=>{F0=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],I0=["January","February","March","April","May","June","July","August","September","October","November","December"],A0=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function qQ(Q){let Z=Q.warnings.length>0?["","Warnings",...Q.warnings.map(($)=>`- ${$}`)]:[];return[...VQ("5-hour",Q.windows.fiveHour),"",...VQ("Weekly",Q.windows.weekly),"",...TZ(Q),...Z].join(`
|
|
2
|
+
`)}function VQ(Q,Z){let $=Z?.remainingPercent??null;return[`${Q} ${FZ($)}`,`Remaining ${$===null?"Unknown":`${Math.round($)}% remaining`}`,DZ($),`Reset ${Z?.resetsIn?`in ${Z.resetsIn}`:"unknown"}`]}function DZ(Q){let Z=Q===null?0:Math.min(Math.max(Q,0),100),$=Math.round(Z/100*22);return`[${"=".repeat($)}${" ".repeat(22-$)}] ${Math.round(Z)}%`}function TZ(Q){let Z=Q.coupons?.available,G=[`Reset credits ${Z===null||Z===void 0?"Unknown":`${Z} ${Z===1?"credit":"credits"} available`}`],J=Q.coupons?.nextExpirationIn,K=Q.coupons?.nextExpirationDate,z=J&&K?`${J} (${K})`:J??K;if(z!==null&&z!==void 0)G.push(`Next expires ${z}`);return G}function FZ(Q){if(Q===null)return"Unknown";if(Q>=50)return"Healthy";if(Q>=15)return"Low";return"Critical"}import{constants as IZ}from"node:fs";import{access as AZ,stat as kZ}from"node:fs/promises";import{homedir as bZ}from"node:os";import{join as A,normalize as r}from"node:path";function M(Q,Z){let $=Q[Z]?.trim();return $?$:null}function T(Q){return Q??process.env}var MQ="CODEX_LIMITS_HOME";function LZ(Q={}){let Z=T(Q.env),$=Q.homeDirectory??M(Z,"HOME")??M(Z,"USERPROFILE")??bZ(),G=Q.appData??M(Z,"APPDATA"),J=Q.localAppData??M(Z,"LOCALAPPDATA"),K=[];if(D(K,M(Z,MQ),"env"),D(K,M(Z,"CODEX_HOME"),"env"),$)D(K,A($,".codex"),"default"),D(K,A($,".config","codex"),"default"),D(K,A($,"Library","Application Support","Codex"),"default"),D(K,A($,"Library","Application Support","Parall","Codex",".codex"),"default");return D(K,G?A(G,"Codex"):null,"default"),D(K,J?A(J,"Codex"):null,"default"),PZ(K)}async function S(Q={}){let Z=T(Q.env),$=M(Z,MQ),G=await Promise.all(LZ(Q).map(async(J)=>({...J,exists:await _Z(J.path)})));return{overrideHome:$?r($):null,candidates:G,foundHome:G.find((J)=>J.exists)?.path??null}}function D(Q,Z,$){if(Z)Q.push({path:r(Z),source:$})}async function _Z(Q){try{if(!(await kZ(Q)).isDirectory())return!1;return await AZ(Q,IZ.R_OK),!0}catch{return!1}}function PZ(Q){let Z=new Set,$=[];for(let G of Q){let J=r(G.path),K=process.platform==="win32"?J.toLowerCase():J;if(!Z.has(K))Z.add(K),$.push({path:J,source:G.source})}return $}import{createReadStream as EZ}from"node:fs";import{lstat as wZ,opendir as gZ,realpath as OQ,stat as vZ}from"node:fs/promises";import{join as DQ}from"node:path";import{isAbsolute as yZ,relative as UQ,resolve as NQ,sep as fZ}from"node:path";var RZ=[/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,/["']?(?:access_token|refresh_token|api_key|account_id|authorization|chatgpt-account-id|cookie)["']?\s*[:=]\s*["']?[^"',\s}\]]+["']?/gi,/(?:access_token|refresh_token|api_key|account_id)=([^&\s]+)/gi,/sk-[A-Za-z0-9_-]{10,}/g];function F(Q){let Z=Q;for(let $ of RZ)Z=Z.replace($,"[redacted]");return Z}function o(Q){return Q.map(F)}function BQ(Q,Z){let $=UQ(NQ(Q),NQ(Z));return $===""||!xQ($)}function x(Q,Z){let $=UQ(Q,Z);return $&&!xQ($)?SZ($):"."}function SZ(Q){let Z=F(Q).replace(/[\u0000-\u001f\u007f]/g,"?").replace(/\b[0-9a-f]{8}-[0-9a-f-]{27,}\b/gi,"[id]").replace(/[A-Za-z0-9_-]{32,}/g,"[id]");return Z.length<=240?Z:`${Z.slice(0,239)}…`}function xQ(Q){return Q===".."||Q.startsWith(`..${fZ}`)||yZ(Q)}function Y(Q){return typeof Q==="object"&&Q!==null&&!Array.isArray(Q)}function U(Q,Z){let $=Q[Z];return typeof $==="string"&&$.trim()?$.trim():null}var mZ=8,i=20,E=1000,hZ=512,cZ=1000,TQ=25000000,pZ=1e6,uZ=/^rollout-.*\.jsonl$/i;async function FQ(Q){let Z=DQ(Q,"sessions"),$=[];if(await tZ(Z))return $.push("Skipped the symbolic-link Codex sessions directory."),{homePath:Q,sessionsRoot:Z,files:[],latestSnapshot:null,warnings:$};let G=await dZ(Q,Z,$),J=[],K=null;for(let z of G.slice(0,i)){let j=x(Q,z.path);if(z.size>TQ){$.push(`Skipped ${j} because it is too large to inspect safely.`),J.push(n(z.path,j,z.modifiedAtMs,!1,"too-large"));continue}try{let H=await oZ(Q,z.path);if(J.push(n(z.path,j,z.modifiedAtMs,H.snapshot!==null,null)),H.skippedOversizedLine)$.push(`Skipped an oversized JSONL line in ${j}.`);if(H.snapshot&&!K)K=H.snapshot}catch(H){let X=H instanceof s;$.push(X?`Skipped ${j} because it grew too large to inspect safely.`:`Could not inspect ${j}.`),J.push(n(z.path,j,z.modifiedAtMs,!1,X?"too-large":"read-error"))}if(K)break}if(G.length>i)$.push(`Skipped ${G.length-i} older session files to keep inspection small.`);if(G.length>0&&!K)$.push("No token-count rate-limit snapshot was found in local Codex session logs.");return{homePath:Q,sessionsRoot:Z,files:J,latestSnapshot:K,warnings:$}}async function dZ(Q,Z,$){let G={directories:0,files:[],hitLimit:!1,skippedSymlink:!1};if(await IQ(Z,0,G,$,Q),G.hitLimit)$.push("Stopped session discovery after reaching a safe inspection limit.");if(G.skippedSymlink)$.push("Skipped symbolic links while inspecting Codex sessions.");let J;try{J=await OQ(Z)}catch{return[]}let K=[];for(let z of G.files){let j=await lZ(Q,J,z,$);if(j)K.push(j)}return K.sort((z,j)=>j.modifiedAtMs-z.modifiedAtMs||z.path.localeCompare(j.path))}async function lZ(Q,Z,$,G){try{let[J,K]=await Promise.all([vZ($),OQ($)]);if(!J.isFile()||!BQ(Z,K))return G.push(`Could not inspect ${x(Q,$)}.`),null;return{path:$,modifiedAtMs:J.mtimeMs,size:J.size}}catch{return G.push(`Could not inspect ${x(Q,$)}.`),null}}async function IQ(Q,Z,$,G,J){if(Z>mZ||$.files.length>=E){$.hitLimit||=$.files.length>=E;return}if($.directories>=hZ){$.hitLimit=!0;return}$.directories+=1;let K;try{K=await rZ(Q,$)}catch{if(Z>0)G.push(`Could not inspect ${x(J,Q)}.`);return}for(let z of K){if($.files.length>=E){$.hitLimit=!0;return}let j=DQ(Q,z.name);if(z.isSymbolicLink()){$.skippedSymlink=!0;continue}if(z.isDirectory()){await IQ(j,Z+1,$,G,J);continue}if(z.isFile()&&uZ.test(z.name)){if($.files.push(j),$.files.length>=E){$.hitLimit=!0;return}}}}async function rZ(Q,Z){let $=await gZ(Q),G=[];for await(let J of $){if(G.length>=cZ){Z.hitLimit=!0;break}G.push(J)}return G.sort((J,K)=>K.name.localeCompare(J.name))}async function oZ(Q,Z){let $=x(Q,Z),G=EZ(Z,{encoding:"utf8",highWaterMark:65536}),J=null,K=null,z="",j=0,H=0,X=!1,V=!1;for await(let q of G){let C=String(q);if(H+=Buffer.byteLength(C,"utf8"),H>TQ)throw new s;let y=0;while(y<C.length){let f=C.indexOf(`
|
|
3
|
+
`,y),YZ=f===-1?C.length:f,WQ=C.slice(y,YZ);if(!X){let O=Buffer.byteLength(WQ,"utf8");if(j+O>pZ)z="",j=0,X=!0,V=!0;else z+=WQ,j+=O}if(f===-1)break;if(!X){let O=CQ(z.endsWith("\r")?z.slice(0,-1):z);if(O.threadId)J=O.threadId;if(O.rateLimits)K={sessionFile:Z,relativePath:$,threadId:J,eventTimestamp:O.timestamp,rateLimits:O.rateLimits}}z="",j=0,X=!1,y=f+1}}if(!X&&z.length>0){let q=CQ(z.endsWith("\r")?z.slice(0,-1):z);if(q.threadId)J=q.threadId;if(q.rateLimits)K={sessionFile:Z,relativePath:$,threadId:J,eventTimestamp:q.timestamp,rateLimits:q.rateLimits}}return{snapshot:K,skippedOversizedLine:V}}function CQ(Q){let Z=iZ(Q);if(!Z)return{threadId:null,timestamp:null,rateLimits:null};return{threadId:nZ(Z),timestamp:U(Z,"timestamp"),rateLimits:sZ(Z)}}function iZ(Q){let Z=Q.trim();if(!Z)return null;try{let $=JSON.parse(Z);return Y($)?$:null}catch{return null}}function nZ(Q){if(Q.type!=="session_meta"||!Y(Q.payload))return null;return U(Q.payload,"id")}function sZ(Q){if(Q.type!=="event_msg"||!Y(Q.payload))return null;if(Q.payload.type!=="token_count"||!Y(Q.payload.rate_limits))return null;return Q.payload.rate_limits}function n(Q,Z,$,G,J){return{path:Q,relativePath:Z,modifiedAtMs:$,hasSnapshot:G,error:J}}async function tZ(Q){try{return(await wZ(Q)).isSymbolicLink()}catch{return!1}}class s extends Error{}import{opendir as Q0}from"node:fs/promises";import{extname as Z0,join as $0}from"node:path";import{open as aZ}from"node:fs/promises";class B extends Error{code;constructor(Q){super(Q);this.name="BoundedFileError",this.code=Q}}async function w(Q,Z){let $;try{$=await aZ(Q,"r");let G=await $.stat();if(!G.isFile())throw new B("not-file");if(G.size>Z)throw new B("too-large");let J=Buffer.allocUnsafe(Z+1),K=0;while(K<=Z){let z=await $.read(J,K,Z+1-K,null);if(z.bytesRead===0)break;K+=z.bytesRead}if(K>Z)throw new B("too-large");return J.subarray(0,K).toString("utf8")}catch(G){if(G instanceof B)throw G;if(eZ(G)&&G.code==="ENOENT")throw new B("not-found");throw new B("read-error")}finally{await $?.close().catch(()=>{return})}}function eZ(Q){return Q instanceof Error&&"code"in Q}var G0=2,t=25,a=100,J0=64,K0=500,z0=1e6,j0=/(?:auth|token|cookie|session|secret|credential|api[-_]?key|keychain)/i;async function AQ(Q){let Z={directories:0,files:[],hitDirectoryLimit:!1,hitEntryLimit:!1,hitFileLimit:!1,skippedSensitive:!1,skippedSymlink:!1,warnings:[]};if(await kQ(Q,Q,0,Z),Z.hitDirectoryLimit||Z.hitEntryLimit||Z.hitFileLimit)Z.warnings.push("Stopped local state discovery after reaching a safe inspection limit.");if(Z.skippedSensitive)Z.warnings.push("Skipped a sensitive-looking local file.");if(Z.skippedSymlink)Z.warnings.push("Skipped symbolic links while inspecting local Codex state.");let $=Z.files.sort((J,K)=>J.localeCompare(K)),G=[];for(let J of $.slice(0,t)){let K=x(Q,J);try{let z=await w(J,z0),j=Y0(z,K,Z.warnings);G.push({path:J,relativePath:K,json:j.value,error:j.error})}catch(z){Z.warnings.push(z instanceof B&&z.code==="too-large"?`Skipped ${K} because it is too large to inspect safely.`:`Could not read ${K}.`)}}if($.length>t)Z.warnings.push(`Skipped ${$.length-t} extra files to keep inspection small.`);return{homePath:Q,files:G,warnings:Z.warnings}}async function kQ(Q,Z,$,G){if($>G0||G.files.length>=a){G.hitFileLimit||=G.files.length>=a;return}if(G.directories>=J0){G.hitDirectoryLimit=!0;return}G.directories+=1;let J=await H0(Q,Z,G);for(let K of J){if(G.files.length>=a){G.hitFileLimit=!0;return}if(j0.test(K.name)){G.skippedSensitive=!0;continue}let z=$0(Z,K.name);if(K.isSymbolicLink())G.skippedSymlink=!0;else if(K.isDirectory())await kQ(Q,z,$+1,G);else if(K.isFile()&&Z0(K.name).toLowerCase()===".json")G.files.push(z)}}async function H0(Q,Z,$){try{let G=await Q0(Z),J=[];for await(let K of G){if(J.length>=K0){$.hitEntryLimit=!0;break}J.push(K)}return J.sort((K,z)=>K.name.localeCompare(z.name))}catch{return $.warnings.push(`Could not inspect ${x(Q,Z)}.`),[]}}function Y0(Q,Z,$){try{return{value:JSON.parse(Q),error:null}}catch{return $.push(`Could not parse JSON in ${Z}.`),{value:null,error:"invalid-json"}}}import{join as X0,normalize as W0}from"node:path";function N(Q,Z,$){return{code:Q,source:Z,severity:"warning",message:$}}function k(Q){return Q.map((Z)=>F(Z.message))}var V0=1e6;async function g(Q={}){let Z=T(Q.env),$=M(Z,"CODEX_LIMITS_ACCESS_TOKEN"),G=M(Z,"CODEX_LIMITS_ACCOUNT_ID");if($&&G)return{credentials:{accessToken:$,accountId:G},status:"configured",diagnostics:[]};if($||G)return{credentials:null,status:"partial",diagnostics:[N("auth.environment.partial","authentication","Codex authentication environment variables are incomplete.")]};let J=await q0(Q);return J?M0(J):{credentials:null,status:"missing",diagnostics:[]}}async function q0(Q){if(Q.authFile)return W0(Q.authFile);let Z=await S(Q);return Z.foundHome?X0(Z.foundHome,"auth.json"):null}async function M0(Q){let Z;try{Z=await w(Q,V0)}catch($){if($ instanceof B&&$.code==="not-found")return{credentials:null,status:"missing",diagnostics:[]};let G=$ instanceof B&&$.code==="too-large";return{credentials:null,status:"unreadable",diagnostics:[N(G?"auth.file.too-large":"auth.file.unreadable","authentication",G?"Codex auth.json is too large to inspect safely.":"Codex auth.json could not be read safely.")]}}try{let $=JSON.parse(Z);if(!Y($))return e();let G=Y($.tokens)?$.tokens:$,J=U(G,"access_token"),K=U(G,"account_id");return J&&K?{credentials:{accessToken:J,accountId:K},status:"configured",diagnostics:[]}:e()}catch{return e()}}function e(){return{credentials:null,status:"malformed",diagnostics:[N("auth.file.malformed","authentication","Codex auth.json is malformed or does not contain required credentials.")]}}import{request as N0}from"node:http";import{request as U0}from"node:https";var B0=new Set(["127.0.0.1","::1","[::1]","localhost"]);function L(Q){try{let Z=new URL(Q);return Z.username="",Z.password="",Z.search="",Z.hash="",Z.href}catch{return"[invalid endpoint]"}}async function v(Q){let Z=x0(Q.endpoint);if(!Z.ok)return Z;if(Q.signal?.aborted)return W("aborted");let $=Q.fetch??globalThis.fetch;if(!$)return bQ(Z.url,Q);let G=await C0(Z.url,Q,$);if(G.ok||G.code==="aborted"||!T0(G,Q.fallbackOnHttpError??!1))return G;let J=await bQ(Z.url,Q);if(J.ok||J.code==="http-error"||J.code==="invalid-json"||J.code==="response-too-large")return J;return G}function x0(Q){let Z;try{Z=new URL(Q)}catch{return W("invalid-url")}if(Z.username||Z.password)return W("invalid-url");if(Z.protocol==="https:")return{ok:!0,url:Z};if(Z.protocol==="http:"&&B0.has(Z.hostname.toLowerCase()))return{ok:!0,url:Z};return W("unsupported-protocol")}async function C0(Q,Z,$){let G=_Q(Z.timeoutMs,Z.signal);try{let J=await $(Q.href,{method:"GET",headers:Z.headers,redirect:"error",signal:G.signal});if(!J.ok)return await LQ(J),W("http-error",ZQ(J.status));let K=await O0(J,Z.maxResponseBytes);return{ok:!0,status:ZQ(J.status)??200,payload:K,transport:"fetch"}}catch(J){if(J instanceof b)return W("response-too-large");if(J instanceof m)return W("invalid-json");if(Z.signal?.aborted)return W("aborted");if(G.didTimeout())return W("timeout");return W("network-error")}finally{G.dispose()}}async function O0(Q,Z){let $=Number(Q.headers?.get("content-length"));if(Number.isFinite($)&&$>Z)throw await LQ(Q),new b;if(Q.body){let G=Q.body.getReader(),J=[],K=0;while(!0){let j=await G.read();if(j.done)break;if(!j.value)continue;if(K+=j.value.byteLength,K>Z)throw await G.cancel?.(),new b;J.push(j.value)}let z=Buffer.concat(J.map((j)=>Buffer.from(j)),K).toString("utf8");return QQ(z)}if(Q.text){let G=await Q.text();if(Buffer.byteLength(G,"utf8")>Z)throw new b;return QQ(G)}throw new m}async function LQ(Q){if(!Q.body)return;try{await Q.body.getReader().cancel?.()}catch{}}function bQ(Q,Z){return new Promise(($)=>{let G=_Q(Z.timeoutMs,Z.signal),J=!1,K=null,z=(H)=>{if(J)return;J=!0,G.dispose(),$(H)},j=()=>{K?.destroy(),z(W(Z.signal?.aborted?"aborted":"timeout"))};G.signal.addEventListener("abort",j,{once:!0});try{K=(Q.protocol==="http:"?N0:U0)(Q,{method:"GET",headers:Z.headers,signal:G.signal},(X)=>D0(X,Z.maxResponseBytes,z)),K.on("error",()=>{if(Z.signal?.aborted)z(W("aborted"));else if(G.didTimeout())z(W("timeout"));else z(W("network-error"))}),K.end()}catch{z(W("network-error"))}})}function D0(Q,Z,$){let G=ZQ(Q.statusCode);if(G===null||G<200||G>=300){Q.resume(),$(W("http-error",G));return}let J=Number(Q.headers["content-length"]);if(Number.isFinite(J)&&J>Z){Q.destroy(),$(W("response-too-large"));return}let K=[],z=0;Q.on("data",(j)=>{let H=Buffer.isBuffer(j)?j:Buffer.from(j);if(z+=H.length,z>Z){Q.destroy(),$(W("response-too-large"));return}K.push(H)}),Q.on("end",()=>{try{let j=QQ(Buffer.concat(K,z).toString("utf8"));$({ok:!0,status:G,payload:j,transport:"node"})}catch{$(W("invalid-json"))}}),Q.on("error",()=>$(W("network-error")))}function _Q(Q,Z){let $=new AbortController,G=!1,J=Number.isFinite(Q)&&Q>0?Math.min(Math.floor(Q),2147483647):1,K=setTimeout(()=>{G=!0,$.abort()},J),z=()=>$.abort();return Z?.addEventListener("abort",z,{once:!0}),{signal:$.signal,didTimeout:()=>G,dispose:()=>{clearTimeout(K),Z?.removeEventListener("abort",z)}}}function T0(Q,Z){return Q.code==="network-error"||Q.code==="timeout"||Q.code==="invalid-json"||Z&&Q.code==="http-error"}function QQ(Q){try{return JSON.parse(Q)}catch{throw new m}}function ZQ(Q){return typeof Q==="number"&&Number.isInteger(Q)&&Q>=0?Q:null}function W(Q,Z=null){return{ok:!1,code:Q,status:Z}}class b extends Error{}class m extends Error{}function h(Q,Z){switch(Q.code){case"aborted":return N("network.request.aborted","network",`${Z} lookup was cancelled.`);case"http-error":return N("network.response.http","network",Q.status===null?`${Z} endpoint returned an invalid HTTP status.`:`${Z} endpoint returned HTTP ${Q.status}.`);case"invalid-json":return N("network.response.invalid-json","network",`${Z} endpoint returned malformed JSON.`);case"invalid-url":return N("network.endpoint.invalid","network",`${Z} endpoint URL is invalid.`);case"response-too-large":return N("network.response.too-large","network",`${Z} endpoint response was too large.`);case"timeout":return N("network.request.timeout","network",`${Z} lookup timed out.`);case"unsupported-protocol":return N("network.endpoint.protocol","network",`${Z} endpoint must use HTTPS or loopback HTTP.`);case"network-error":return N("network.request.failed","network",`${Z} lookup failed.`)}}$Q();var SQ=["credits","reset_credits","items"],EQ=["available_count","availableCount","available"],wQ=["total_earned_count","earned_this_period","earnedThisPeriod","totalEarnedCount"];function gQ(Q,Z,$){if(!Y(Q)||!b0(Q))return _(Z,["Live reset coupon endpoint returned an unexpected payload."]);let G=P0(Q,SQ);if(G.malformed)return _(Z,["Live reset coupon endpoint returned an unexpected payload."]);let J=G.value,K=J.map((V,q)=>L0(V,q+1,$)).filter((V)=>V!==null).sort(_0).map((V,q)=>({...V,index:q+1})),z=K.find((V)=>V.status?.toLowerCase()==="available")??K[0]??null,j=fQ(Q,EQ),H=fQ(Q,wQ),X=[];if(J.length!==K.length)X.push("Live reset coupon endpoint ignored malformed coupon entries.");if(j.malformed||H.malformed)X.push("Live reset coupon endpoint ignored malformed summary fields.");return{status:X.length>0?"partial":"available",available:j.value,earnedThisPeriod:H.value,nextExpirationDate:z?.expirationDate??null,nextExpirationIn:z?.expiresIn??null,items:K,warnings:X,source:{live:!0,label:"live Codex reset-credit endpoint",endpoint:Z}}}function _(Q,Z=[]){return{status:"unavailable",available:null,earnedThisPeriod:null,nextExpirationDate:null,nextExpirationIn:null,items:[],warnings:Z,source:{live:!1,label:"live Codex reset-credit endpoint",endpoint:Q}}}function b0(Q){return[...SQ,...EQ,...wQ].some((Z)=>(Z in Q))}function L0(Q,Z,$){if(!Y(Q))return null;let G=U(Q,"expires_at")??U(Q,"expiresAt"),J=U(Q,"granted_at")??U(Q,"grantedAt"),K=I(G),z=I(J),j=U(Q,"status"),H=j&&/^[a-z][a-z0-9_-]{0,63}$/i.test(j)?j:null,X=z?J:null,V=K?G:null;if(!H&&!X&&!V)return null;return{index:Z,status:H,grantedAt:X,expiresAt:V,expirationDate:K?RQ(K):null,expiresIn:K?c(K.getTime()-$.getTime()):null}}function _0(Q,Z){return yQ(Q.expiresAt)-yQ(Z.expiresAt)}function yQ(Q){return I(Q)?.getTime()??Number.POSITIVE_INFINITY}function P0(Q,Z){let $=!1;for(let G of Z){if(!(G in Q))continue;$=!0;let J=Q[G];if(Array.isArray(J))return{value:J,malformed:!1}}return{value:[],malformed:$}}function fQ(Q,Z){let $=!1;for(let G of Z){if(!(G in Q))continue;$=!0;let J=Q[G];if(typeof J==="number"&&Number.isFinite(J)&&J>=0)return{value:J,malformed:!1}}return{value:null,malformed:$}}var mQ="https://chatgpt.com/backend-api/wham/rate-limit-reset-credits",R0=1e4,y0=1e6;async function hQ(Q={}){let Z=Q.endpoint??mQ,$=L(Z),G=await g(Q);if(!G.credentials){let K=k(G.diagnostics);return f0($,K.length>0?K:["Live reset coupons require a readable Codex auth.json file or CODEX_LIMITS_ACCESS_TOKEN and CODEX_LIMITS_ACCOUNT_ID."])}let J={endpoint:Z,headers:{Authorization:`Bearer ${G.credentials.accessToken}`,"ChatGPT-Account-ID":G.credentials.accountId,"OpenAI-Beta":"codex-1",originator:"Codex Desktop",Accept:"application/json"},timeoutMs:Q.timeoutMs??R0,maxResponseBytes:y0,...Q.fetch?{fetch:Q.fetch}:{},...Q.signal?{signal:Q.signal}:{}};try{let K=await(Q.transport??v)(J);return K.ok?gQ(K.payload,$,Q.now??new Date):vQ($,K)}catch{return vQ($,{ok:!1,code:"network-error",status:null})}}function f0(Q=mQ,Z=[]){return _(L(Q),Z)}function vQ(Q,Z){return _(Q,k([h(Z,"Live reset coupon")]))}$Q();var lQ=5,KQ="5-hour usage limit",zQ="Weekly usage limit",rQ=["fiveHour","five_hour","fiveHourWindow","five_hour_window","primary","primaryWindow","primary_window","main","mainWindow"],oQ=["weekly","week","weeklyWindow","weekly_window","secondary","secondaryWindow","secondary_window","backup","backupWindow"],S0=["used_percent","usedPercent","used","usage","percentUsed","usagePercent","usagePercentage","percent"],E0=["remaining_percent","remainingPercent","remaining","percentRemaining","availablePercent","available_percentage"],w0=["resets_at","resetsAt","resetAt","resetTime","reset_at","reset","ends_at","endsAt","windowEnd"],g0=["resetsIn","resetIn","resets_in","reset_in","timeUntilReset"];function iQ(Q,Z=new Date){let $=Q.latestSnapshot;if(!$)return YQ(Q.warnings);return d(jQ($.rateLimits,Z),Q.warnings)}function jQ(Q,Z=new Date){return{fiveHour:p(uQ(Q,rQ),KQ,Z),weekly:p(uQ(Q,oQ),zQ,Z)}}function nQ(Q,Z){return{...Q,source:Z}}function HQ(Q,Z,$=[]){return{...d(Q,$),source:Z}}function sQ(Q,Z=new Date){let $={fiveHour:null,weekly:null};for(let G of Q.files){if(!G.json)continue;let J=v0(G.json,Z);$=aQ($,J.windows)}return d($,Q.warnings)}function YQ(Q=[]){return{status:"unavailable",windows:{fiveHour:null,weekly:null},warnings:Q}}function tQ(Q,Z){let $=aQ(Q.windows,Z.windows);return d($,[...Q.warnings,...Z.warnings])}function v0(Q,Z){if(!Y(Q))return{windows:{fiveHour:null,weekly:null}};let $=JQ(Q,rQ),G=JQ(Q,oQ),J=p($??(G?null:Q),KQ,Z),K=p(G,zQ,Z);return{windows:{fiveHour:J,weekly:K}}}function p(Q,Z,$){if(!Q)return null;let G=dQ(P(Q,S0,!0)),J=dQ(P(Q,E0,!0)),K=G??(J===null?null:u(100-J)),z=J??(G===null?null:u(100-G)),j=P(Q,w0,!0),H=I(j),X=H?H.toISOString():null,V=h0(P(Q,g0,!0)),q=H?c(H.getTime()-$.getTime()):V&&V.length<=100?F(V):null,C={label:Z,remainingPercent:z,usedPercent:K,resetsAt:X,resetsIn:q};return GQ(C)?C:null}function d(Q,Z){return{status:m0(Q),windows:Q,warnings:Z}}function m0(Q){if(pQ(Q.fiveHour)&&pQ(Q.weekly))return"available";if(GQ(Q.fiveHour)||GQ(Q.weekly))return"partial";return"unavailable"}function aQ(Q,Z){return{fiveHour:cQ(Q.fiveHour,Z.fiveHour,KQ),weekly:cQ(Q.weekly,Z.weekly,zQ)}}function cQ(Q,Z,$){if(!Q&&!Z)return null;return{label:$,remainingPercent:Q?.remainingPercent??Z?.remainingPercent??null,usedPercent:Q?.usedPercent??Z?.usedPercent??null,resetsAt:Q?.resetsAt??Z?.resetsAt??null,resetsIn:Q?.resetsIn??Z?.resetsIn??null}}function GQ(Q){return Q!==null&&(Q.remainingPercent!==null||Q.usedPercent!==null||Q.resetsAt!==null||Q.resetsIn!==null)}function pQ(Q){return Q!==null&&Q.remainingPercent!==null&&Q.usedPercent!==null&&(Q.resetsAt!==null||Q.resetsIn!==null)}function JQ(Q,Z,$=0){if($>lQ)return null;for(let G of Z){let J=Q[G];if(Y(J))return J}for(let G of Object.values(Q)){if(!Y(G))continue;let J=JQ(G,Z,$+1);if(J)return J}return null}function uQ(Q,Z){for(let $ of Z){let G=Q[$];if(Y(G))return G}return null}function P(Q,Z,$,G=0){if(G>lQ)return;for(let J of Z)if(J in Q)return Q[J];if(!$)return;for(let J of Object.values(Q)){if(!Y(J))continue;let K=P(J,Z,$,G+1);if(K!==void 0)return K}return}function dQ(Q){if(typeof Q==="number"&&Number.isFinite(Q))return u(Q);if(typeof Q==="string"&&Q.trim().length>0){let Z=Q.trim(),$=Z.endsWith("%")?Z.slice(0,-1):Z,G=Number($);return Number.isFinite(G)?u(G):null}return null}function u(Q){return Math.round(Math.min(Math.max(Q,0),100)*10)/10}function h0(Q){if(typeof Q==="string"&&Q.trim().length>0)return Q.trim();return null}var c0=5,p0=1000,u0={kind:"unavailable",label:"Unavailable"};function QZ(Q,Z,$){let G=d0(Q)??l0(Q);if(!G)return R(["Live usage endpoint returned an unexpected payload."]);let J=HQ(jQ(G,$),{kind:"api",label:"API",endpoint:Z});if(J.status==="unavailable")return R(["Live usage endpoint returned an unexpected payload."]);if(J.status==="partial")return{...J,warnings:["Live usage endpoint returned incomplete usage data."]};return J}function R(Q){return HQ({fiveHour:null,weekly:null},u0,Q)}function d0(Q){return ZZ(Q,(Z)=>{if(!Y(Z))return null;let $=Z.rate_limits??Z.rateLimits??Z.rate_limit??Z.rateLimit;if(Y($))return $;return Y(Z.primary)||Y(Z.secondary)||Y(Z.primary_window)||Y(Z.secondary_window)?Z:null})}function l0(Q){return ZZ(Q,(Z)=>{if(!Array.isArray(Z))return null;let $=Z.find((J)=>eQ(J,"primary")),G=Z.find((J)=>eQ(J,"secondary"));return $||G?{primary:$,secondary:G}:null})}function ZZ(Q,Z){let $=[{value:Q,depth:0}],G=new WeakSet;for(let J=0;J<$.length;J+=1){let K=$[J];if(!K)break;let z=Z(K.value);if(z)return z;if(K.depth>=c0||typeof K.value!=="object"||!K.value)continue;if(G.has(K.value))continue;G.add(K.value);let j=Array.isArray(K.value)?K.value:Y(K.value)?Object.values(K.value):[];for(let H of j){if($.length>=p0)break;$.push({value:H,depth:K.depth+1})}}return null}function eQ(Q,Z){if(!Y(Q))return!1;let $=String(Q.type??Q.kind??Q.name??Q.label??Q.window??"").toLowerCase();if(Z==="primary"&&($.includes("primary")||$.includes("5-hour")||$.includes("five")))return!0;if(Z==="secondary"&&($.includes("secondary")||$.includes("weekly")||$.includes("week")))return!0;let G=Q.window_minutes??Q.windowMinutes??Q.window_length_minutes??Q.windowLengthMinutes;return Z==="primary"?G===300:G===10080}var r0="https://chatgpt.com/backend-api/codex/usage",o0=1e4,i0=1e6;async function GZ(Q={}){let Z=s0(Q),$=L(Z),G=await g(Q);if(!G.credentials){let K=k(G.diagnostics);return R(K.length>0?K:["Live usage requires Codex authentication."])}let J={endpoint:Z,headers:n0(G.credentials),timeoutMs:Q.timeoutMs??o0,maxResponseBytes:i0,fallbackOnHttpError:!0,...Q.fetch?{fetch:Q.fetch}:{},...Q.signal?{signal:Q.signal}:{}};try{let K=await(Q.transport??v)(J);return K.ok?QZ(K.payload,$,Q.now??new Date):$Z(K)}catch{return $Z({ok:!1,code:"network-error",status:null})}}function n0(Q){return{Authorization:`Bearer ${Q.accessToken}`,"ChatGPT-Account-ID":Q.accountId,"OpenAI-Beta":"codex-1",originator:"Codex Desktop",Accept:"application/json","User-Agent":"Codex Desktop",Referer:"https://chatgpt.com/codex/cloud/settings/analytics",Origin:"https://chatgpt.com"}}function s0(Q){let Z=T(Q.env);return Q.usageEndpoint??M(Z,"CODEX_LIMITS_USAGE_ENDPOINT")??r0}function $Z(Q){return R(k([h(Q,"Live usage")]))}var t0={kind:"local",label:"Local"};async function JZ(Q={}){let Z=await a0(Q),$=Q.includeCoupons===!1?null:await hQ(Q),G=$?{...$,warnings:o($.warnings)}:null;return{windows:Z.windows,usageSource:Z.source,coupons:G,warnings:o([...Z.warnings,...G?.warnings??[]])}}async function a0(Q={}){let Z=await GZ(Q);if(Z.status==="available")return Z;let $=nQ(await Q1(Q),t0);return e0(Z,$)}function e0(Q,Z){if(Z.status!=="unavailable")return Z;if(Q.status==="partial")return{...Q,warnings:[...Q.warnings,...Z.warnings]};return{...Z,warnings:[...Q.warnings,...Z.warnings]}}async function Q1(Q={}){let Z=Q.now??new Date,$=await S(Q);if(!$.foundHome)return YQ(["No readable local Codex home directory was found."]);let[G,J]=await Promise.all([FQ($.foundHome),AQ($.foundHome)]);return tQ(iQ(G,Z),sQ(J,Z))}var XQ="Codex Limits",Z1="Check Codex limits, resets, and credits.",KZ="Could not load Codex limits.";function $1(Q={}){let Z=new WeakSet,$=Q.getLimits??JZ,G=Q.nextFrame??(()=>new Promise((J)=>setTimeout(J,0)));return{id:"codex-limits",tui:async(J)=>{if(Z.has(J))return;let K=G1(J,$,G),z;try{let H=J1(J,K);z=typeof H==="function"?H:void 0}catch{throw Error("Could not register the codex-limits OpenCode command.")}let j=()=>{if(!Z.delete(J))return;try{z?.()}catch{}};Z.add(J);try{J.lifecycle.onDispose(j)}catch{throw j(),Error("Could not register the codex-limits OpenCode lifecycle.")}}}}function G1(Q,Z,$){let G=0;return{title:XQ,value:"codex-limits.show",description:Z1,category:"Codex",slash:{name:"codex-limits"},onSelect:async(J)=>{let K=++G;J?.clear(),Q.ui.dialog.clear(),await $();let z=Q.ui.dialog,j=(H)=>z.replace(()=>Q.ui.DialogAlert({title:XQ,message:H}));z.setSize("large"),j("Loading Codex limits...");try{let H=await Z();if(K===G)j(qQ(H))}catch{if(K===G)Q.ui.toast({variant:"error",title:XQ,message:KZ}),j(KZ)}}}}function J1(Q,Z){let $=Q;if(typeof $.keymap?.registerLayer==="function")return $.keymap.registerLayer({commands:[{namespace:"palette",name:Z.value,title:Z.title,desc:Z.description,category:Z.category,slashName:Z.slash?.name,slashAliases:Z.slash?.aliases,run:()=>Z.onSelect?.()}],bindings:[]});if(typeof $.command?.register==="function")return $.command.register(()=>[Z]);throw Error("No supported OpenCode command API is available.")}var zZ=$1(),jZ=zZ,E2=zZ.tui;var HZ=jZ,h2=HZ,v2=HZ.tui;export{v2 as tui,h2 as default};
|