@howaboua/opencode-usage-plugin 0.1.6 → 0.1.8

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.
Files changed (58) hide show
  1. package/README.md +22 -1
  2. package/dist/hooks/command.d.ts.map +1 -1
  3. package/dist/hooks/command.js +3 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -0
  6. package/dist/providers/anthropic/auth.d.ts +3 -0
  7. package/dist/providers/anthropic/auth.d.ts.map +1 -0
  8. package/dist/providers/anthropic/auth.js +42 -0
  9. package/dist/providers/anthropic/fetch.d.ts +4 -0
  10. package/dist/providers/anthropic/fetch.d.ts.map +1 -0
  11. package/dist/providers/anthropic/fetch.js +44 -0
  12. package/dist/providers/anthropic/index.d.ts +3 -0
  13. package/dist/providers/anthropic/index.d.ts.map +1 -0
  14. package/dist/providers/anthropic/index.js +30 -0
  15. package/dist/providers/anthropic/parse.d.ts +5 -0
  16. package/dist/providers/anthropic/parse.d.ts.map +1 -0
  17. package/dist/providers/anthropic/parse.js +112 -0
  18. package/dist/providers/anthropic/types.d.ts +68 -0
  19. package/dist/providers/anthropic/types.d.ts.map +1 -0
  20. package/dist/providers/anthropic/types.js +44 -0
  21. package/dist/providers/index.d.ts +2 -0
  22. package/dist/providers/index.d.ts.map +1 -1
  23. package/dist/providers/index.js +6 -0
  24. package/dist/providers/openrouter/fetch.d.ts +6 -0
  25. package/dist/providers/openrouter/fetch.d.ts.map +1 -0
  26. package/dist/providers/openrouter/fetch.js +39 -0
  27. package/dist/providers/openrouter/index.d.ts +8 -0
  28. package/dist/providers/openrouter/index.d.ts.map +1 -0
  29. package/dist/providers/openrouter/index.js +55 -0
  30. package/dist/providers/openrouter/types.d.ts +35 -0
  31. package/dist/providers/openrouter/types.d.ts.map +1 -0
  32. package/dist/providers/openrouter/types.js +32 -0
  33. package/dist/providers/proxy/index.d.ts.map +1 -1
  34. package/dist/providers/proxy/index.js +6 -3
  35. package/dist/state.d.ts +1 -0
  36. package/dist/state.d.ts.map +1 -1
  37. package/dist/state.js +1 -0
  38. package/dist/types.d.ts +35 -1
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/types.js +3 -0
  41. package/dist/ui/formatters/anthropic.d.ts +3 -0
  42. package/dist/ui/formatters/anthropic.d.ts.map +1 -0
  43. package/dist/ui/formatters/anthropic.js +71 -0
  44. package/dist/ui/formatters/openrouter.d.ts +6 -0
  45. package/dist/ui/formatters/openrouter.d.ts.map +1 -0
  46. package/dist/ui/formatters/openrouter.js +23 -0
  47. package/dist/ui/formatters/shared.d.ts.map +1 -1
  48. package/dist/ui/formatters/shared.js +3 -1
  49. package/dist/ui/status.d.ts.map +1 -1
  50. package/dist/ui/status.js +8 -0
  51. package/dist/usage/config.d.ts.map +1 -1
  52. package/dist/usage/config.js +5 -1
  53. package/dist/usage/fetch.d.ts.map +1 -1
  54. package/dist/usage/fetch.js +7 -3
  55. package/dist/usage/registry.d.ts +4 -0
  56. package/dist/usage/registry.d.ts.map +1 -1
  57. package/dist/usage/registry.js +8 -0
  58. package/package.json +1 -1
package/README.md CHANGED
@@ -5,8 +5,11 @@ Track AI provider rate limits and quotas in real-time.
5
5
  ## Features
6
6
 
7
7
  - **Live rate limits** – See Codex/OpenAI hourly/weekly limits at a glance
8
+ - **Anthropic subscription limits** – Track Claude OAuth windows (5h, 7d, Sonnet/Opus/cowork tiers)
8
9
  - **Proxy quota stats** – Monitor Mirrowel Proxy credentials and tier usage
9
10
  - **Copilot usage** – Track GitHub Copilot chat + completions quotas
11
+ - **Z.ai usage** – Track GLM Coding Plan 5-hour token quota and monthly tool quota
12
+ - **OpenRouter usage** – Track API credit usage and remaining balance
10
13
  - **Inline status** – Results appear directly in your chat, no context switching
11
14
  - **Zero setup** – Auto-detects providers from your existing config
12
15
 
@@ -45,11 +48,17 @@ The plugin creates a default config file on first run at:
45
48
  // Optional: Request timeout in milliseconds (default: 10000)
46
49
  "timeout": 10000,
47
50
 
51
+ // Optional: Z.ai API endpoint (default: "https://api.z.ai")
52
+ "zaiEndpoint": "https://api.z.ai",
53
+
48
54
  // Optional: Show/hide providers in /usage output
49
55
  "providers": {
50
56
  "openai": true,
57
+ "anthropic": true,
51
58
  "proxy": true,
52
- "copilot": true
59
+ "copilot": true,
60
+ "zai": true,
61
+ "openrouter": true
53
62
  },
54
63
 
55
64
  // Model group display configuration (optional)
@@ -106,8 +115,14 @@ Copilot is detected from either of these locations:
106
115
 
107
116
  ```
108
117
  /usage codex
118
+ /usage anthropic
119
+ /usage claude
109
120
  /usage proxy
110
121
  /usage copilot
122
+ /usage zai
123
+ /usage glm
124
+ /usage openrouter
125
+ /usage or
111
126
  ```
112
127
 
113
128
  ### Support the proxy
@@ -121,8 +136,11 @@ Copilot is detected from either of these locations:
121
136
  | Provider | Source |
122
137
  |----------|--------|
123
138
  | **Codex / OpenAI** | Auth tokens + `/wham/usage` endpoint |
139
+ | **Anthropic Claude** | OAuth profile + `/api/oauth/usage` windows |
124
140
  | **Mirrowel Proxy** | Local `/v1/quota-stats` endpoint |
125
141
  | **GitHub Copilot** | GitHub internal usage APIs |
142
+ | **Z.ai GLM Coding Plan** | `chat.z.ai` auth + Z.ai usage APIs |
143
+ | **OpenRouter** | API key + `openrouter.ai/api/v1/key` |
126
144
 
127
145
  ## Troubleshooting
128
146
 
@@ -136,6 +154,9 @@ Copilot is detected from either of these locations:
136
154
  - Use `providers: { ... }` in config to disable unused providers
137
155
  - For Codex: Ensure you have valid auth tokens
138
156
  - For Copilot: Check token file locations in Configuration section above
157
+ - For Z.ai: Ensure your OpenCode auth includes `chat.z.ai` credentials
158
+ - For Anthropic: Ensure Claude OAuth credentials are available (`anthropic` in auth.json)
159
+ - For OpenRouter: Ensure OpenRouter API key is available (`openrouter` or `or` in auth.json)
139
160
 
