quota-axi 0.1.0
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/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/bin/quota-axi.d.ts +2 -0
- package/dist/bin/quota-axi.js +4 -0
- package/dist/bin/quota-axi.js.map +1 -0
- package/dist/src/cache.d.ts +3 -0
- package/dist/src/cache.js +198 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/cli.d.ts +18 -0
- package/dist/src/cli.js +152 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/lib/fs.d.ts +14 -0
- package/dist/src/lib/fs.js +72 -0
- package/dist/src/lib/fs.js.map +1 -0
- package/dist/src/lib/process.d.ts +4 -0
- package/dist/src/lib/process.js +77 -0
- package/dist/src/lib/process.js.map +1 -0
- package/dist/src/lib/time.d.ts +5 -0
- package/dist/src/lib/time.js +38 -0
- package/dist/src/lib/time.js.map +1 -0
- package/dist/src/providers/claude.d.ts +9 -0
- package/dist/src/providers/claude.js +425 -0
- package/dist/src/providers/claude.js.map +1 -0
- package/dist/src/providers/codex.d.ts +12 -0
- package/dist/src/providers/codex.js +501 -0
- package/dist/src/providers/codex.js.map +1 -0
- package/dist/src/providers/common.d.ts +19 -0
- package/dist/src/providers/common.js +61 -0
- package/dist/src/providers/common.js.map +1 -0
- package/dist/src/providers/index.d.ts +3 -0
- package/dist/src/providers/index.js +20 -0
- package/dist/src/providers/index.js.map +1 -0
- package/dist/src/render.d.ts +6 -0
- package/dist/src/render.js +94 -0
- package/dist/src/render.js.map +1 -0
- package/dist/src/types.d.ts +70 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
+
export function collapseHome(path) {
|
|
5
|
+
const home = homedir();
|
|
6
|
+
if (path === home)
|
|
7
|
+
return "~";
|
|
8
|
+
if (!isAbsolute(path) && !startsWithHomePrefix(path, home))
|
|
9
|
+
return path;
|
|
10
|
+
const relativePath = relative(home, path);
|
|
11
|
+
if (relativePath === "")
|
|
12
|
+
return "~";
|
|
13
|
+
if (isHomeRelativePath(relativePath))
|
|
14
|
+
return `~/${normalizeRelativePath(relativePath)}`;
|
|
15
|
+
if (startsWithHomePrefix(path, home))
|
|
16
|
+
return `~/${path.slice(home.length + 1).replace(/\\/g, "/")}`;
|
|
17
|
+
return path;
|
|
18
|
+
}
|
|
19
|
+
function isHomeRelativePath(path) {
|
|
20
|
+
return path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
|
|
21
|
+
}
|
|
22
|
+
function normalizeRelativePath(path) {
|
|
23
|
+
return sep === "\\" ? path.replace(/\\/g, "/") : path;
|
|
24
|
+
}
|
|
25
|
+
function startsWithHomePrefix(path, home) {
|
|
26
|
+
const separator = path[home.length];
|
|
27
|
+
return (separator !== undefined &&
|
|
28
|
+
(separator === "/" || separator === "\\") &&
|
|
29
|
+
samePath(path.slice(0, home.length), home));
|
|
30
|
+
}
|
|
31
|
+
function samePath(left, right) {
|
|
32
|
+
if (process.platform === "win32")
|
|
33
|
+
return left.toLowerCase() === right.toLowerCase();
|
|
34
|
+
return left === right;
|
|
35
|
+
}
|
|
36
|
+
export function cacheFilePath() {
|
|
37
|
+
const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
38
|
+
return join(base, "quota-axi", "quotas.json");
|
|
39
|
+
}
|
|
40
|
+
export function ensurePrivateParent(file) {
|
|
41
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
|
|
42
|
+
}
|
|
43
|
+
export function readJsonFile(file) {
|
|
44
|
+
const result = readJsonFileResult(file);
|
|
45
|
+
return result.status === "success" ? result.value : undefined;
|
|
46
|
+
}
|
|
47
|
+
export function readJsonFileResult(file) {
|
|
48
|
+
let text;
|
|
49
|
+
try {
|
|
50
|
+
text = readFileSync(file, "utf8");
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
if (errorCode(error) === "ENOENT")
|
|
54
|
+
return { status: "missing" };
|
|
55
|
+
return { status: "invalid", error: "file_read_error" };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return { status: "success", value: JSON.parse(text) };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return { status: "invalid", error: "json_parse_error" };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function errorCode(error) {
|
|
65
|
+
return error &&
|
|
66
|
+
typeof error === "object" &&
|
|
67
|
+
"code" in error &&
|
|
68
|
+
typeof error.code === "string"
|
|
69
|
+
? error.code
|
|
70
|
+
: undefined;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../../src/lib/fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
export declare function execFileText(command: string, args: string[], timeoutMs: number): Promise<string>;
|
|
3
|
+
export declare function commandExists(command: string): Promise<boolean>;
|
|
4
|
+
export declare function terminateChild(child: ChildProcess): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { access, stat } from "node:fs/promises";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
export function execFileText(command, args, timeoutMs) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
execFile(command, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024 }, (error, stdout) => {
|
|
8
|
+
if (error) {
|
|
9
|
+
reject(error);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
resolve(String(stdout));
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export async function commandExists(command) {
|
|
17
|
+
const normalized = command.trim();
|
|
18
|
+
if (normalized.length === 0)
|
|
19
|
+
return false;
|
|
20
|
+
for (const candidate of commandPathCandidates(normalized)) {
|
|
21
|
+
if (await isExecutableFile(candidate))
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
function commandPathCandidates(command) {
|
|
27
|
+
if (hasPathSeparator(command))
|
|
28
|
+
return executableCandidates(command);
|
|
29
|
+
const pathValue = process.env.PATH;
|
|
30
|
+
if (!pathValue)
|
|
31
|
+
return [];
|
|
32
|
+
const pathApi = process.platform === "win32" ? path.win32 : path;
|
|
33
|
+
const delimiter = process.platform === "win32" ? path.win32.delimiter : path.delimiter;
|
|
34
|
+
return pathValue
|
|
35
|
+
.split(delimiter)
|
|
36
|
+
.map((entry) => entry.replace(/^"|"$/g, "") || ".")
|
|
37
|
+
.flatMap((entry) => executableCandidates(pathApi.join(entry, command)));
|
|
38
|
+
}
|
|
39
|
+
function executableCandidates(file) {
|
|
40
|
+
if (process.platform !== "win32")
|
|
41
|
+
return [file];
|
|
42
|
+
const pathApi = path.win32;
|
|
43
|
+
if (pathApi.extname(file))
|
|
44
|
+
return [file];
|
|
45
|
+
const extensions = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
|
|
46
|
+
.split(";")
|
|
47
|
+
.map((extension) => extension.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
return extensions.map((extension) => `${file}${extension}`);
|
|
50
|
+
}
|
|
51
|
+
function hasPathSeparator(command) {
|
|
52
|
+
return command.includes("/") || command.includes("\\");
|
|
53
|
+
}
|
|
54
|
+
async function isExecutableFile(file) {
|
|
55
|
+
try {
|
|
56
|
+
const info = await stat(file);
|
|
57
|
+
if (!info.isFile())
|
|
58
|
+
return false;
|
|
59
|
+
await access(file, process.platform === "win32" ? constants.F_OK : constants.X_OK);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function terminateChild(child) {
|
|
67
|
+
child.stdin?.destroy();
|
|
68
|
+
child.stdout?.destroy();
|
|
69
|
+
child.stderr?.destroy();
|
|
70
|
+
child.kill("SIGTERM");
|
|
71
|
+
if (child.exitCode !== null || child.signalCode !== null)
|
|
72
|
+
return;
|
|
73
|
+
const forceKill = setTimeout(() => child.kill("SIGKILL"), 2000);
|
|
74
|
+
forceKill.unref();
|
|
75
|
+
child.once("exit", () => clearTimeout(forceKill));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../../src/lib/process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function nowIso(): string;
|
|
2
|
+
export declare function clampPercent(value: number): number;
|
|
3
|
+
export declare function percentRemaining(percentUsed: number | undefined): number | undefined;
|
|
4
|
+
export declare function parseEpochOrIso(value: unknown): string | undefined;
|
|
5
|
+
export declare function retryAfterToIso(value: string | null, now?: number): string | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function nowIso() {
|
|
2
|
+
return new Date().toISOString();
|
|
3
|
+
}
|
|
4
|
+
export function clampPercent(value) {
|
|
5
|
+
if (!Number.isFinite(value))
|
|
6
|
+
return 0;
|
|
7
|
+
return Math.min(100, Math.max(0, Math.round(value)));
|
|
8
|
+
}
|
|
9
|
+
export function percentRemaining(percentUsed) {
|
|
10
|
+
if (percentUsed === undefined)
|
|
11
|
+
return undefined;
|
|
12
|
+
return clampPercent(100 - percentUsed);
|
|
13
|
+
}
|
|
14
|
+
export function parseEpochOrIso(value) {
|
|
15
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
16
|
+
return new Date(value * 1000).toISOString();
|
|
17
|
+
}
|
|
18
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
19
|
+
const date = new Date(value);
|
|
20
|
+
if (!Number.isNaN(date.getTime())) {
|
|
21
|
+
return date.toISOString();
|
|
22
|
+
}
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
export function retryAfterToIso(value, now = Date.now()) {
|
|
28
|
+
const raw = value?.trim();
|
|
29
|
+
if (!raw)
|
|
30
|
+
return undefined;
|
|
31
|
+
const seconds = Number(raw);
|
|
32
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
33
|
+
return new Date(now + seconds * 1000).toISOString();
|
|
34
|
+
}
|
|
35
|
+
const date = new Date(raw);
|
|
36
|
+
return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time.js","sourceRoot":"","sources":["../../../src/lib/time.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,MAAM;IACpB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,WAA+B;IAE/B,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAChD,OAAO,YAAY,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,OAAO,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5B,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,KAAoB,EACpB,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAEhB,MAAM,GAAG,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,IAAI,CAAC,GAAG,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACtD,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AuthProviderReport, ProviderAdapter, ProviderOptions, ProviderQuota, QuotaWindow } from "../types.js";
|
|
2
|
+
export declare const claudeAdapter: ProviderAdapter;
|
|
3
|
+
export declare function fetchQuota(options: ProviderOptions): Promise<ProviderQuota>;
|
|
4
|
+
export declare function inspectAuth(options: ProviderOptions): Promise<AuthProviderReport>;
|
|
5
|
+
export declare function normalizeClaudeApiUsage(raw: unknown, plan?: string): {
|
|
6
|
+
plan?: string;
|
|
7
|
+
windows: QuotaWindow[];
|
|
8
|
+
refreshedAt: string;
|
|
9
|
+
} | undefined;
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readCachedProvider } from "../cache.js";
|
|
4
|
+
import { readJsonFileResult } from "../lib/fs.js";
|
|
5
|
+
import { execFileText } from "../lib/process.js";
|
|
6
|
+
import { clampPercent, nowIso, retryAfterToIso } from "../lib/time.js";
|
|
7
|
+
import { failedProvider, sourceNames, staleFromCache, statusFromError, successProvider, withRemaining, } from "./common.js";
|
|
8
|
+
const API_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
9
|
+
const OAUTH_BETA = "oauth-2025-04-20";
|
|
10
|
+
const CLAUDE_CODE_USER_AGENT = "claude-code/2.1.202";
|
|
11
|
+
const API_TIMEOUT_MS = 15_000;
|
|
12
|
+
const KEYCHAIN_PROMPT_TIMEOUT_MS = 60_000;
|
|
13
|
+
const KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE = 44;
|
|
14
|
+
const CREDENTIAL_FILE = join(homedir(), ".claude", ".credentials.json");
|
|
15
|
+
const KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
16
|
+
export const claudeAdapter = {
|
|
17
|
+
id: "claude",
|
|
18
|
+
label: "Claude",
|
|
19
|
+
fetchQuota,
|
|
20
|
+
inspectAuth,
|
|
21
|
+
};
|
|
22
|
+
export async function fetchQuota(options) {
|
|
23
|
+
const attempts = [];
|
|
24
|
+
let finalError = "Claude quota unavailable";
|
|
25
|
+
let retryAfter;
|
|
26
|
+
const credentialStates = await readCredentialStates(options);
|
|
27
|
+
const credentials = credentialStates
|
|
28
|
+
.filter((state) => state.status === "available")
|
|
29
|
+
.map((state) => state.credentials)
|
|
30
|
+
.sort((a, b) => {
|
|
31
|
+
if (process.platform === "darwin") {
|
|
32
|
+
if (a.source === "keychain" && b.source !== "keychain")
|
|
33
|
+
return -1;
|
|
34
|
+
if (b.source === "keychain" && a.source !== "keychain")
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
return (b.expiresAt ?? 0) - (a.expiresAt ?? 0);
|
|
38
|
+
});
|
|
39
|
+
for (const state of credentialStates) {
|
|
40
|
+
if (state.status === "available")
|
|
41
|
+
continue;
|
|
42
|
+
if (state.status === "skipped") {
|
|
43
|
+
attempts.push({
|
|
44
|
+
source: state.source.source,
|
|
45
|
+
status: "skipped",
|
|
46
|
+
error: state.source.error,
|
|
47
|
+
});
|
|
48
|
+
if (finalError === "Claude quota unavailable")
|
|
49
|
+
finalError = state.source.error ?? finalError;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
attempts.push({
|
|
53
|
+
source: state.source.source,
|
|
54
|
+
status: "skipped",
|
|
55
|
+
error: `credentials_${state.status}`,
|
|
56
|
+
});
|
|
57
|
+
finalError = "Claude sign-in required";
|
|
58
|
+
}
|
|
59
|
+
if (credentials.length > 0) {
|
|
60
|
+
for (const credential of credentials) {
|
|
61
|
+
attempts.push({ source: "oauth", status: "failed" });
|
|
62
|
+
try {
|
|
63
|
+
const quota = await fetchOauthUsage(credential);
|
|
64
|
+
attempts[attempts.length - 1] = { source: "oauth", status: "success" };
|
|
65
|
+
return successProvider({
|
|
66
|
+
provider: "claude",
|
|
67
|
+
label: "Claude",
|
|
68
|
+
source: "oauth",
|
|
69
|
+
plan: quota.plan,
|
|
70
|
+
account: quota.account,
|
|
71
|
+
windows: quota.windows,
|
|
72
|
+
refreshedAt: quota.refreshedAt,
|
|
73
|
+
sourcesTried: sourceNames(attempts),
|
|
74
|
+
attempts,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const message = errorMessage(error);
|
|
79
|
+
attempts[attempts.length - 1] = {
|
|
80
|
+
source: "oauth",
|
|
81
|
+
status: "failed",
|
|
82
|
+
error: message,
|
|
83
|
+
};
|
|
84
|
+
finalError = message;
|
|
85
|
+
if (error instanceof RateLimitError)
|
|
86
|
+
retryAfter = error.retryAfter;
|
|
87
|
+
if (message === "Claude sign-in required") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const cached = readCachedProvider("claude");
|
|
94
|
+
if (cached) {
|
|
95
|
+
return staleFromCache(cached, finalError, sourceNames(attempts), attempts);
|
|
96
|
+
}
|
|
97
|
+
return failedProvider({
|
|
98
|
+
provider: "claude",
|
|
99
|
+
label: "Claude",
|
|
100
|
+
status: retryAfter ? "rate_limited" : statusFromError(finalError),
|
|
101
|
+
error: finalError,
|
|
102
|
+
retryAfter,
|
|
103
|
+
sourcesTried: sourceNames(attempts),
|
|
104
|
+
attempts,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
export async function inspectAuth(options) {
|
|
108
|
+
const states = await readCredentialStates(options);
|
|
109
|
+
const sources = states.map((state) => {
|
|
110
|
+
if (state.status === "available") {
|
|
111
|
+
return {
|
|
112
|
+
source: state.credentials.source,
|
|
113
|
+
path: state.credentials.source === "oauth-file"
|
|
114
|
+
? CREDENTIAL_FILE
|
|
115
|
+
: undefined,
|
|
116
|
+
status: "available",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return state.source;
|
|
120
|
+
});
|
|
121
|
+
return { provider: "claude", sources };
|
|
122
|
+
}
|
|
123
|
+
export function normalizeClaudeApiUsage(raw, plan) {
|
|
124
|
+
if (!raw || typeof raw !== "object")
|
|
125
|
+
return undefined;
|
|
126
|
+
const data = raw;
|
|
127
|
+
// The `limits` array (when present) is the vendor's own authoritative list
|
|
128
|
+
// of every window the account currently has, including ones scoped to a
|
|
129
|
+
// specific model (e.g. Fable, Opus). Prefer it over the fixed top-level
|
|
130
|
+
// fields so newly introduced scoped limits show up without code changes.
|
|
131
|
+
const scopedWindows = normalizeScopedLimits(data.limits);
|
|
132
|
+
const windows = scopedWindows.length > 0
|
|
133
|
+
? scopedWindows
|
|
134
|
+
: [
|
|
135
|
+
normalizeWindow(data.five_hour, "five_hour", "session", "session"),
|
|
136
|
+
normalizeWindow(data.seven_day, "seven_day", "week", "weekly"),
|
|
137
|
+
normalizeWindow(data.seven_day_opus, "seven_day_opus", "opus week", "model"),
|
|
138
|
+
].filter((window) => Boolean(window));
|
|
139
|
+
const extraUsage = normalizeExtraUsage(data.extra_usage);
|
|
140
|
+
if (extraUsage)
|
|
141
|
+
windows.push(extraUsage);
|
|
142
|
+
if (windows.length === 0)
|
|
143
|
+
return undefined;
|
|
144
|
+
return { plan, windows, refreshedAt: nowIso() };
|
|
145
|
+
}
|
|
146
|
+
function normalizeScopedLimits(raw) {
|
|
147
|
+
if (!Array.isArray(raw))
|
|
148
|
+
return [];
|
|
149
|
+
const windows = [];
|
|
150
|
+
for (const entry of raw) {
|
|
151
|
+
const window = normalizeScopedLimitEntry(entry);
|
|
152
|
+
if (window)
|
|
153
|
+
windows.push(window);
|
|
154
|
+
}
|
|
155
|
+
return windows;
|
|
156
|
+
}
|
|
157
|
+
function normalizeScopedLimitEntry(raw) {
|
|
158
|
+
if (!raw || typeof raw !== "object")
|
|
159
|
+
return undefined;
|
|
160
|
+
const entry = raw;
|
|
161
|
+
const percent = typeof entry.percent === "number" ? entry.percent : undefined;
|
|
162
|
+
if (percent === undefined)
|
|
163
|
+
return undefined;
|
|
164
|
+
const resetsAt = stringValue(entry.resets_at);
|
|
165
|
+
const scope = objectValue(entry.scope);
|
|
166
|
+
const model = scope ? objectValue(scope.model) : undefined;
|
|
167
|
+
const modelName = model ? stringValue(model.display_name) : undefined;
|
|
168
|
+
if (modelName) {
|
|
169
|
+
const modelKey = stringValue(model?.id) ?? slugify(modelName);
|
|
170
|
+
return withRemaining({
|
|
171
|
+
id: `model:${modelKey}`,
|
|
172
|
+
label: `${modelName} week`,
|
|
173
|
+
kind: "model",
|
|
174
|
+
percentUsed: clampPercent(percent),
|
|
175
|
+
resetsAt,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const group = stringValue(entry.group);
|
|
179
|
+
if (group === "session") {
|
|
180
|
+
return withRemaining({
|
|
181
|
+
id: "five_hour",
|
|
182
|
+
label: "session",
|
|
183
|
+
kind: "session",
|
|
184
|
+
percentUsed: clampPercent(percent),
|
|
185
|
+
resetsAt,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (group === "weekly") {
|
|
189
|
+
return withRemaining({
|
|
190
|
+
id: "seven_day",
|
|
191
|
+
label: "week",
|
|
192
|
+
kind: "weekly",
|
|
193
|
+
percentUsed: clampPercent(percent),
|
|
194
|
+
resetsAt,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const kind = stringValue(entry.kind);
|
|
198
|
+
return withRemaining({
|
|
199
|
+
id: kind ?? "limit",
|
|
200
|
+
label: kind ?? "limit",
|
|
201
|
+
kind: "unknown",
|
|
202
|
+
percentUsed: clampPercent(percent),
|
|
203
|
+
resetsAt,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function slugify(value) {
|
|
207
|
+
return value
|
|
208
|
+
.trim()
|
|
209
|
+
.toLowerCase()
|
|
210
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
211
|
+
.replace(/^_+|_+$/g, "");
|
|
212
|
+
}
|
|
213
|
+
async function readCredentialStates(options) {
|
|
214
|
+
const states = [];
|
|
215
|
+
const fileState = extractCredentialState(readJsonFileResult(CREDENTIAL_FILE), "oauth-file", CREDENTIAL_FILE);
|
|
216
|
+
states.push(fileState);
|
|
217
|
+
if (process.platform === "darwin") {
|
|
218
|
+
if (!options.allowKeychainPrompt) {
|
|
219
|
+
states.push({
|
|
220
|
+
status: "skipped",
|
|
221
|
+
source: {
|
|
222
|
+
source: "keychain",
|
|
223
|
+
status: "skipped",
|
|
224
|
+
error: "keychain_prompt_required",
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
states.push(await readKeychainCredentialState());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return states;
|
|
233
|
+
}
|
|
234
|
+
async function readKeychainCredentialState() {
|
|
235
|
+
let blob;
|
|
236
|
+
try {
|
|
237
|
+
blob = await execFileText("security", ["find-generic-password", "-s", KEYCHAIN_SERVICE, "-w"], KEYCHAIN_PROMPT_TIMEOUT_MS);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return keychainFailureState(error);
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
return extractCredentialState({ status: "success", value: JSON.parse(blob) }, "keychain");
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return {
|
|
247
|
+
status: "invalid",
|
|
248
|
+
source: {
|
|
249
|
+
source: "keychain",
|
|
250
|
+
status: "invalid",
|
|
251
|
+
error: "json_parse_error",
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function keychainFailureState(error) {
|
|
257
|
+
const failure = error;
|
|
258
|
+
if (failure.killed || failure.signal) {
|
|
259
|
+
return {
|
|
260
|
+
status: "skipped",
|
|
261
|
+
source: {
|
|
262
|
+
source: "keychain",
|
|
263
|
+
status: "skipped",
|
|
264
|
+
error: "keychain_prompt_timeout",
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (failure.code === KEYCHAIN_ITEM_NOT_FOUND_EXIT_CODE) {
|
|
269
|
+
return {
|
|
270
|
+
status: "missing",
|
|
271
|
+
source: { source: "keychain", status: "missing" },
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
status: "skipped",
|
|
276
|
+
source: {
|
|
277
|
+
source: "keychain",
|
|
278
|
+
status: "skipped",
|
|
279
|
+
error: "keychain_access_denied",
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function extractCredentialState(raw, source, path) {
|
|
284
|
+
if (raw.status === "missing")
|
|
285
|
+
return { status: "missing", source: { source, path, status: "missing" } };
|
|
286
|
+
if (raw.status === "invalid")
|
|
287
|
+
return {
|
|
288
|
+
status: "invalid",
|
|
289
|
+
source: { source, path, status: "invalid", error: raw.error },
|
|
290
|
+
};
|
|
291
|
+
const data = objectValue(raw.value);
|
|
292
|
+
if (!data)
|
|
293
|
+
return { status: "invalid", source: { source, path, status: "invalid" } };
|
|
294
|
+
const oauth = data.claudeAiOauth && typeof data.claudeAiOauth === "object"
|
|
295
|
+
? data.claudeAiOauth
|
|
296
|
+
: data;
|
|
297
|
+
const accessToken = stringValue(oauth.accessToken) ?? stringValue(oauth.access_token);
|
|
298
|
+
if (!accessToken)
|
|
299
|
+
return { status: "invalid", source: { source, path, status: "invalid" } };
|
|
300
|
+
const expiresAt = expiresAtMillis(oauth.expiresAt);
|
|
301
|
+
if (expiresAt !== undefined && expiresAt <= Date.now())
|
|
302
|
+
return { status: "expired", source: { source, path, status: "expired" } };
|
|
303
|
+
const plan = stringValue(oauth.subscriptionType) ?? stringValue(data.subscriptionType);
|
|
304
|
+
return {
|
|
305
|
+
status: "available",
|
|
306
|
+
credentials: { source, accessToken, plan, expiresAt },
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async function fetchOauthUsage(credentials) {
|
|
310
|
+
const controller = new AbortController();
|
|
311
|
+
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
312
|
+
try {
|
|
313
|
+
const response = await fetch(API_URL, {
|
|
314
|
+
headers: {
|
|
315
|
+
authorization: `Bearer ${credentials.accessToken}`,
|
|
316
|
+
"anthropic-beta": OAUTH_BETA,
|
|
317
|
+
"User-Agent": CLAUDE_CODE_USER_AGENT,
|
|
318
|
+
"Content-Type": "application/json",
|
|
319
|
+
accept: "application/json",
|
|
320
|
+
},
|
|
321
|
+
signal: controller.signal,
|
|
322
|
+
});
|
|
323
|
+
rejectUnusableUsageResponse(response);
|
|
324
|
+
const quota = normalizeClaudeApiUsage(await response.json(), credentials.plan);
|
|
325
|
+
if (!quota)
|
|
326
|
+
throw new Error("Claude quota unavailable");
|
|
327
|
+
return quota;
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
clearTimeout(timer);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Anthropic's OAuth usage endpoint follows plain HTTP semantics: 401/403 mean
|
|
334
|
+
// the access token no longer authenticates, and 429 means the caller must
|
|
335
|
+
// back off, honoring the standard `Retry-After` header (RFC 9110 - either a
|
|
336
|
+
// delay in seconds or an HTTP-date).
|
|
337
|
+
function rejectUnusableUsageResponse(response) {
|
|
338
|
+
if (response.status === 401 || response.status === 403) {
|
|
339
|
+
throw new Error("Claude sign-in required");
|
|
340
|
+
}
|
|
341
|
+
if (response.status === 429) {
|
|
342
|
+
throw new RateLimitError(retryAfterToIso(response.headers.get("retry-after")));
|
|
343
|
+
}
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
throw new Error(`Claude quota unavailable (${response.status})`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function normalizeWindow(raw, id, label, kind) {
|
|
349
|
+
if (!raw || typeof raw !== "object")
|
|
350
|
+
return undefined;
|
|
351
|
+
const data = raw;
|
|
352
|
+
const used = typeof data.utilization === "number" ? data.utilization : undefined;
|
|
353
|
+
if (used === undefined)
|
|
354
|
+
return undefined;
|
|
355
|
+
return withRemaining({
|
|
356
|
+
id,
|
|
357
|
+
label,
|
|
358
|
+
kind,
|
|
359
|
+
percentUsed: clampPercent(used),
|
|
360
|
+
resetsAt: stringValue(data.resets_at) ?? stringValue(data.reset_at),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function normalizeExtraUsage(raw) {
|
|
364
|
+
if (!raw || typeof raw !== "object")
|
|
365
|
+
return undefined;
|
|
366
|
+
const data = raw;
|
|
367
|
+
if (data.is_enabled !== true)
|
|
368
|
+
return undefined;
|
|
369
|
+
const decimalPlaces = typeof data.decimal_places === "number" ? data.decimal_places : 2;
|
|
370
|
+
const minorUnitDivisor = 10 ** decimalPlaces;
|
|
371
|
+
const spentUsd = typeof data.used_credits === "number"
|
|
372
|
+
? data.used_credits / minorUnitDivisor
|
|
373
|
+
: undefined;
|
|
374
|
+
const limitUsd = typeof data.monthly_limit === "number"
|
|
375
|
+
? data.monthly_limit / minorUnitDivisor
|
|
376
|
+
: undefined;
|
|
377
|
+
const percentUsed = typeof data.utilization === "number"
|
|
378
|
+
? clampPercent(data.utilization)
|
|
379
|
+
: spentUsd !== undefined && limitUsd && limitUsd > 0
|
|
380
|
+
? clampPercent((spentUsd / limitUsd) * 100)
|
|
381
|
+
: undefined;
|
|
382
|
+
return withRemaining({
|
|
383
|
+
id: "extra_usage",
|
|
384
|
+
label: "extra usage",
|
|
385
|
+
kind: "credits",
|
|
386
|
+
percentUsed,
|
|
387
|
+
spentUsd,
|
|
388
|
+
limitUsd,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function objectValue(value) {
|
|
392
|
+
return value && typeof value === "object"
|
|
393
|
+
? value
|
|
394
|
+
: undefined;
|
|
395
|
+
}
|
|
396
|
+
function stringValue(value) {
|
|
397
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
398
|
+
}
|
|
399
|
+
function expiresAtMillis(value) {
|
|
400
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
401
|
+
return value;
|
|
402
|
+
if (typeof value !== "string")
|
|
403
|
+
return undefined;
|
|
404
|
+
const trimmed = value.trim();
|
|
405
|
+
if (!trimmed)
|
|
406
|
+
return undefined;
|
|
407
|
+
const numeric = Number(trimmed);
|
|
408
|
+
if (Number.isFinite(numeric))
|
|
409
|
+
return numeric;
|
|
410
|
+
const parsed = Date.parse(trimmed);
|
|
411
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
412
|
+
}
|
|
413
|
+
function errorMessage(error) {
|
|
414
|
+
if (error instanceof Error && error.name === "AbortError")
|
|
415
|
+
return "Claude quota request timed out";
|
|
416
|
+
return error instanceof Error ? error.message : "Claude quota unavailable";
|
|
417
|
+
}
|
|
418
|
+
class RateLimitError extends Error {
|
|
419
|
+
retryAfter;
|
|
420
|
+
constructor(retryAfter) {
|
|
421
|
+
super("Claude quota endpoint rate limited");
|
|
422
|
+
this.retryAfter = retryAfter;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
//# sourceMappingURL=claude.js.map
|