@metyatech/ai-quota 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 metyatech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # @metyatech/ai-quota
2
+
3
+ AI agent quota/rate-limit fetching library for Claude, Gemini, Copilot, Amazon Q, and Codex.
4
+
5
+ This package extracts the **quota fetching** layer from agent-runner so it can be reused
6
+ independently. Gate/ramp evaluation logic (e.g. `evaluateUsageGate`) is intentionally kept
7
+ out of this package — it remains in the calling application.
8
+
9
+ ## CLI
10
+
11
+ After installing the package globally or via `npx`, run `ai-quota` to check quota for all agents at once.
12
+
13
+ ```bash
14
+ # Install globally
15
+ npm install -g @metyatech/ai-quota
16
+
17
+ # Or use with npx (no install required)
18
+ npx @metyatech/ai-quota
19
+ ```
20
+
21
+ ### Commands
22
+
23
+ ```
24
+ ai-quota [agent] Show quota for all agents, or a single named agent
25
+ ai-quota --json Machine-readable JSON output
26
+ ai-quota --quiet Suppress non-error output (useful in scripts)
27
+ ai-quota --verbose Print debug info to stderr
28
+ ai-quota --help Show usage information
29
+ ai-quota --version Show version
30
+ ```
31
+
32
+ Supported agent names: `claude`, `gemini`, `copilot`, `amazon-q`, `codex`
33
+
34
+ ### Human-readable output example
35
+
36
+ ```
37
+ claude: 72% used (resets in 3h 12m)
38
+ gemini: 45% used (resets in 18h)
39
+ copilot: 28% used (resets in 9d)
40
+ amazon-q: 12/50 requests used
41
+ codex: 38% used (resets in 6h)
42
+ ```
43
+
44
+ ### JSON output example
45
+
46
+ ```bash
47
+ ai-quota --json
48
+ ```
49
+
50
+ ```json
51
+ {
52
+ "claude": { "usedPercent": 72, "resetsAt": "2026-02-19T18:00:00+09:00" },
53
+ "gemini": { "usedPercent": 45, "resetsAt": "2026-02-20T00:00:00Z" },
54
+ "copilot": { "usedPercent": 28, "resetsAt": "2026-03-01T00:00:00Z" },
55
+ "amazon-q": { "used": 12, "limit": 50, "percentRemaining": 76, "resetsAt": "2026-03-01T00:00:00Z" },
56
+ "codex": { "usedPercent": 38, "resetsAt": "2026-02-19T22:00:00Z" }
57
+ }
58
+ ```
59
+
60
+ ### Credential lookup
61
+
62
+ | Agent | Source |
63
+ |-----------|------------------------------------------------------------------------|
64
+ | Claude | `~/.claude/.credentials.json` |
65
+ | Gemini | `~/.gemini/oauth_creds.json` |
66
+ | Copilot | `GITHUB_TOKEN` env var, or `~/.config/gh/hosts.yml` (gh CLI token) |
67
+ | Amazon Q | `AMAZON_Q_STATE_PATH` env var (defaults to `~/agent-runner/state/`) |
68
+ | Codex | `~/.codex/sessions/` JSONL files, or `~/.codex/auth.json` |
69
+
70
+ Exit code is `0` on success. Exit code `1` if any agent fetch fails.
71
+
72
+ ## Supported agents
73
+
74
+ | Agent | Source | API type |
75
+ |-----------|---------------------------------------------|-----------------------|
76
+ | Claude | `~/.claude/.credentials.json` | REST (Anthropic OAuth)|
77
+ | Gemini | `~/.gemini/oauth_creds.json` | REST (Google OAuth) |
78
+ | Copilot | GitHub token (caller-provided) | REST (GitHub API) |
79
+ | Amazon Q | Local JSON counter file | Local state (see note)|
80
+ | Codex | JSONL session files / ChatGPT backend API | Local files + REST |
81
+
82
+ > **Amazon Q limitation:** Amazon Q Developer (free tier) does not provide a public API for
83
+ > querying usage or quota programmatically as of February 2026. There is no official AWS SDK
84
+ > method, REST endpoint, or CLI command that returns the number of agentic requests consumed
85
+ > against the monthly free-tier limit for Builder ID users. This library uses a local JSON
86
+ > counter file as the best available approach. Call `recordAmazonQUsage` after each Amazon Q
87
+ > invocation to keep the counter accurate.
88
+
89
+ ## Requirements
90
+
91
+ - Node.js >= 18
92
+ - TypeScript (peer — types are included in the package)
93
+
94
+ ## Installation
95
+
96
+ ```bash
97
+ npm install @metyatech/ai-quota
98
+ ```
99
+
100
+ ## Usage
101
+
102
+ ### Claude
103
+
104
+ ```typescript
105
+ import { fetchClaudeRateLimits } from "@metyatech/ai-quota";
106
+
107
+ const usage = await fetchClaudeRateLimits();
108
+ if (usage) {
109
+ console.log("5h utilization:", usage.five_hour?.utilization);
110
+ console.log("7-day utilization:", usage.seven_day?.utilization);
111
+ }
112
+ ```
113
+
114
+ ### Gemini
115
+
116
+ ```typescript
117
+ import { fetchGeminiRateLimits } from "@metyatech/ai-quota";
118
+
119
+ const usage = await fetchGeminiRateLimits();
120
+ if (usage) {
121
+ const pro = usage["gemini-3-pro-preview"];
122
+ console.log("Pro used %:", pro?.usage, "/ limit:", pro?.limit);
123
+ }
124
+ ```
125
+
126
+ Set `AGENT_RUNNER_GEMINI_OAUTH_CLIENT_ID` and `AGENT_RUNNER_GEMINI_OAUTH_CLIENT_SECRET` when
127
+ the Gemini CLI is not installed, or extract them from the Gemini CLI source.
128
+
129
+ ### Copilot
130
+
131
+ ```typescript
132
+ import { fetchCopilotRateLimits } from "@metyatech/ai-quota";
133
+
134
+ const usage = await fetchCopilotRateLimits({ token: process.env.GITHUB_TOKEN! });
135
+ if (usage) {
136
+ console.log("Percent remaining:", usage.percentRemaining);
137
+ console.log("Resets at:", usage.resetAt);
138
+ }
139
+ ```
140
+
141
+ Options:
142
+
143
+ | Option | Type | Default | Description |
144
+ |------------------|----------|----------------------------|------------------------------------------|
145
+ | `token` | `string` | required | GitHub personal access token |
146
+ | `timeoutSeconds` | `number` | `20` | Request timeout in seconds |
147
+ | `apiBaseUrl` | `string` | `https://api.github.com` | Override GitHub API base URL |
148
+ | `apiVersion` | `string` | `2025-05-01` | GitHub API version header |
149
+
150
+ ### Amazon Q
151
+
152
+ ```typescript
153
+ import {
154
+ fetchAmazonQRateLimits,
155
+ recordAmazonQUsage,
156
+ resolveAmazonQUsageStatePath
157
+ } from "@metyatech/ai-quota";
158
+
159
+ const statePath = resolveAmazonQUsageStatePath("/path/to/workdir");
160
+
161
+ // After each Amazon Q invocation:
162
+ recordAmazonQUsage(statePath);
163
+
164
+ // Check current quota:
165
+ const snapshot = fetchAmazonQRateLimits(statePath, 50 /* monthly limit */);
166
+ console.log("Used:", snapshot.used, "/", snapshot.limit);
167
+ console.log("Remaining:", snapshot.percentRemaining, "%");
168
+ ```
169
+
170
+ ### Codex
171
+
172
+ ```typescript
173
+ import { fetchCodexRateLimits, rateLimitSnapshotToStatus } from "@metyatech/ai-quota";
174
+
175
+ const snapshot = await fetchCodexRateLimits({ codexHome: "~/.codex" });
176
+ if (snapshot) {
177
+ const status = rateLimitSnapshotToStatus(snapshot);
178
+ const weekly = status?.windows.find((w) => w.key === "weekly");
179
+ console.log("Weekly % left:", weekly?.percentLeft);
180
+ }
181
+ ```
182
+
183
+ Options for `fetchCodexRateLimits`:
184
+
185
+ | Option | Type | Default | Description |
186
+ |------------------|------------|----------------|------------------------------------------|
187
+ | `codexHome` | `string` | `~/.codex` | Path to the Codex home directory |
188
+ | `timeoutSeconds` | `number` | `20` | HTTP API fallback timeout in seconds |
189
+ | `timingSink` | `function` | none | Callback for per-phase timing (ms) |
190
+
191
+ ## Dev commands
192
+
193
+ ```bash
194
+ npm install # install dependencies
195
+ npm run build # compile TypeScript to dist/
196
+ npm test # run tests with vitest
197
+ npm run lint # ESLint + tsc typecheck
198
+ npm run format # Prettier format
199
+ npm run verify # lint + test + build (full CI suite)
200
+ ```
201
+
202
+ ## Environment variables
203
+
204
+ | Variable | Used by | Purpose |
205
+ |---------------------------------------|---------|-------------------------------------------------|
206
+ | `AGENT_RUNNER_GEMINI_OAUTH_CLIENT_ID` | Gemini | Override OAuth client ID when Gemini CLI absent |
207
+ | `AGENT_RUNNER_GEMINI_OAUTH_CLIENT_SECRET` | Gemini | Override OAuth client secret |
208
+
209
+ ## SemVer policy
210
+
211
+ Breaking changes (removed/renamed exports, changed function signatures) bump the major version.
212
+ New exports and backward-compatible changes bump the minor version.
213
+ Bug fixes bump the patch version.
214
+
215
+ ## Links
216
+
217
+ - [CHANGELOG.md](./CHANGELOG.md)
218
+ - [SECURITY.md](./SECURITY.md)
219
+ - [CONTRIBUTING.md](./CONTRIBUTING.md)
220
+ - [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
221
+ - [LICENSE](./LICENSE)
@@ -0,0 +1,54 @@
1
+ import type { AmazonQUsageSnapshot } from "./types.js";
2
+ export type { AmazonQUsageSnapshot } from "./types.js";
3
+ /**
4
+ * NOTE: Amazon Q Developer (free tier) does not provide a public API for
5
+ * querying usage or quota programmatically (as of February 2026). There is
6
+ * no official AWS SDK method, REST endpoint, or CLI command that returns
7
+ * the number of agentic requests consumed against the 50-requests/month
8
+ * free-tier limit for Builder ID users.
9
+ *
10
+ * The approach used here — a local JSON counter file — is therefore the
11
+ * best available strategy. Callers must record each Amazon Q invocation
12
+ * with `recordAmazonQUsage` to keep the counter accurate.
13
+ *
14
+ * If AWS publishes an official API in the future, this file should be
15
+ * updated to call it (with the local counter as a fallback).
16
+ */
17
+ type AmazonQUsageState = {
18
+ periodKey: string;
19
+ used: number;
20
+ updatedAt: string;
21
+ };
22
+ /**
23
+ * Resolves the default state file path for the Amazon Q usage counter.
24
+ *
25
+ * @param workdirRoot - The root directory of the agent-runner working directory.
26
+ */
27
+ export declare function resolveAmazonQUsageStatePath(workdirRoot: string): string;
28
+ /**
29
+ * Loads the Amazon Q usage state from disk.
30
+ *
31
+ * Returns a fresh zeroed state when the file does not exist or when the
32
+ * stored period key does not match the current month (auto-reset).
33
+ */
34
+ export declare function loadAmazonQUsageState(statePath: string, now?: Date): AmazonQUsageState;
35
+ /**
36
+ * Persists Amazon Q usage state to disk.
37
+ */
38
+ export declare function saveAmazonQUsageState(statePath: string, state: AmazonQUsageState): void;
39
+ /**
40
+ * Increments the local Amazon Q usage counter by `count` (default 1) and
41
+ * persists the updated state.
42
+ *
43
+ * Call this once per Amazon Q invocation to keep the counter accurate.
44
+ */
45
+ export declare function recordAmazonQUsage(statePath: string, count?: number, now?: Date): AmazonQUsageState;
46
+ /**
47
+ * Reads the local counter and returns a quota snapshot.
48
+ *
49
+ * The `monthlyLimit` parameter is the configured monthly request limit
50
+ * (e.g. 50 for the free tier). The snapshot's `percentRemaining` is
51
+ * computed from `(limit - used) / limit * 100`.
52
+ */
53
+ export declare function fetchAmazonQRateLimits(statePath: string, monthlyLimit: number, now?: Date): AmazonQUsageSnapshot;
54
+ //# sourceMappingURL=amazon-q.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amazon-q.d.ts","sourceRoot":"","sources":["../src/amazon-q.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AAEH,KAAK,iBAAiB,GAAG;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAiBF;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAExE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,IAAiB,GACrB,iBAAiB,CAkCnB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAGvF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAU,EACjB,GAAG,GAAE,IAAiB,GACrB,iBAAiB,CAUnB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,GAAG,GAAE,IAAiB,GACrB,oBAAoB,CAatB"}
@@ -0,0 +1,103 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ function resolveMonthlyPeriodKey(now) {
4
+ const year = now.getUTCFullYear();
5
+ const month = now.getUTCMonth() + 1;
6
+ return `${year}-${String(month).padStart(2, "0")}`;
7
+ }
8
+ function resolveNextMonthlyResetAt(now) {
9
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0));
10
+ }
11
+ function normalizeUsed(value) {
12
+ if (typeof value !== "number" || !Number.isFinite(value))
13
+ return null;
14
+ return Math.max(0, Math.floor(value));
15
+ }
16
+ /**
17
+ * Resolves the default state file path for the Amazon Q usage counter.
18
+ *
19
+ * @param workdirRoot - The root directory of the agent-runner working directory.
20
+ */
21
+ export function resolveAmazonQUsageStatePath(workdirRoot) {
22
+ return path.resolve(workdirRoot, "agent-runner", "state", "amazon-q-usage.json");
23
+ }
24
+ /**
25
+ * Loads the Amazon Q usage state from disk.
26
+ *
27
+ * Returns a fresh zeroed state when the file does not exist or when the
28
+ * stored period key does not match the current month (auto-reset).
29
+ */
30
+ export function loadAmazonQUsageState(statePath, now = new Date()) {
31
+ const currentPeriodKey = resolveMonthlyPeriodKey(now);
32
+ if (!fs.existsSync(statePath)) {
33
+ return {
34
+ periodKey: currentPeriodKey,
35
+ used: 0,
36
+ updatedAt: now.toISOString()
37
+ };
38
+ }
39
+ const raw = fs.readFileSync(statePath, "utf8");
40
+ const parsed = JSON.parse(raw);
41
+ if (!parsed || typeof parsed !== "object") {
42
+ throw new Error(`Invalid Amazon Q usage state at ${statePath}`);
43
+ }
44
+ const record = parsed;
45
+ const periodKey = typeof record.periodKey === "string" ? record.periodKey : null;
46
+ const used = normalizeUsed(record.used);
47
+ const updatedAt = typeof record.updatedAt === "string" ? record.updatedAt : null;
48
+ if (!periodKey || used === null || !updatedAt) {
49
+ throw new Error(`Invalid Amazon Q usage state at ${statePath}`);
50
+ }
51
+ if (periodKey !== currentPeriodKey) {
52
+ return {
53
+ periodKey: currentPeriodKey,
54
+ used: 0,
55
+ updatedAt: now.toISOString()
56
+ };
57
+ }
58
+ return { periodKey, used, updatedAt };
59
+ }
60
+ /**
61
+ * Persists Amazon Q usage state to disk.
62
+ */
63
+ export function saveAmazonQUsageState(statePath, state) {
64
+ fs.mkdirSync(path.dirname(statePath), { recursive: true });
65
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
66
+ }
67
+ /**
68
+ * Increments the local Amazon Q usage counter by `count` (default 1) and
69
+ * persists the updated state.
70
+ *
71
+ * Call this once per Amazon Q invocation to keep the counter accurate.
72
+ */
73
+ export function recordAmazonQUsage(statePath, count = 1, now = new Date()) {
74
+ const normalizedCount = Math.max(0, Math.floor(count));
75
+ const state = loadAmazonQUsageState(statePath, now);
76
+ const updated = {
77
+ periodKey: state.periodKey,
78
+ used: state.used + normalizedCount,
79
+ updatedAt: now.toISOString()
80
+ };
81
+ saveAmazonQUsageState(statePath, updated);
82
+ return updated;
83
+ }
84
+ /**
85
+ * Reads the local counter and returns a quota snapshot.
86
+ *
87
+ * The `monthlyLimit` parameter is the configured monthly request limit
88
+ * (e.g. 50 for the free tier). The snapshot's `percentRemaining` is
89
+ * computed from `(limit - used) / limit * 100`.
90
+ */
91
+ export function fetchAmazonQRateLimits(statePath, monthlyLimit, now = new Date()) {
92
+ const state = loadAmazonQUsageState(statePath, now);
93
+ const used = Math.max(0, state.used);
94
+ const percentRemaining = monthlyLimit <= 0 ? 0 : Math.min(100, Math.max(0, ((monthlyLimit - used) / monthlyLimit) * 100));
95
+ return {
96
+ used,
97
+ limit: monthlyLimit,
98
+ percentRemaining,
99
+ resetAt: resolveNextMonthlyResetAt(now),
100
+ periodKey: state.periodKey
101
+ };
102
+ }
103
+ //# sourceMappingURL=amazon-q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amazon-q.js","sourceRoot":"","sources":["../src/amazon-q.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { ClaudeUsageData } from "./types.js";
2
+ export type { ClaudeUsageData, ClaudeUsageBucket } from "./types.js";
3
+ /**
4
+ * Fetches Claude usage data from the Anthropic OAuth usage API.
5
+ *
6
+ * Returns null when credentials are unavailable, expired, or the request fails.
7
+ * Credentials are read from `~/.claude/.credentials.json` (Claude desktop app format).
8
+ */
9
+ export declare function fetchClaudeRateLimits(timeoutMs?: number): Promise<ClaudeUsageData | null>;
10
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAiCrE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,SAAS,GAAE,MAAa,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAkErG"}
package/dist/claude.js ADDED
@@ -0,0 +1,105 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ function getClaudeConfigDir() {
4
+ const home = process.env.USERPROFILE ?? process.env.HOME ?? "";
5
+ return path.join(home, ".claude");
6
+ }
7
+ function readClaudeCredentials() {
8
+ const credsPath = path.join(getClaudeConfigDir(), ".credentials.json");
9
+ try {
10
+ if (!fs.existsSync(credsPath))
11
+ return null;
12
+ const raw = fs.readFileSync(credsPath, "utf8");
13
+ const parsed = JSON.parse(raw);
14
+ if (!parsed || typeof parsed !== "object")
15
+ return null;
16
+ const record = parsed;
17
+ const oauth = record.claudeAiOauth;
18
+ if (!oauth || typeof oauth !== "object")
19
+ return null;
20
+ const oauthRecord = oauth;
21
+ const accessToken = typeof oauthRecord.accessToken === "string" && oauthRecord.accessToken.length > 0
22
+ ? oauthRecord.accessToken
23
+ : null;
24
+ const expiresAt = typeof oauthRecord.expiresAt === "number" && Number.isFinite(oauthRecord.expiresAt)
25
+ ? oauthRecord.expiresAt
26
+ : null;
27
+ if (!accessToken || expiresAt === null)
28
+ return null;
29
+ return { accessToken, expiresAt };
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ /**
36
+ * Fetches Claude usage data from the Anthropic OAuth usage API.
37
+ *
38
+ * Returns null when credentials are unavailable, expired, or the request fails.
39
+ * Credentials are read from `~/.claude/.credentials.json` (Claude desktop app format).
40
+ */
41
+ export async function fetchClaudeRateLimits(timeoutMs = 5000) {
42
+ try {
43
+ const creds = readClaudeCredentials();
44
+ if (!creds)
45
+ return null;
46
+ // Check token expiry with 5-minute buffer
47
+ if (Date.now() + 300_000 >= creds.expiresAt)
48
+ return null;
49
+ const controller = new AbortController();
50
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
51
+ let res;
52
+ try {
53
+ res = await fetch("https://api.anthropic.com/api/oauth/usage", {
54
+ method: "GET",
55
+ headers: {
56
+ Authorization: `Bearer ${creds.accessToken}`,
57
+ "Content-Type": "application/json",
58
+ "anthropic-beta": "oauth-2025-04-20"
59
+ },
60
+ signal: controller.signal
61
+ });
62
+ }
63
+ finally {
64
+ clearTimeout(timer);
65
+ }
66
+ if (!res.ok)
67
+ return null;
68
+ const data = (await res.json());
69
+ if (!data || typeof data !== "object")
70
+ return null;
71
+ const record = data;
72
+ const parseBucket = (val) => {
73
+ if (!val || typeof val !== "object")
74
+ return null;
75
+ const b = val;
76
+ const utilization = typeof b.utilization === "number" && Number.isFinite(b.utilization) ? b.utilization : null;
77
+ const resets_at = typeof b.resets_at === "string" ? b.resets_at : null;
78
+ if (utilization === null || !resets_at)
79
+ return null;
80
+ return { utilization, resets_at };
81
+ };
82
+ const parseExtraUsage = (val) => {
83
+ if (!val || typeof val !== "object")
84
+ return null;
85
+ const e = val;
86
+ const is_enabled = typeof e.is_enabled === "boolean" ? e.is_enabled : false;
87
+ const monthly_limit = typeof e.monthly_limit === "number" && Number.isFinite(e.monthly_limit)
88
+ ? e.monthly_limit
89
+ : null;
90
+ const used_credits = typeof e.used_credits === "number" && Number.isFinite(e.used_credits) ? e.used_credits : 0;
91
+ const utilization = typeof e.utilization === "number" && Number.isFinite(e.utilization) ? e.utilization : 0;
92
+ return { is_enabled, monthly_limit, used_credits, utilization };
93
+ };
94
+ return {
95
+ five_hour: parseBucket(record.five_hour),
96
+ seven_day: parseBucket(record.seven_day),
97
+ seven_day_sonnet: parseBucket(record.seven_day_sonnet),
98
+ extra_usage: parseExtraUsage(record.extra_usage)
99
+ };
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ai-quota CLI
4
+ *
5
+ * Usage:
6
+ * ai-quota [agent] Show quota for all agents, or a specific agent
7
+ * ai-quota --json Output machine-readable JSON
8
+ * ai-quota --help Show help
9
+ * ai-quota --version Show version
10
+ *
11
+ * Agents: claude, gemini, copilot, amazon-q, codex
12
+ */
13
+ export {};
14
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}