140
161
  **Config file not found**
141
162
  - The plugin auto-creates `usage-config.jsonc` on first run
@@ -1 +1 @@
1
- {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/hooks/command.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAI1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAWxC,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;CAClB,GAAG,IAAI,CAAC,KAAK,EAAE,wBAAwB,GAAG,QAAQ,CAAC,CAoDnD"}
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/hooks/command.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAI1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAWxC,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;CAClB,GAAG,IAAI,CAAC,KAAK,EAAE,wBAAwB,GAAG,QAAQ,CAAC,CAqDnD"}
@@ -11,7 +11,7 @@ export function commandHooks(options) {
11
11
  config.command ??= {};
12
12
  config.command["usage"] = {
13
13
  template: "/usage",
14
- description: "Show API usage and rate limits (codex/proxy or all)",
14
+ description: "Show API usage and rate limits (anthropic/codex/proxy/copilot/zai)",
15
15
  };
16
16
  },
17
17
  "command.execute.before": async (input) => {
@@ -36,6 +36,8 @@ export function commandHooks(options) {
36
36
  return true;
37
37
  if (s.provider === "codex")
38
38
  return options.state.availableProviders.codex;
39
+ if (s.provider === "anthropic")
40
+ return options.state.availableProviders.anthropic;
39
41
  if (s.provider === "proxy")
40
42
  return options.state.availableProviders.proxy;
41
43
  if (s.provider === "copilot")
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAMjD,eAAO,MAAM,WAAW,EAAE,MAoCzB,CAAA;AAED,eAAe,WAAW,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAMjD,eAAO,MAAM,WAAW,EAAE,MAqCzB,CAAA;AAED,eAAe,WAAW,CAAA"}
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export const UsagePlugin = async ({ client }) => {
12
12
  state.availableProviders.codex = usageConfig?.providers?.openai !== false;
13
13
  state.availableProviders.proxy = usageConfig?.providers?.proxy !== false;
14
14
  state.availableProviders.copilot = usageConfig?.providers?.copilot !== false;
15
+ state.availableProviders.anthropic = usageConfig?.providers?.anthropic !== false;
15
16
  }
16
17
  catch (err) { }
17
18
  async function sendStatusMessage(sessionID, text) {
@@ -0,0 +1,3 @@
1
+ import { type AnthropicAuthData } from "./types.js";
2
+ export declare function readAnthropicAuth(): Promise<AnthropicAuthData | null>;
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/providers/anthropic/auth.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAqC3E"}
@@ -0,0 +1,42 @@
1
+ import { existsSync } from "fs";
2
+ import { readFile } from "fs/promises";
3
+ import { homedir } from "os";
4
+ import { join } from "path";
5
+ import { getAuthFilePath } from "../../utils/paths.js";
6
+ export async function readAnthropicAuth() {
7
+ // 1. Check environment variable
8
+ if (process.env.CLAUDE_CODE_OAUTH_TOKEN) {
9
+ return { access: process.env.CLAUDE_CODE_OAUTH_TOKEN };
10
+ }
11
+ // 2. Check opencode auth.json
12
+ try {
13
+ const authPath = getAuthFilePath();
14
+ if (existsSync(authPath)) {
15
+ const content = await readFile(authPath, "utf-8");
16
+ const authData = JSON.parse(content);
17
+ const anthropicAuth = authData?.["anthropic"];
18
+ if (anthropicAuth?.access) {
19
+ return anthropicAuth;
20
+ }
21
+ }
22
+ }
23
+ catch { }
24
+ // 3. Check ~/.claude/credentials.json (Claude Code)
25
+ try {
26
+ const claudePath = join(homedir(), ".claude", "credentials.json");
27
+ if (existsSync(claudePath)) {
28
+ const content = await readFile(claudePath, "utf-8");
29
+ const creds = JSON.parse(content);
30
+ const oauth = creds?.claudeAiOauth;
31
+ if (oauth?.accessToken) {
32
+ return {
33
+ access: oauth.accessToken,
34
+ refresh: oauth.refreshToken,
35
+ expires: oauth.expiresAt
36
+ };
37
+ }
38
+ }
39
+ }
40
+ catch { }
41
+ return null;
42
+ }
@@ -0,0 +1,4 @@
1
+ import { type AnthropicProfileResponse, type AnthropicUsageResponse } from "./types.js";
2
+ export declare function fetchAnthropicUsage(token: string): Promise<AnthropicUsageResponse | null>;
3
+ export declare function fetchAnthropicProfile(token: string): Promise<AnthropicProfileResponse | null>;
4
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/providers/anthropic/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC5B,MAAM,YAAY,CAAA;AA4BnB,wBAAsB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAM/F;AAED,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAMnG"}
@@ -0,0 +1,44 @@
1
+ import { anthropicProfileResponseSchema, anthropicUsageResponseSchema, oauthUsageHeaders, } from "./types.js";
2
+ const USAGE_ENDPOINT = "https://api.anthropic.com/api/oauth/usage";
3
+ const PROFILE_ENDPOINT = "https://api.anthropic.com/api/oauth/profile";
4
+ const REQUEST_TIMEOUT_MS = 5000;
5
+ async function fetchOAuthJson(url, token) {
6
+ const controller = new AbortController();
7
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
8
+ try {
9
+ const response = await fetch(url, {
10
+ headers: {
11
+ Authorization: `Bearer ${token}`,
12
+ ...oauthUsageHeaders,
13
+ },
14
+ signal: controller.signal,
15
+ });
16
+ if (!response.ok)
17
+ return null;
18
+ return await response.json().catch(() => null);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ finally {
24
+ clearTimeout(timeout);
25
+ }
26
+ }
27
+ export async function fetchAnthropicUsage(token) {
28
+ const data = await fetchOAuthJson(USAGE_ENDPOINT, token);
29
+ if (!data)
30
+ return null;
31
+ const parsed = anthropicUsageResponseSchema.safeParse(data);
32
+ if (!parsed.success)
33
+ return null;
34
+ return parsed.data;
35
+ }
36
+ export async function fetchAnthropicProfile(token) {
37
+ const data = await fetchOAuthJson(PROFILE_ENDPOINT, token);
38
+ if (!data)
39
+ return null;
40
+ const parsed = anthropicProfileResponseSchema.safeParse(data);
41
+ if (!parsed.success)
42
+ return null;
43
+ return parsed.data;
44
+ }
@@ -0,0 +1,3 @@
1
+ import type { UsageProvider } from "../base.js";
2
+ export declare const AnthropicProvider: UsageProvider<void>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/anthropic/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAM/C,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,IAAI,CA4BjD,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { readAnthropicAuth } from "./auth.js";
2
+ import { fetchAnthropicProfile, fetchAnthropicUsage } from "./fetch.js";
3
+ import { buildAnthropicQuota, inferAnthropicPlanType } from "./parse.js";
4
+ export const AnthropicProvider = {
5
+ id: "anthropic",
6
+ displayName: "Anthropic Claude",
7
+ usageEndpoint: "https://api.anthropic.com/api/oauth/usage",
8
+ async fetchUsage() {
9
+ const auth = await readAnthropicAuth();
10
+ if (!auth?.access)
11
+ return null;
12
+ const [usage, profile] = await Promise.all([
13
+ fetchAnthropicUsage(auth.access),
14
+ fetchAnthropicProfile(auth.access),
15
+ ]);
16
+ if (!usage)
17
+ return null;
18
+ return {
19
+ timestamp: Date.now(),
20
+ updatedAt: Date.now(),
21
+ provider: "anthropic",
22
+ planType: inferAnthropicPlanType(profile),
23
+ primary: null,
24
+ secondary: null,
25
+ codeReview: null,
26
+ credits: null,
27
+ anthropicQuota: buildAnthropicQuota(usage, profile),
28
+ };
29
+ },
30
+ };
@@ -0,0 +1,5 @@
1
+ import type { AnthropicQuota, PlanType } from "../../types.js";
2
+ import type { AnthropicProfileResponse, AnthropicUsageResponse } from "./types.js";
3
+ export declare function inferAnthropicPlanType(profile: AnthropicProfileResponse | null): PlanType | null;
4
+ export declare function buildAnthropicQuota(usage: AnthropicUsageResponse, profile: AnthropicProfileResponse | null): AnthropicQuota;
5
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../../src/providers/anthropic/parse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,KAAK,EAAE,wBAAwB,EAAE,sBAAsB,EAAwB,MAAM,YAAY,CAAA;AAqDxG,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,wBAAwB,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,CAehG;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,sBAAsB,EAAE,OAAO,EAAE,wBAAwB,GAAG,IAAI,GAAG,cAAc,CAyC3H"}
@@ -0,0 +1,112 @@
1
+ const KNOWN_LIMIT_ORDER = [
2
+ "five_hour",
3
+ "seven_day",
4
+ "seven_day_oauth_apps",
5
+ "seven_day_sonnet",
6
+ "seven_day_opus",
7
+ "seven_day_cowork",
8
+ "iguana_necktie",
9
+ ];
10
+ const LIMIT_LABELS = {
11
+ five_hour: "5-Hour",
12
+ seven_day: "7-Day (All)",
13
+ seven_day_oauth_apps: "7-Day (OAuth Apps)",
14
+ seven_day_sonnet: "7-Day (Sonnet)",
15
+ seven_day_opus: "7-Day (Opus)",
16
+ seven_day_cowork: "7-Day (Co-work)",
17
+ iguana_necktie: "Iguana Necktie",
18
+ };
19
+ function humanizeKey(key) {
20
+ return key
21
+ .split("_")
22
+ .filter(Boolean)
23
+ .map((token) => token[0]?.toUpperCase() + token.slice(1))
24
+ .join(" ");
25
+ }
26
+ function toPercent(value) {
27
+ if (typeof value !== "number" || Number.isNaN(value))
28
+ return 0;
29
+ return Math.max(0, Math.min(100, value));
30
+ }
31
+ function asString(value) {
32
+ if (value === null || value === undefined)
33
+ return null;
34
+ if (typeof value === "string")
35
+ return value;
36
+ if (typeof value === "number")
37
+ return String(value);
38
+ return null;
39
+ }
40
+ function isUsageWindow(value) {
41
+ if (!value || typeof value !== "object")
42
+ return false;
43
+ return "utilization" in value || "resets_at" in value;
44
+ }
45
+ function getOrderedLimitKeys(raw) {
46
+ const known = KNOWN_LIMIT_ORDER.filter((k) => k in raw);
47
+ const extras = Object.keys(raw).filter((k) => !KNOWN_LIMIT_ORDER.includes(k));
48
+ return [...known, ...extras];
49
+ }
50
+ export function inferAnthropicPlanType(profile) {
51
+ const account = profile?.account;
52
+ const organization = profile?.organization;
53
+ const orgType = (organization?.organization_type || "").toLowerCase();
54
+ const tier = (organization?.rate_limit_tier || "").toLowerCase();
55
+ if (tier.includes("max_20"))
56
+ return "max_20x";
57
+ if (tier.includes("max_5"))
58
+ return "max_5x";
59
+ if (orgType.includes("max"))
60
+ return "max";
61
+ if (orgType.includes("enterprise"))
62
+ return "enterprise";
63
+ if (orgType.includes("team"))
64
+ return "team";
65
+ if (orgType.includes("pro"))
66
+ return "pro";
67
+ if (account?.has_claude_max)
68
+ return "max";
69
+ if (account?.has_claude_pro)
70
+ return "pro";
71
+ return null;
72
+ }
73
+ export function buildAnthropicQuota(usage, profile) {
74
+ const raw = usage;
75
+ const limits = [];
76
+ for (const key of getOrderedLimitKeys(raw)) {
77
+ if (key === "extra_usage")
78
+ continue;
79
+ const candidate = raw[key];
80
+ if (!isUsageWindow(candidate))
81
+ continue;
82
+ const utilization = typeof candidate.utilization === "number" ? toPercent(candidate.utilization) : null;
83
+ const resetsAt = asString(candidate.resets_at);
84
+ if (utilization === null && resetsAt === null)
85
+ continue;
86
+ limits.push({
87
+ key,
88
+ label: LIMIT_LABELS[key] ?? humanizeKey(key),
89
+ utilization: utilization ?? 0,
90
+ resetsAt,
91
+ });
92
+ }
93
+ const extraRaw = usage.extra_usage;
94
+ return {
95
+ limits,
96
+ extraUsage: extraRaw
97
+ ? {
98
+ isEnabled: extraRaw.is_enabled === true,
99
+ monthlyLimit: asString(extraRaw.monthly_limit),
100
+ usedCredits: asString(extraRaw.used_credits),
101
+ utilization: typeof extraRaw.utilization === "number" ? toPercent(extraRaw.utilization) : null,
102
+ }
103
+ : null,
104
+ subscription: {
105
+ organizationType: profile?.organization?.organization_type || null,
106
+ rateLimitTier: profile?.organization?.rate_limit_tier || null,
107
+ subscriptionStatus: profile?.organization?.subscription_status || null,
108
+ hasClaudeMax: profile?.account?.has_claude_max === true,
109
+ hasClaudePro: profile?.account?.has_claude_pro === true,
110
+ },
111
+ };
112
+ }
@@ -0,0 +1,68 @@
1
+ import z from "zod";
2
+ export interface AnthropicAuthData {
3
+ access: string;
4
+ refresh?: string;
5
+ expires?: number;
6
+ }
7
+ export declare const oauthUsageHeaders: {
8
+ readonly "anthropic-beta": "oauth-2025-04-20";
9
+ readonly "anthropic-dangerous-direct-browser-access": "true";
10
+ readonly "x-app": "cli";
11
+ readonly "user-agent": "claude-cli/2.1.2 (external, cli)";
12
+ };
13
+ declare const usageWindowSchema: z.ZodObject<{
14
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
15
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
16
+ }, z.core.$loose>;
17
+ export declare const anthropicUsageResponseSchema: z.ZodObject<{
18
+ five_hour: z.ZodNullable<z.ZodOptional<z.ZodObject<{
19
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
20
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
21
+ }, z.core.$loose>>>;
22
+ seven_day: z.ZodNullable<z.ZodOptional<z.ZodObject<{
23
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
24
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
25
+ }, z.core.$loose>>>;
26
+ seven_day_oauth_apps: z.ZodNullable<z.ZodOptional<z.ZodObject<{
27
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
28
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
29
+ }, z.core.$loose>>>;
30
+ seven_day_opus: z.ZodNullable<z.ZodOptional<z.ZodObject<{
31
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
32
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
+ }, z.core.$loose>>>;
34
+ seven_day_sonnet: z.ZodNullable<z.ZodOptional<z.ZodObject<{
35
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
36
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
37
+ }, z.core.$loose>>>;
38
+ seven_day_cowork: z.ZodNullable<z.ZodOptional<z.ZodObject<{
39
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
40
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
41
+ }, z.core.$loose>>>;
42
+ iguana_necktie: z.ZodNullable<z.ZodOptional<z.ZodObject<{
43
+ utilization: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
44
+ resets_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
45
+ }, z.core.$loose>>>;
46
+ extra_usage: z.ZodNullable<z.ZodOptional<z.ZodObject<{
47
+ is_enabled: z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
48
+ monthly_limit: z.ZodNullable<z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
49
+ used_credits: z.ZodNullable<z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
50
+ utilization: z.ZodNullable<z.ZodOptional<z.ZodNumber>>;
51
+ }, z.core.$loose>>>;
52
+ }, z.core.$loose>;
53
+ export declare const anthropicProfileResponseSchema: z.ZodObject<{
54
+ account: z.ZodOptional<z.ZodObject<{
55
+ has_claude_max: z.ZodOptional<z.ZodBoolean>;
56
+ has_claude_pro: z.ZodOptional<z.ZodBoolean>;
57
+ }, z.core.$loose>>;
58
+ organization: z.ZodOptional<z.ZodObject<{
59
+ organization_type: z.ZodNullable<z.ZodOptional<z.ZodString>>;
60
+ rate_limit_tier: z.ZodNullable<z.ZodOptional<z.ZodString>>;
61
+ subscription_status: z.ZodNullable<z.ZodOptional<z.ZodString>>;
62
+ }, z.core.$loose>>;
63
+ }, z.core.$loose>;
64
+ export type AnthropicUsageResponse = z.infer<typeof anthropicUsageResponseSchema>;
65
+ export type AnthropicProfileResponse = z.infer<typeof anthropicProfileResponseSchema>;
66
+ export type AnthropicUsageWindow = z.infer<typeof usageWindowSchema>;
67
+ export {};
68
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/anthropic/types.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,KAAK,CAAA;AAEnB,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAA;AAEV,QAAA,MAAM,iBAAiB;;;iBAGP,CAAA;AAShB,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBASzB,CAAA;AAEhB,eAAO,MAAM,8BAA8B;;;;;;;;;;iBAgB3B,CAAA;AAEhB,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAA;AACjF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAA;AACrF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA"}
@@ -0,0 +1,44 @@
1
+ import z from "zod";
2
+ export const oauthUsageHeaders = {
3
+ "anthropic-beta": "oauth-2025-04-20",
4
+ "anthropic-dangerous-direct-browser-access": "true",
5
+ "x-app": "cli",
6
+ "user-agent": "claude-cli/2.1.2 (external, cli)",
7
+ };
8
+ const usageWindowSchema = z.object({
9
+ utilization: z.number().nullable().optional(),
10
+ resets_at: z.string().nullable().optional(),
11
+ }).passthrough();
12
+ const extraUsageSchema = z.object({
13
+ is_enabled: z.boolean().optional().nullable(),
14
+ monthly_limit: z.union([z.number(), z.string()]).optional().nullable(),
15
+ used_credits: z.union([z.number(), z.string()]).optional().nullable(),
16
+ utilization: z.number().optional().nullable(),
17
+ }).passthrough();
18
+ export const anthropicUsageResponseSchema = z.object({
19
+ five_hour: usageWindowSchema.optional().nullable(),
20
+ seven_day: usageWindowSchema.optional().nullable(),
21
+ seven_day_oauth_apps: usageWindowSchema.optional().nullable(),
22
+ seven_day_opus: usageWindowSchema.optional().nullable(),
23
+ seven_day_sonnet: usageWindowSchema.optional().nullable(),
24
+ seven_day_cowork: usageWindowSchema.optional().nullable(),
25
+ iguana_necktie: usageWindowSchema.optional().nullable(),
26
+ extra_usage: extraUsageSchema.optional().nullable(),
27
+ }).passthrough();
28
+ export const anthropicProfileResponseSchema = z.object({
29
+ account: z
30
+ .object({
31
+ has_claude_max: z.boolean().optional(),
32
+ has_claude_pro: z.boolean().optional(),
33
+ })
34
+ .passthrough()
35
+ .optional(),
36
+ organization: z
37
+ .object({
38
+ organization_type: z.string().optional().nullable(),
39
+ rate_limit_tier: z.string().optional().nullable(),
40
+ subscription_status: z.string().optional().nullable(),
41
+ })
42
+ .passthrough()
43
+ .optional(),
44
+ }).passthrough();
@@ -4,5 +4,7 @@ export { CodexProvider } from "./codex";
4
4
  export { ProxyProvider } from "./proxy";
5
5
  export { CopilotProvider } from "./copilot";
6
6
  export { ZaiProvider } from "./zai";
7
+ export { OpenRouterProvider } from "./openrouter";
8
+ export { AnthropicProvider } from "./anthropic";
7
9
  export type { UsageProvider } from "./base";
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAM3C,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAK5D,CAAA;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACnC,YAAY,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AAQ3C,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAO5D,CAAA;AAED,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA"}
@@ -2,13 +2,19 @@ import { CodexProvider } from "./codex";
2
2
  import { ProxyProvider } from "./proxy";
3
3
  import { CopilotProvider } from "./copilot";
4
4
  import { ZaiProvider } from "./zai";
5
+ import { OpenRouterProvider } from "./openrouter";
6
+ import { AnthropicProvider } from "./anthropic";
5
7
  export const providers = {
6
8
  [CodexProvider.id]: CodexProvider,
7
9
  [ProxyProvider.id]: ProxyProvider,
8
10
  [CopilotProvider.id]: CopilotProvider,
9
11
  [ZaiProvider.id]: ZaiProvider,
12
+ [OpenRouterProvider.id]: OpenRouterProvider,
13
+ [AnthropicProvider.id]: AnthropicProvider,
10
14
  };
11
15
  export { CodexProvider } from "./codex";
12
16
  export { ProxyProvider } from "./proxy";
13
17
  export { CopilotProvider } from "./copilot";
14
18
  export { ZaiProvider } from "./zai";
19
+ export { OpenRouterProvider } from "./openrouter";
20
+ export { AnthropicProvider } from "./anthropic";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Fetch logic for OpenRouter usage monitoring.
3
+ */
4
+ import { type OpenRouterAuth, type OpenRouterAuthResponse } from "./types";
5
+ export declare function fetchOpenRouterUsage(auth: OpenRouterAuth): Promise<OpenRouterAuthResponse>;
6
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../../src/providers/openrouter/fetch.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC5B,MAAM,SAAS,CAAA;AAEhB,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAsChG"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Fetch logic for OpenRouter usage monitoring.
3
+ */
4
+ import { openRouterAuthResponseSchema, } from "./types";
5
+ export async function fetchOpenRouterUsage(auth) {
6
+ const urls = [
7
+ "https://openrouter.ai/api/v1/key",
8
+ "https://openrouter.ai/api/v1/auth/key",
9
+ ];
10
+ let lastError = "Unknown OpenRouter API error";
11
+ for (const url of urls) {
12
+ const response = await fetch(url, {
13
+ headers: {
14
+ "Authorization": `Bearer ${auth.key}`,
15
+ "Content-Type": "application/json",
16
+ },
17
+ }).catch(() => null);
18
+ if (!response) {
19
+ lastError = `OpenRouter API failed: network error for ${url}`;
20
+ continue;
21
+ }
22
+ if (!response.ok) {
23
+ lastError = `OpenRouter API failed: ${response.status} ${await response.text()}`;
24
+ continue;
25
+ }
26
+ const data = await response.json().catch(() => null);
27
+ if (!data) {
28
+ lastError = "Invalid OpenRouter response: empty JSON body";
29
+ continue;
30
+ }
31
+ const parsed = openRouterAuthResponseSchema.safeParse(data);
32
+ if (!parsed.success) {
33
+ lastError = "Invalid OpenRouter response structure";
34
+ continue;
35
+ }
36
+ return parsed.data;
37
+ }
38
+ throw new Error(lastError);
39
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * providers/openrouter/index.ts
3
+ * Main entry point for the OpenRouter usage provider.
4
+ */
5
+ import type { UsageProvider } from "../base";
6
+ import type { OpenRouterAuth } from "./types";
7
+ export declare const OpenRouterProvider: UsageProvider<OpenRouterAuth>;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/openrouter/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAG5C,OAAO,KAAK,EAAE,cAAc,EAA0B,MAAM,SAAS,CAAA;AAerE,eAAO,MAAM,kBAAkB,EAAE,aAAa,CAAC,cAAc,CAsC5D,CAAA"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * providers/openrouter/index.ts
3
+ * Main entry point for the OpenRouter usage provider.
4
+ */
5
+ import { fetchOpenRouterUsage } from "./fetch";
6
+ function toRateLimitWindow(data) {
7
+ const limit = data.data.limit;
8
+ const usage = data.data.usage;
9
+ if (limit === null || limit <= 0)
10
+ return null;
11
+ return {
12
+ usedPercent: (usage / limit) * 100,
13
+ windowMinutes: null,
14
+ resetsAt: data.data.limit_reset ? new Date(data.data.limit_reset).getTime() : null,
15
+ };
16
+ }
17
+ export const OpenRouterProvider = {
18
+ id: "openrouter",
19
+ displayName: "OpenRouter",
20
+ async fetchUsage(auth) {
21
+ try {
22
+ if (!auth?.key)
23
+ return null;
24
+ const data = await fetchOpenRouterUsage(auth);
25
+ const now = Date.now();
26
+ return {
27
+ timestamp: now,
28
+ provider: "openrouter",
29
+ planType: data.data.is_free_tier ? "free" : "plus",
30
+ primary: toRateLimitWindow(data),
31
+ secondary: null,
32
+ codeReview: null,
33
+ credits: {
34
+ hasCredits: true,
35
+ unlimited: data.data.limit === null,
36
+ balance: data.data.limit_remaining === null ? "Unlimited" : `$${data.data.limit_remaining.toFixed(2)}`,
37
+ },
38
+ updatedAt: now,
39
+ openrouterQuota: {
40
+ limit: data.data.limit,
41
+ usage: data.data.usage,
42
+ limitRemaining: data.data.limit_remaining,
43
+ usageDaily: data.data.usage_daily,
44
+ usageWeekly: data.data.usage_weekly,
45
+ usageMonthly: data.data.usage_monthly,
46
+ isFreeTier: data.data.is_free_tier,
47
+ },
48
+ };
49
+ }
50
+ catch (error) {
51
+ void error;
52
+ return null;
53
+ }
54
+ },
55
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Type definitions for the OpenRouter provider.
3
+ */
4
+ import z from "zod";
5
+ export interface OpenRouterAuth {
6
+ key: string;
7
+ }
8
+ export declare const openRouterAuthResponseSchema: z.ZodObject<{
9
+ data: z.ZodObject<{
10
+ label: z.ZodOptional<z.ZodString>;
11
+ is_management_key: z.ZodOptional<z.ZodBoolean>;
12
+ is_provisioning_key: z.ZodOptional<z.ZodBoolean>;
13
+ limit: z.ZodNullable<z.ZodNumber>;
14
+ limit_reset: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15
+ limit_remaining: z.ZodNullable<z.ZodNumber>;
16
+ include_byok_in_limit: z.ZodOptional<z.ZodBoolean>;
17
+ usage: z.ZodNumber;
18
+ usage_daily: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
19
+ usage_weekly: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
20
+ usage_monthly: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
21
+ byok_usage: z.ZodOptional<z.ZodNumber>;
22
+ byok_usage_daily: z.ZodOptional<z.ZodNumber>;
23
+ byok_usage_weekly: z.ZodOptional<z.ZodNumber>;
24
+ byok_usage_monthly: z.ZodOptional<z.ZodNumber>;
25
+ is_free_tier: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
26
+ expires_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
27
+ rate_limit: z.ZodOptional<z.ZodObject<{
28
+ requests: z.ZodOptional<z.ZodNumber>;
29
+ interval: z.ZodOptional<z.ZodString>;
30
+ note: z.ZodOptional<z.ZodString>;
31
+ }, z.core.$strip>>;
32
+ }, z.core.$strip>;
33
+ }, z.core.$strip>;
34
+ export type OpenRouterAuthResponse = z.infer<typeof openRouterAuthResponseSchema>;
35
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/openrouter/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,CAAC,MAAM,KAAK,CAAA;AAEnB,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAA;CACZ;AAED,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BvC,CAAA;AAEF,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAA"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Type definitions for the OpenRouter provider.
3
+ */
4
+ import z from "zod";
5
+ export const openRouterAuthResponseSchema = z.object({
6
+ data: z.object({
7
+ label: z.string().optional(),
8
+ is_management_key: z.boolean().optional(),
9
+ is_provisioning_key: z.boolean().optional(),
10
+ limit: z.number().nullable(),
11
+ limit_reset: z.string().nullable().optional(),
12
+ limit_remaining: z.number().nullable(),
13
+ include_byok_in_limit: z.boolean().optional(),
14
+ usage: z.number(),
15
+ usage_daily: z.number().optional().default(0),
16
+ usage_weekly: z.number().optional().default(0),
17
+ usage_monthly: z.number().optional().default(0),
18
+ byok_usage: z.number().optional(),
19
+ byok_usage_daily: z.number().optional(),
20
+ byok_usage_weekly: z.number().optional(),
21
+ byok_usage_monthly: z.number().optional(),
22
+ is_free_tier: z.boolean().optional().default(false),
23
+ expires_at: z.string().nullable().optional(),
24
+ rate_limit: z
25
+ .object({
26
+ requests: z.number().optional(),
27
+ interval: z.string().optional(),
28
+ note: z.string().optional(),
29
+ })
30
+ .optional(),
31
+ }),
32
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAgF,WAAW,EAAE,MAAM,aAAa,CAAA;AAK5H,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AA4B5C;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GAAG,IAAI,GACzB,MAAM,GAAG,IAAI,CAoBf;AAyND,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,IAAI,CAwB7C,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAgF,WAAW,EAAE,MAAM,aAAa,CAAA;AAK5H,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AA4B5C;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GAAG,IAAI,GACzB,MAAM,GAAG,IAAI,CAoBf;AA4ND,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,IAAI,CAwB7C,CAAA"}
@@ -192,15 +192,18 @@ function aggregateByProvider(provider, config) {
192
192
  }
193
193
  }
194
194
  if (aggregated.length > 0) {
195
+ const getTierGroupKey = (tier, groupName) => `${tier}::${groupName}`;
195
196
  const resetLookup = new Map();
196
- for (const tierInfo of Object.values(tiers)) {
197
+ for (const [tierName, tierInfo] of Object.entries(tiers)) {
197
198
  for (const group of tierInfo.values()) {
198
- resetLookup.set(group.name, pickPreferredResetTime(resetLookup.get(group.name), group.resetTime));
199
+ const key = getTierGroupKey(tierName, group.name);
200
+ resetLookup.set(key, pickPreferredResetTime(resetLookup.get(key), group.resetTime));
199
201
  }
200
202
  }
201
203
  for (const tier of aggregated) {
202
204
  for (const group of tier.quotaGroups) {
203
- group.resetTime = pickPreferredResetTime(group.resetTime, resetLookup.get(group.name));
205
+ const key = getTierGroupKey(tier.tier, group.name);
206
+ group.resetTime = pickPreferredResetTime(group.resetTime, resetLookup.get(key));
204
207
  }
205
208
  }
206
209
  return aggregated;
package/dist/state.d.ts CHANGED
@@ -13,6 +13,7 @@ export type UsageState = {
13
13
  codex: boolean;
14
14
  proxy: boolean;
15
15
  copilot: boolean;
16
+ anthropic: boolean;
16
17
  };
17
18
  };
18
19
  export declare function createUsageState(): UsageState;
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,kBAAkB,EAAE;QAClB,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,OAAO,CAAA;QACd,OAAO,EAAE,OAAO,CAAA;KACjB,CAAA;CACF,CAAA;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAS7C"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C,kBAAkB,EAAE;QAClB,KAAK,EAAE,OAAO,CAAA;QACd,KAAK,EAAE,OAAO,CAAA;QACd,OAAO,EAAE,OAAO,CAAA;QAChB,SAAS,EAAE,OAAO,CAAA;KACnB,CAAA;CACF,CAAA;AAED,wBAAgB,gBAAgB,IAAI,UAAU,CAU7C"}
package/dist/state.js CHANGED
@@ -9,6 +9,7 @@ export function createUsageState() {
9
9
  codex: false,
10
10
  proxy: false,
11
11
  copilot: false,
12
+ anthropic: false,
12
13
  },
13
14
  };
14
15
  }
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Core type definitions for the Usage Tracking Plugin.
3
3
  */
4
- export declare const PlanTypes: readonly ["guest", "free", "go", "plus", "pro", "free_workspace", "team", "business", "education", "quorum", "k12", "enterprise", "edu"];
4
+ export declare const PlanTypes: readonly ["guest", "free", "go", "plus", "pro", "max", "max_5x", "max_20x", "free_workspace", "team", "business", "education", "quorum", "k12", "enterprise", "edu"];
5
5
  export type PlanType = (typeof PlanTypes)[number];
6
6
  export interface RateLimitWindow {
7
7
  usedPercent: number;
@@ -52,6 +52,8 @@ export interface UsageConfig {
52
52
  proxy?: boolean;
53
53
  copilot?: boolean;
54
54
  zai?: boolean;
55
+ openrouter?: boolean;
56
+ anthropic?: boolean;
55
57
  };
56
58
  modelGroups?: {
57
59
  showAll?: boolean;
@@ -81,6 +83,36 @@ export interface ZaiQuota {
81
83
  totalZreadMcpCount: number;
82
84
  };
83
85
  }
86
+ export interface AnthropicQuota {
87
+ limits: Array<{
88
+ key: string;
89
+ label: string;
90
+ utilization: number;
91
+ resetsAt: string | null;
92
+ }>;
93
+ extraUsage: {
94
+ isEnabled: boolean;
95
+ monthlyLimit: string | null;
96
+ usedCredits: string | null;
97
+ utilization: number | null;
98
+ } | null;
99
+ subscription: {
100
+ organizationType: string | null;
101
+ rateLimitTier: string | null;
102
+ subscriptionStatus: string | null;
103
+ hasClaudeMax: boolean;
104
+ hasClaudePro: boolean;
105
+ } | null;
106
+ }
107
+ export interface OpenRouterQuota {
108
+ limit: number | null;
109
+ usage: number;
110
+ limitRemaining: number | null;
111
+ usageDaily: number;
112
+ usageWeekly: number;
113
+ usageMonthly: number;
114
+ isFreeTier: boolean;
115
+ }
84
116
  export interface UsageSnapshot {
85
117
  timestamp: number;
86
118
  provider: string;
@@ -92,6 +124,8 @@ export interface UsageSnapshot {
92
124
  proxyQuota?: ProxyQuota;
93
125
  copilotQuota?: CopilotQuota;
94
126
  zaiQuota?: ZaiQuota;
127
+ openrouterQuota?: OpenRouterQuota;
128
+ anthropicQuota?: AnthropicQuota;
95
129
  updatedAt: number;
96
130
  isMissing?: boolean;
97
131
  missingReason?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -7,6 +7,9 @@ export const PlanTypes = [
7
7
  "go",
8
8
  "plus",
9
9
  "pro",
10
+ "max",
11
+ "max_5x",
12
+ "max_20x",
10
13
  "free_workspace",
11
14
  "team",
12
15
  "business",
@@ -0,0 +1,3 @@
1
+ import type { UsageSnapshot } from "../../types";
2
+ export declare function formatAnthropicSnapshot(snapshot: UsageSnapshot): string[];
3
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/ui/formatters/anthropic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAwDhD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CA0BzE"}
@@ -0,0 +1,71 @@
1
+ import { formatBar, formatMissingSnapshot, formatResetSuffixISO } from "./shared";
2
+ function toTitleCase(value) {
3
+ return value.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
4
+ }
5
+ function formatTierLabel(tier) {
6
+ if (!tier)
7
+ return null;
8
+ const known = {
9
+ default_claude_ai: "Claude Pro",
10
+ default_claude_max_5x: "Claude Max 5x",
11
+ default_claude_max_20x: "Claude Max 20x",
12
+ };
13
+ if (known[tier])
14
+ return known[tier];
15
+ if (tier.startsWith("default_claude_")) {
16
+ return toTitleCase(tier.replace(/^default_claude_/, "Claude "));
17
+ }
18
+ return toTitleCase(tier);
19
+ }
20
+ function formatExtraUsage(extra) {
21
+ if (!extra?.isEnabled)
22
+ return [];
23
+ const lines = [
24
+ ` Extra Usage: ${extra.utilization !== null ? `${extra.utilization.toFixed(0)}% used` : "enabled"}`,
25
+ ];
26
+ if (extra.usedCredits || extra.monthlyLimit) {
27
+ lines.push(` Credits: ${extra.usedCredits ?? "-"} / ${extra.monthlyLimit ?? "-"}`);
28
+ }
29
+ return lines;
30
+ }
31
+ function shouldShowTier(planType, tierLabel) {
32
+ if (!tierLabel)
33
+ return false;
34
+ if (!planType)
35
+ return true;
36
+ const plan = planType.toLowerCase();
37
+ const tier = tierLabel.toLowerCase();
38
+ if (plan === "pro" && tier.includes("pro"))
39
+ return false;
40
+ if (plan === "max" && tier.includes("max"))
41
+ return false;
42
+ if (plan === "max_5x" && tier.includes("max") && tier.includes("5x"))
43
+ return false;
44
+ if (plan === "max_20x" && tier.includes("max") && tier.includes("20x"))
45
+ return false;
46
+ if (plan === "team" && tier.includes("team"))
47
+ return false;
48
+ if (plan === "enterprise" && tier.includes("enterprise"))
49
+ return false;
50
+ return true;
51
+ }
52
+ export function formatAnthropicSnapshot(snapshot) {
53
+ const quota = snapshot.anthropicQuota;
54
+ if (!quota)
55
+ return formatMissingSnapshot(snapshot);
56
+ const plan = snapshot.planType ? ` (${toTitleCase(snapshot.planType)})` : "";
57
+ const lines = [`→ [ANTHROPIC]${plan}`];
58
+ const tierLabel = formatTierLabel(quota.subscription?.rateLimitTier);
59
+ if (shouldShowTier(snapshot.planType, tierLabel)) {
60
+ lines.push(` Tier: ${tierLabel}`);
61
+ }
62
+ for (const limit of quota.limits) {
63
+ const leftPercent = Math.max(0, Math.min(100, 100 - limit.utilization));
64
+ lines.push(` ${`${limit.label}:`.padEnd(14)} ${formatBar(leftPercent)} ${leftPercent.toFixed(0)}% left${limit.resetsAt ? formatResetSuffixISO(limit.resetsAt) : ""}`);
65
+ }
66
+ lines.push(...formatExtraUsage(quota.extraUsage));
67
+ if (quota.limits.length === 0 && !quota.extraUsage?.isEnabled) {
68
+ return formatMissingSnapshot(snapshot);
69
+ }
70
+ return lines;
71
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Formats OpenRouter usage snapshots.
3
+ */
4
+ import type { UsageSnapshot } from "../../types";
5
+ export declare function formatOpenRouterSnapshot(snapshot: UsageSnapshot): string[];
6
+ //# sourceMappingURL=openrouter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openrouter.d.ts","sourceRoot":"","sources":["../../../src/ui/formatters/openrouter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGhD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAoB1E"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Formats OpenRouter usage snapshots.
3
+ */
4
+ import { formatBar, formatMissingSnapshot, formatResetSuffix } from "./shared";
5
+ export function formatOpenRouterSnapshot(snapshot) {
6
+ const or = snapshot.openrouterQuota;
7
+ if (!or)
8
+ return formatMissingSnapshot(snapshot);
9
+ const lines = ["→ [OPENROUTER]"];
10
+ if (or.limit === null) {
11
+ lines.push(` ${"Credit:".padEnd(13)} Unlimited`);
12
+ lines.push(` ${"Used:".padEnd(13)} $${or.usage.toFixed(2)}`);
13
+ }
14
+ else {
15
+ const remainingPct = snapshot.primary ? 100 - snapshot.primary.usedPercent : 100;
16
+ const resetSuffix = snapshot.primary ? formatResetSuffix(snapshot.primary.resetsAt) : "";
17
+ lines.push(` ${"Credit:".padEnd(13)} ${formatBar(remainingPct)} ${remainingPct.toFixed(0)}% left`);
18
+ const remaining = or.limitRemaining === null ? "Unlimited" : `$${or.limitRemaining.toFixed(2)}`;
19
+ lines.push(` ${"Used:".padEnd(13)} $${or.usage.toFixed(2)} / $${or.limit.toFixed(2)}`);
20
+ lines.push(` ${"Remaining:".padEnd(13)} ${remaining}${resetSuffix}`);
21
+ }
22
+ return lines;
23
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/ui/formatters/shared.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK7C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAIhE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMxD;AAwBD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAgBvE"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/ui/formatters/shared.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK7C;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAIhE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMxD;AAwBD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,EAAE,CAkBvE"}
@@ -50,7 +50,9 @@ export function formatMissingSnapshot(snapshot) {
50
50
  const instructions = {
51
51
  codex: "if you dont have codex oauth, please set your usage-config.jsonc to openai: false",
52
52
  proxy: "check your usage-config.jsonc. Default: endpoint http://localhost:8000, apiKey VerysecretKey. If you changed these during proxy setup, update your config to match. Or set proxy: false to disable.",
53
- copilot: "if you are not running GitHub Copilot, please set your usage-config.jsonc to copilot: false"
53
+ copilot: "if you are not running GitHub Copilot, please set your usage-config.jsonc to copilot: false",
54
+ anthropic: "if you are not using Claude Code with a subscription, please set your usage-config.jsonc to anthropic: false",
55
+ openrouter: "if you are not using OpenRouter API keys, please set your usage-config.jsonc to openrouter: false",
54
56
  };
55
57
  const lines = [`→ [${provider.toUpperCase()}] - ${instructions[provider] || ""}`];
56
58
  if (snapshot.missingReason)
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAM1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAExC,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhB;AAkCD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAQ1C,KAAK,WAAW,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAA;AAExC,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;CACb,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBhB;AAqCD,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,UAAU,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,aAAa,EAAE,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAahB"}
package/dist/ui/status.js CHANGED
@@ -5,6 +5,8 @@
5
5
  import { formatProxySnapshot } from "./formatters/proxy";
6
6
  import { formatCopilotSnapshot } from "./formatters/copilot";
7
7
  import { formatZaiSnapshot } from "./formatters/zai";
8
+ import { formatOpenRouterSnapshot } from "./formatters/openrouter";
9
+ import { formatAnthropicSnapshot } from "./formatters/anthropic";
8
10
  import { formatBar, formatResetSuffix, formatMissingSnapshot } from "./formatters/shared";
9
11
  export async function sendStatusMessage(options) {
10
12
  const bus = options.client.bus;
@@ -35,6 +37,12 @@ function formatSnapshot(snapshot) {
35
37
  return formatCopilotSnapshot(snapshot);
36
38
  if (snapshot.provider === "zai-coding-plan")
37
39
  return formatZaiSnapshot(snapshot);
40
+ if (snapshot.provider === "openrouter")
41
+ return formatOpenRouterSnapshot(snapshot);
42
+ if (snapshot.provider === "anthropic")
43
+ return formatAnthropicSnapshot(snapshot);
44
+ if (snapshot.provider === "openrouter")
45
+ return formatOpenRouterSnapshot(snapshot);
38
46
  const plan = snapshot.planType ? ` (${snapshot.planType.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase())})` : "";
39
47
  const lines = [`→ [${snapshot.provider.toUpperCase()}]${plan}`];
40
48
  const metrics = [
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/usage/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAiB3C,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CA0D5D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/usage/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAiB3C,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CA8D5D"}
@@ -37,7 +37,9 @@ export async function loadUsageConfig() {
37
37
  "openai": true,
38
38
  "proxy": true,
39
39
  "copilot": true,
40
- "zai": true
40
+ "zai": true,
41
+ "anthropic": true,
42
+ "openrouter": true
41
43
  }
42
44
  }
43
45
  `;
@@ -51,6 +53,8 @@ export async function loadUsageConfig() {
51
53
  proxy: true,
52
54
  copilot: true,
53
55
  zai: true,
56
+ anthropic: true,
57
+ openrouter: true,
54
58
  },
55
59
  };
56
60
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/usage/fetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAQ7C,wBAAsB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA4CnF;AAYD,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzE;AA8BD,wBAAsB,SAAS,gDAG9B"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/usage/fetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAQ7C,wBAAsB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA6CnF;AAcD,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEzE;AA8BD,wBAAsB,SAAS,gDAG9B"}
@@ -6,7 +6,7 @@ import { providers } from "../providers";
6
6
  import { loadUsageConfig } from "./config";
7
7
  import { loadMergedAuths } from "./auth/loader";
8
8
  import { resolveProviderAuths } from "./registry";
9
- const CORE_PROVIDERS = ["codex", "proxy", "copilot", "zai-coding-plan"];
9
+ const CORE_PROVIDERS = ["codex", "proxy", "copilot", "zai-coding-plan", "anthropic", "openrouter"];
10
10
  export async function fetchUsageSnapshots(filter) {
11
11
  const target = resolveFilter(filter);
12
12
  const config = await loadUsageConfig().catch(() => null);
@@ -16,6 +16,8 @@ export async function fetchUsageSnapshots(filter) {
16
16
  return toggles.openai !== false;
17
17
  if (id === "zai-coding-plan")
18
18
  return toggles.zai !== false;
19
+ if (id === "openrouter")
20
+ return toggles.openrouter !== false;
19
21
  return toggles[id] !== false;
20
22
  };
21
23
  const { auths, codexDiagnostics } = await loadMergedAuths();
@@ -32,7 +34,7 @@ export async function fetchUsageSnapshots(filter) {
32
34
  }
33
35
  });
34
36
  // Handle special/default fetches
35
- for (const id of ["proxy", "copilot"]) {
37
+ for (const id of ["proxy", "copilot", "anthropic"]) {
36
38
  if ((!target || target === id) && isEnabled(id) && !fetched.has(id)) {
37
39
  const provider = providers[id];
38
40
  if (provider?.fetchUsage) {
@@ -54,7 +56,9 @@ function resolveFilter(f) {
54
56
  codex: "codex", openai: "codex", gpt: "codex",
55
57
  proxy: "proxy", agy: "proxy", gemini: "proxy",
56
58
  copilot: "copilot", github: "copilot",
57
- zai: "zai-coding-plan", glm: "zai-coding-plan"
59
+ zai: "zai-coding-plan", glm: "zai-coding-plan",
60
+ anthropic: "anthropic", claude: "anthropic",
61
+ openrouter: "openrouter", or: "openrouter",
58
62
  };
59
63
  return f ? aliases[f.toLowerCase().trim()] : undefined;
60
64
  }
@@ -4,6 +4,7 @@
4
4
  import type { CodexAuth } from "../providers/codex";
5
5
  import type { CopilotAuthData } from "../providers/copilot/types";
6
6
  import type { ZaiAuth } from "../providers/zai/types";
7
+ import type { OpenRouterAuth } from "../providers/openrouter/types";
7
8
  export type AuthEntry = {
8
9
  type?: string;
9
10
  access?: string;
@@ -22,6 +23,9 @@ type ProviderAuthEntry = {
22
23
  } | {
23
24
  providerID: "zai-coding-plan";
24
25
  auth: ZaiAuth;
26
+ } | {
27
+ providerID: "openrouter";
28
+ auth: OpenRouterAuth;
25
29
  };
26
30
  export declare function resolveProviderAuths(auths: AuthRecord, usageToken: string | null): ProviderAuthEntry[];
27
31
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/usage/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAErD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAElD,KAAK,iBAAiB,GAClB;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACxC;IAAE,UAAU,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAChD;IAAE,UAAU,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAsCpD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,EAAE,CActG"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/usage/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAEnE,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAElD,KAAK,iBAAiB,GAClB;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GACxC;IAAE,UAAU,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAChD;IAAE,UAAU,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAChD;IAAE,UAAU,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAA;AA8CtD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,EAAE,CActG"}
@@ -28,6 +28,14 @@ const providerDescriptors = [
28
28
  key: entry.key || entry.access || "",
29
29
  }),
30
30
  },
31
+ {
32
+ id: "openrouter",
33
+ authKeys: ["openrouter", "or"],
34
+ requiresOAuth: false,
35
+ buildAuth: (entry) => ({
36
+ key: entry.key || entry.access || "",
37
+ }),
38
+ },
31
39
  ];
32
40
  export function resolveProviderAuths(auths, usageToken) {
33
41
  const entries = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/opencode-usage-plugin",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "opencode plugin for tracking AI provider usage, rate limits, and quotas",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",