@howaboua/opencode-usage-plugin 0.1.1 → 0.1.3

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.
@@ -33,7 +33,7 @@ export function commandHooks(options) {
33
33
  const snapshots = await fetchUsageSnapshots(effectiveFilter);
34
34
  const filteredSnapshots = snapshots.filter(s => {
35
35
  if (targetProvider)
36
- return true; // User explicitly asked for it
36
+ return true;
37
37
  if (s.provider === "codex")
38
38
  return options.state.availableProviders.codex;
39
39
  if (s.provider === "proxy")
@@ -1,11 +1,7 @@
1
1
  /**
2
2
  * providers/copilot/auth.ts
3
3
  * Provides authentication and configuration helpers for GitHub Copilot.
4
- * Handles local auth token discovery and quota configuration reading.
5
4
  */
6
- import { type CopilotAuthData, type CopilotQuotaConfig } from "./types.js";
7
- export declare function getQuotaConfigPath(): string;
8
- export declare function getUsageTokenPath(): string;
5
+ import { type CopilotAuthData } from "./types.js";
9
6
  export declare function readCopilotAuth(): Promise<CopilotAuthData | null>;
10
- export declare function readQuotaConfig(): CopilotQuotaConfig | null;
11
7
  //# sourceMappingURL=auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE1E,wBAAgB,kBAAkB,IAAI,MAAM,CAM3C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CA4BvE;AAED,wBAAgB,eAAe,IAAI,kBAAkB,GAAG,IAAI,CAY3D"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAevE"}
@@ -1,33 +1,12 @@
1
1
  /**
2
2
  * providers/copilot/auth.ts
3
3
  * Provides authentication and configuration helpers for GitHub Copilot.
4
- * Handles local auth token discovery and quota configuration reading.
5
4
  */
6
- import { existsSync, readFileSync } from "fs";
5
+ import { existsSync } from "fs";
7
6
  import { readFile } from "fs/promises";
8
- import { homedir } from "os";
9
- import { join } from "path";
10
- import { getAppDataPath, getAuthFilePath } from "../../utils/paths.js";
11
- export function getQuotaConfigPath() {
12
- return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode", "copilot-quota-token.json");
13
- }
14
- export function getUsageTokenPath() {
15
- return join(getAppDataPath(), "copilot-usage-token.json");
16
- }
7
+ import { getAuthFilePath } from "../../utils/paths.js";
17
8
  export async function readCopilotAuth() {
18
9
  try {
19
- const usagePath = getUsageTokenPath();
20
- if (existsSync(usagePath)) {
21
- const content = await readFile(usagePath, "utf-8");
22
- const data = JSON.parse(content);
23
- if (data?.token) {
24
- return {
25
- type: "oauth",
26
- refresh: data.token,
27
- access: data.token,
28
- };
29
- }
30
- }
31
10
  const authPath = getAuthFilePath();
32
11
  if (existsSync(authPath)) {
33
12
  const content = await readFile(authPath, "utf-8");
@@ -43,18 +22,3 @@ export async function readCopilotAuth() {
43
22
  return null;
44
23
  }
45
24
  }
46
- export function readQuotaConfig() {
47
- try {
48
- const configPath = getQuotaConfigPath();
49
- if (!existsSync(configPath))
50
- return null;
51
- const content = readFileSync(configPath, "utf-8");
52
- const parsed = JSON.parse(content);
53
- if (!parsed?.token || !parsed?.username || !parsed?.tier)
54
- return null;
55
- return parsed;
56
- }
57
- catch {
58
- return null;
59
- }
60
- }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * providers/copilot/index.ts
3
3
  * Main entry point for the GitHub Copilot usage provider.
4
- * Orchestrates token exchange and fetching from public and internal APIs.
5
4
  */
6
5
  import type { UsageProvider } from "../base.js";
7
6
  export declare const CopilotProvider: UsageProvider<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAiE/C,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,IAAI,CAgF/C,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AA8D/C,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,IAAI,CAuD/C,CAAA"}
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * providers/copilot/index.ts
3
3
  * Main entry point for the GitHub Copilot usage provider.
4
- * Orchestrates token exchange and fetching from public and internal APIs.
5
4
  */
6
- import { COPILOT_PLAN_LIMITS } from "./types.js";
7
- import { readCopilotAuth, readQuotaConfig } from "./auth.js";
8
- import { toCopilotQuotaFromBilling, toCopilotQuotaFromInternal, } from "./response.js";
5
+ import { readCopilotAuth } from "./auth.js";
6
+ import { toCopilotQuotaFromInternal, } from "./response.js";
9
7
  const GITHUB_API_BASE_URL = "https://api.github.com";
10
8
  const COPILOT_INTERNAL_USER_URL = `${GITHUB_API_BASE_URL}/copilot_internal/user`;
11
9
  const COPILOT_TOKEN_EXCHANGE_URL = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`;
@@ -57,57 +55,35 @@ export const CopilotProvider = {
57
55
  async fetchUsage() {
58
56
  const now = Date.now();
59
57
  let quota = null;
60
- const config = readQuotaConfig();
61
- if (config) {
58
+ const auth = await readCopilotAuth();
59
+ const oauthToken = auth?.refresh || auth?.access;
60
+ if (oauthToken) {
62
61
  try {
63
- const resp = await fetchWithTimeout(`${GITHUB_API_BASE_URL}/users/${config.username}/settings/billing/premium_request/usage`, {
62
+ let resp = await fetchWithTimeout(COPILOT_INTERNAL_USER_URL, {
64
63
  headers: {
65
- Accept: "application/vnd.github+json",
66
- Authorization: `Bearer ${config.token}`,
67
- "X-GitHub-Api-Version": "2022-11-28",
64
+ Accept: "application/json",
65
+ Authorization: `token ${oauthToken}`,
66
+ ...COPILOT_HEADERS,
68
67
  },
69
68
  });
69
+ if (!resp.ok) {
70
+ const copilotToken = await exchangeForCopilotToken(oauthToken);
71
+ if (copilotToken) {
72
+ resp = await fetchWithTimeout(COPILOT_INTERNAL_USER_URL, {
73
+ headers: {
74
+ Accept: "application/json",
75
+ Authorization: `Bearer ${copilotToken}`,
76
+ ...COPILOT_HEADERS,
77
+ },
78
+ });
79
+ }
80
+ }
70
81
  if (resp.ok) {
71
82
  const data = (await resp.json());
72
- quota = toCopilotQuotaFromBilling(data, COPILOT_PLAN_LIMITS[config.tier]);
83
+ quota = toCopilotQuotaFromInternal(data);
73
84
  }
74
85
  }
75
86
  catch {
76
- // Fallback to internal API
77
- }
78
- }
79
- if (!quota) {
80
- const auth = await readCopilotAuth();
81
- const oauthToken = auth?.refresh || auth?.access;
82
- if (oauthToken) {
83
- try {
84
- let resp = await fetchWithTimeout(COPILOT_INTERNAL_USER_URL, {
85
- headers: {
86
- Accept: "application/json",
87
- Authorization: `token ${oauthToken}`,
88
- ...COPILOT_HEADERS,
89
- },
90
- });
91
- if (!resp.ok) {
92
- const copilotToken = await exchangeForCopilotToken(oauthToken);
93
- if (copilotToken) {
94
- resp = await fetchWithTimeout(COPILOT_INTERNAL_USER_URL, {
95
- headers: {
96
- Accept: "application/json",
97
- Authorization: `Bearer ${copilotToken}`,
98
- ...COPILOT_HEADERS,
99
- },
100
- });
101
- }
102
- }
103
- if (resp.ok) {
104
- const data = (await resp.json());
105
- quota = toCopilotQuotaFromInternal(data);
106
- }
107
- }
108
- catch {
109
- // Ignore
110
- }
111
87
  }
112
88
  }
113
89
  if (!quota)
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD,MAAM,WAAW,2BAA2B;IAC1C,mBAAmB,CAAC,EAAE;QACpB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE;QAChB,oBAAoB,CAAC,EAAE;YACrB,WAAW,EAAE,MAAM,CAAA;YACnB,iBAAiB,EAAE,MAAM,CAAA;YACzB,SAAS,EAAE,MAAM,CAAA;YACjB,SAAS,EAAE,OAAO,CAAA;SACnB,CAAA;KACF,CAAA;IACD,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,gBAAgB,EAAE,CAAA;CAC/B;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,YAAY,GAAG,IAAI,CA8BjG;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,MAAM,GACZ,YAAY,CAed"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/response.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAElD,MAAM,WAAW,2BAA2B;IAC1C,mBAAmB,CAAC,EAAE;QACpB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;IACD,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,eAAe,CAAC,EAAE;QAChB,oBAAoB,CAAC,EAAE;YACrB,WAAW,EAAE,MAAM,CAAA;YACnB,iBAAiB,EAAE,MAAM,CAAA;YACzB,SAAS,EAAE,MAAM,CAAA;YACjB,SAAS,EAAE,OAAO,CAAA;SACnB,CAAA;KACF,CAAA;IACD,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,MAAM,CAAA;QACZ,WAAW,EAAE,MAAM,CAAA;KACpB,CAAA;CACF;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAA;IACX,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,gBAAgB,EAAE,CAAA;CAC/B;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,2BAA2B,GAAG,YAAY,GAAG,IAAI,CA4CjG;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,oBAAoB,EAC1B,KAAK,EAAE,MAAM,GACZ,YAAY,CAed"}
@@ -4,27 +4,36 @@
4
4
  * Handles both public billing and internal user API shapes.
5
5
  */
6
6
  export function toCopilotQuotaFromInternal(data) {
7
- // Handle "limited" user format (Free/Pro limited)
8
7
  if (data.limited_user_quotas) {
9
- const chatRemaining = data.limited_user_quotas.chat ?? 0;
10
- const chatTotal = data.monthly_quotas?.chat ?? 50;
11
- const completionsRemaining = data.limited_user_quotas.completions ?? 0;
12
- const completionsTotal = data.monthly_quotas?.completions ?? 2000;
8
+ const chatRemainingRaw = data.limited_user_quotas.chat ?? 0;
9
+ const chatTotalRaw = data.monthly_quotas?.chat ?? 0;
10
+ const chatScale = chatTotalRaw === 500 ? 10 : 1;
11
+ const chatRemaining = Math.floor(chatRemainingRaw / chatScale);
12
+ const chatTotal = Math.floor(chatTotalRaw / chatScale);
13
+ const completionsRemainingRaw = data.limited_user_quotas.completions ?? 0;
14
+ const completionsTotalRaw = data.monthly_quotas?.completions ?? 2000;
15
+ const compScale = completionsTotalRaw === 4000 ? 2 : 1;
16
+ const completionsRemaining = Math.floor(completionsRemainingRaw / compScale);
17
+ const completionsTotal = Math.floor(completionsTotalRaw / compScale);
13
18
  return {
14
- used: Math.max(0, chatTotal - chatRemaining),
19
+ used: chatRemaining,
15
20
  total: chatTotal,
16
21
  percentRemaining: chatTotal > 0 ? Math.round((chatRemaining / chatTotal) * 100) : 0,
17
22
  resetTime: data.limited_user_reset_date || data.quota_reset_date,
18
- completionsUsed: Math.max(0, completionsTotal - completionsRemaining),
19
- completionsTotal: completionsTotal,
23
+ completionsUsed: completionsRemaining,
24
+ completionsTotal,
20
25
  };
21
26
  }
22
- // Handle standard format
23
27
  if (data.quota_snapshots?.premium_interactions) {
24
28
  const premium = data.quota_snapshots.premium_interactions;
29
+ const totalRaw = premium.unlimited ? -1 : premium.entitlement;
30
+ const remainingRaw = premium.remaining;
31
+ const scaleFactor = totalRaw === 500 ? 10 : 1;
32
+ const chatRemaining = totalRaw === -1 ? -1 : Math.floor(remainingRaw / scaleFactor);
33
+ const chatTotal = totalRaw === -1 ? -1 : Math.floor(totalRaw / scaleFactor);
25
34
  return {
26
- used: premium.unlimited ? 0 : premium.entitlement - premium.remaining,
27
- total: premium.unlimited ? -1 : premium.entitlement,
35
+ used: chatRemaining,
36
+ total: chatTotal,
28
37
  percentRemaining: Math.round(premium.percent_remaining),
29
38
  resetTime: data.quota_reset_date,
30
39
  };
@@ -4,11 +4,6 @@
4
4
  * Includes auth data shapes and plan limit configurations.
5
5
  */
6
6
  export type CopilotTier = "free" | "pro" | "pro+" | "business" | "enterprise";
7
- export interface CopilotQuotaConfig {
8
- token: string;
9
- username: string;
10
- tier: CopilotTier;
11
- }
12
7
  export interface CopilotAuthData {
13
8
  type: string;
14
9
  refresh?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAA;AAE7E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,WAAW,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAM3D,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,YAAY,CAAA;AAE7E,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAM3D,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA8B,MAAM,SAAS,CAAA;AAwHxE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAoC7D"}
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAA8B,MAAM,SAAS,CAAA;AAoHxE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAoC7D"}
@@ -12,7 +12,6 @@ const GROUP_MAPPING = {
12
12
  "25-flash": "25-flash",
13
13
  "25-lite": "25-lite"
14
14
  };
15
- // Display name ordering priority
16
15
  const GROUP_ORDER = ["claude", "g3-pro", "g3-flash", "25-flash", "25-lite"];
17
16
  function sortGroupNames(groups) {
18
17
  return Array.from(groups.keys()).sort((a, b) => {
@@ -72,10 +71,8 @@ function aggregateCredentialsByTier(credentials) {
72
71
  if (!(name in GROUP_MAPPING))
73
72
  continue;
74
73
  const mappedName = GROUP_MAPPING[name];
75
- // Find the best window from group_usage
76
74
  const windows = groupData.windows || {};
77
75
  let bestWindow = null;
78
- // Priority order for windows
79
76
  const windowPriority = ["daily", "5h", "1h", "15m"];
80
77
  for (const windowName of windowPriority) {
81
78
  if (windows[windowName]) {
@@ -83,7 +80,6 @@ function aggregateCredentialsByTier(credentials) {
83
80
  break;
84
81
  }
85
82
  }
86
- // Fallback to any available window
87
83
  if (!bestWindow && Object.keys(windows).length > 0) {
88
84
  bestWindow = Object.values(windows)[0];
89
85
  }
@@ -6,5 +6,5 @@ import type { UsageProvider } from "../base";
6
6
  export type { ProxyResponse } from "./types";
7
7
  export { fetchProxyLimits } from "./fetch";
8
8
  export { formatProxyLimits } from "./format";
9
- export declare const ProxyProvider: UsageProvider;
9
+ export declare const ProxyProvider: UsageProvider<void>;
10
10
  //# sourceMappingURL=index.d.ts.map
@@ -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;AAM5C,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAiK5C,eAAO,MAAM,aAAa,EAAE,aAwB3B,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;AAM5C,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAyI5C,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,IAAI,CAwB7C,CAAA"}
@@ -17,34 +17,28 @@ const GROUP_MAPPING = {
17
17
  "25-flash": "25-flash",
18
18
  "25-lite": "25-lite"
19
19
  };
20
- // Display name ordering priority
21
20
  const GROUP_ORDER = ["claude", "g3-pro", "g3-flash", "25-flash", "25-lite"];
22
21
  function sortQuotaGroups(groups) {
23
22
  return groups.sort((a, b) => {
24
23
  const aIndex = GROUP_ORDER.indexOf(a.name);
25
24
  const bIndex = GROUP_ORDER.indexOf(b.name);
26
- // Both in priority list: sort by index
27
25
  if (aIndex !== -1 && bIndex !== -1)
28
26
  return aIndex - bIndex;
29
- // Only a in priority list: a comes first
30
27
  if (aIndex !== -1)
31
28
  return -1;
32
- // Only b in priority list: b comes first
33
29
  if (bIndex !== -1)
34
30
  return 1;
35
- // Neither in priority list: alphabetical
36
31
  return a.name.localeCompare(b.name);
37
32
  });
38
33
  }
39
34
  function normalizeTier(tier) {
40
35
  if (!tier)
41
36
  return "free";
42
- return tier.includes("free") ? "free" : "paid";
37
+ const t = tier.toLowerCase();
38
+ if (t === "paid" || t === "pro" || t === "premium" || t === "individual" || t.includes("paid"))
39
+ return "paid";
40
+ return "free";
43
41
  }
44
- /**
45
- * Extract quota groups from group_usage data
46
- * New API structure: group_usage[groupName].windows[windowName]
47
- */
48
42
  function parseQuotaGroupsFromCredential(groupUsage) {
49
43
  if (!groupUsage)
50
44
  return [];
@@ -53,10 +47,8 @@ function parseQuotaGroupsFromCredential(groupUsage) {
53
47
  const mappedName = GROUP_MAPPING[groupName];
54
48
  if (!mappedName)
55
49
  continue;
56
- // Find the window with the best data (prefer daily, then 5h, then any)
57
50
  const windows = groupData.windows || {};
58
51
  let bestWindow = null;
59
- // Priority order for windows
60
52
  const windowPriority = ["daily", "5h", "1h", "15m"];
61
53
  for (const windowName of windowPriority) {
62
54
  if (windows[windowName]) {
@@ -64,55 +56,42 @@ function parseQuotaGroupsFromCredential(groupUsage) {
64
56
  break;
65
57
  }
66
58
  }
67
- // Fallback to any available window
68
59
  if (!bestWindow && Object.keys(windows).length > 0) {
69
60
  bestWindow = Object.values(windows)[0];
70
61
  }
71
62
  if (!bestWindow)
72
63
  continue;
73
- const existing = result.get(mappedName);
74
- if (existing) {
75
- existing.remaining += bestWindow.remaining;
76
- existing.max += bestWindow.limit || 0;
77
- // Use the latest reset time
78
- if (bestWindow.reset_at) {
79
- const newResetTime = new Date(bestWindow.reset_at * 1000).toISOString();
80
- if (!existing.resetTime || new Date(newResetTime) > new Date(existing.resetTime)) {
81
- existing.resetTime = newResetTime;
82
- }
83
- }
84
- }
85
- else {
86
- result.set(mappedName, {
87
- name: mappedName,
88
- remaining: bestWindow.remaining,
89
- max: bestWindow.limit || bestWindow.remaining,
90
- remainingPct: bestWindow.limit ? Math.round((bestWindow.remaining / bestWindow.limit) * 100) : 0,
91
- resetTime: bestWindow.reset_at ? new Date(bestWindow.reset_at * 1000).toISOString() : null,
92
- });
93
- }
64
+ result.set(mappedName, {
65
+ name: mappedName,
66
+ remaining: bestWindow.remaining,
67
+ max: bestWindow.limit || bestWindow.remaining,
68
+ remainingPct: bestWindow.limit ? Math.round((bestWindow.remaining / bestWindow.limit) * 100) : 0,
69
+ resetTime: bestWindow.reset_at ? new Date(bestWindow.reset_at * 1000).toISOString() : null,
70
+ });
94
71
  }
95
72
  return Array.from(result.values());
96
73
  }
97
- function aggregateByTier(credentials) {
74
+ function aggregateByProvider(provider) {
98
75
  const tiers = {
99
76
  paid: new Map(),
100
77
  free: new Map(),
101
78
  };
102
- for (const cred of credentials) {
103
- const tier = normalizeTier(cred.tier);
104
- const groups = parseQuotaGroupsFromCredential(cred.group_usage);
105
- for (const group of groups) {
106
- const existing = tiers[tier].get(group.name);
107
- if (existing) {
108
- existing.remaining += group.remaining;
109
- existing.max += group.max;
110
- if (group.resetTime && (!existing.resetTime || new Date(group.resetTime) > new Date(existing.resetTime))) {
111
- existing.resetTime = group.resetTime;
79
+ if (provider.credentials) {
80
+ for (const cred of Object.values(provider.credentials)) {
81
+ const tier = normalizeTier(cred.tier);
82
+ const groups = parseQuotaGroupsFromCredential(cred.group_usage);
83
+ for (const group of groups) {
84
+ const existing = tiers[tier].get(group.name);
85
+ if (existing) {
86
+ existing.remaining += group.remaining;
87
+ existing.max += group.max;
88
+ if (group.resetTime && (!existing.resetTime || new Date(group.resetTime) > new Date(existing.resetTime))) {
89
+ existing.resetTime = group.resetTime;
90
+ }
91
+ }
92
+ else {
93
+ tiers[tier].set(group.name, { ...group });
112
94
  }
113
- }
114
- else {
115
- tiers[tier].set(group.name, { ...group });
116
95
  }
117
96
  }
118
97
  }
@@ -134,11 +113,9 @@ function parseProviders(data) {
134
113
  if (!data.providers)
135
114
  return [];
136
115
  return Object.entries(data.providers).map(([name, provider]) => {
137
- // Convert credentials object to array
138
- const credentialsArray = Object.values(provider.credentials || {});
139
116
  return {
140
117
  name,
141
- tiers: aggregateByTier(credentialsArray),
118
+ tiers: aggregateByProvider(provider),
142
119
  };
143
120
  });
144
121
  }
@@ -159,7 +136,7 @@ export const ProxyProvider = {
159
136
  const config = await loadUsageConfig();
160
137
  const data = await fetchProxyLimits(config);
161
138
  return {
162
- timestamp: data.timestamp * 1000,
139
+ timestamp: (data.timestamp || Date.now() / 1000) * 1000,
163
140
  provider: "proxy",
164
141
  planType: null,
165
142
  primary: null,
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * Type definitions for the Antigravity proxy provider.
3
3
  */
4
- /** Configuration stored in ~/.config/opencode/usage-config.jsonc */
5
- /** Token statistics from the proxy */
6
4
  export type TokenStats = {
7
5
  input_cached?: number;
8
6
  input_uncached?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,oEAAoE;AACpE,sCAAsC;AACtC,MAAM,MAAM,UAAU,GAAG;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,qCAAqC;AACrC,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,oCAAoC;AACpC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,gBAAgB,CAAA;IACzB,MAAM,EAAE,UAAU,CAAA;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B,CAAA;AAED,8BAA8B;AAC9B,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,gBAAgB,CAAA;IACzB,MAAM,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,6BAA6B;AAC7B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,uBAAuB;AACvB,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACpD,CAAA;AAED,wBAAwB;AACxB,MAAM,MAAM,UAAU,GAAG;IACvB,CAAC,QAAQ,EAAE,MAAM,GAAG;QAClB,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAE,MAAM,CAAA;KACd,CAAA;CACF,CAAA;AAED,yBAAyB;AACzB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,8BAA8B;AAC9B,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,UAAU,CAAA;IACjB,OAAO,EAAE;QACP,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,CAAA;KACjC,CAAA;IACD,kBAAkB,EAAE,gBAAgB,CAAA;CACrC,CAAA;AAED,0CAA0C;AAC1C,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,UAAU,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACxC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,wCAAwC;AACxC,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAA;CACrD,CAAA;AAED,sCAAsC;AACtC,MAAM,MAAM,OAAO,GAAG;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,UAAU,CAAA;IAClB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC,CAAA;AAED,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IACnC,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/proxy/types.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -63,6 +63,7 @@ export interface UsageSnapshot {
63
63
  proxyQuota?: ProxyQuota;
64
64
  copilotQuota?: CopilotQuota;
65
65
  updatedAt: number;
66
+ isMissing?: boolean;
66
67
  }
67
68
  export interface UsageEntry {
68
69
  id: 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":"AAAA;;GAEG;AAEH,eAAO,MAAM,SAAS,0IAcZ,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjD,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;IACrB,WAAW,EAAE,eAAe,EAAE,CAAA;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,aAAa,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,iBAAiB,EAAE,CAAA;IAC9B,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;CACF;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,SAAS,EAAE,eAAe,GAAG,IAAI,CAAA;IACjC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAA;IAC/B,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;GAEG;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;AAE1C,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,CAwChB;AAwID,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,CA6BhB"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/ui/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAGtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,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,CAqChB;AA4KD,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,CA6BhB"}
package/dist/ui/status.js CHANGED
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Renders usage snapshots into readable status text.
3
3
  */
4
+ import { platform, homedir } from "os";
5
+ import { join } from "path";
4
6
  export async function sendStatusMessage(options) {
5
- // 1. Send to Companion via Bus
6
7
  // @ts-ignore
7
8
  const bus = options.client.bus;
8
9
  if (bus) {
@@ -18,7 +19,6 @@ export async function sendStatusMessage(options) {
18
19
  }
19
20
  catch { }
20
21
  }
21
- // 2. Send plain message to TUI
22
22
  await options.client.session
23
23
  .prompt({
24
24
  path: { id: options.sessionID },
@@ -34,7 +34,6 @@ export async function sendStatusMessage(options) {
34
34
  },
35
35
  })
36
36
  .catch(async () => {
37
- // 3. Fallback: Toast
38
37
  await options.client.tui
39
38
  .showToast({
40
39
  body: { title: "Usage Status", message: options.text, variant: "info" },
@@ -111,17 +110,53 @@ function formatCopilotSnapshot(snapshot) {
111
110
  const resetSuffix = copilot.resetTime ? formatResetSuffixISO(copilot.resetTime) : "";
112
111
  const totalLabel = copilot.total === -1 ? "∞" : copilot.total.toString();
113
112
  const chatLabel = "Chat:".padEnd(13);
114
- lines.push(` ${chatLabel} ${formatBar(copilot.percentRemaining)} ${copilot.used}/${totalLabel}${resetSuffix}`);
113
+ const chatRemaining = copilot.used;
114
+ const chatPct = copilot.percentRemaining;
115
+ lines.push(` ${chatLabel} ${formatBar(chatPct)} ${chatRemaining}/${totalLabel}${resetSuffix}`);
115
116
  if (copilot.completionsUsed !== undefined && copilot.completionsTotal !== undefined) {
116
117
  const compLabel = "Completions:".padEnd(13);
117
118
  const compPct = copilot.completionsTotal > 0
118
- ? Math.round(((copilot.completionsTotal - copilot.completionsUsed) / copilot.completionsTotal) * 100)
119
+ ? Math.round((copilot.completionsUsed / copilot.completionsTotal) * 100)
119
120
  : 0;
120
121
  lines.push(` ${compLabel} ${formatBar(compPct)} ${copilot.completionsUsed}/${copilot.completionsTotal}`);
121
122
  }
122
123
  return lines;
123
124
  }
125
+ function getAppDataPath() {
126
+ const home = homedir();
127
+ const plat = platform();
128
+ if (plat === "darwin")
129
+ return join(home, ".config", "opencode");
130
+ if (plat === "win32")
131
+ return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode");
132
+ return join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "opencode");
133
+ }
134
+ function formatMissingSnapshot(snapshot) {
135
+ const provider = snapshot.provider;
136
+ const label = provider === "codex" ? "codex" : provider === "proxy" ? "proxy" : "gh";
137
+ const configPath = join(getAppDataPath(), "usage-config.jsonc");
138
+ let providerInstruction = "";
139
+ if (provider === "codex") {
140
+ providerInstruction = "if you dont have codex oauth, please set your usage-config.jsonc to openai: false";
141
+ }
142
+ else if (provider === "proxy") {
143
+ providerInstruction = "if you are not running Mirrowel's proxy, please set your usage-config.jsonc to proxy: false";
144
+ }
145
+ else if (provider === "copilot") {
146
+ providerInstruction = "if you are not running GitHub Copilot, please set your usage-config.jsonc to copilot: false";
147
+ }
148
+ return [
149
+ `→ [${provider.toUpperCase()}] - ${providerInstruction}`,
150
+ "",
151
+ `The file can be found in ${configPath}. Please read the comments in the file or visit the repo for the readme.`,
152
+ "",
153
+ "If you are seeing empty usages or errors, despite having everything set up correctly, please fire an issue at https://github.com/IgorWarzocha/opencode-usage-plugin/issues - thank you!",
154
+ ];
155
+ }
124
156
  function formatSnapshot(snapshot) {
157
+ if (snapshot.isMissing) {
158
+ return formatMissingSnapshot(snapshot);
159
+ }
125
160
  if (snapshot.provider === "proxy" && snapshot.proxyQuota) {
126
161
  return formatProxySnapshot(snapshot);
127
162
  }
@@ -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;AAI3C,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC,CAsD5D"}
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,CA0C5D"}
@@ -2,25 +2,25 @@
2
2
  * Configuration management for the Usage Plugin.
3
3
  */
4
4
  import { join } from "path";
5
- import { homedir } from "os";
6
- const CONFIG_PATH = join(homedir(), ".config", "opencode", "usage-config.jsonc");
5
+ import { homedir, platform } from "os";
6
+ function getConfigPath() {
7
+ const plat = platform();
8
+ const home = homedir();
9
+ if (plat === "win32") {
10
+ return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode", "usage-config.jsonc");
11
+ }
12
+ // For macOS, Linux, and other Unix-like systems, use XDG_CONFIG_HOME or default to ~/.config
13
+ const configHome = process.env.XDG_CONFIG_HOME || join(home, ".config");
14
+ return join(configHome, "opencode", "usage-config.jsonc");
15
+ }
16
+ const CONFIG_PATH = getConfigPath();
7
17
  export async function loadUsageConfig() {
8
18
  const file = Bun.file(CONFIG_PATH);
9
19
  if (!(await file.exists())) {
10
- const content = `/**
11
- * Usage Plugin Configuration
12
- */
13
- {
14
- // Proxy endpoint (e.g. http://localhost:8000)
20
+ const content = `{
15
21
  "endpoint": "",
16
-
17
- // API key for authentication
18
22
  "apiKey": "",
19
-
20
- // Request timeout in milliseconds
21
23
  "timeout": 10000,
22
-
23
- // Provider visibility
24
24
  "providers": {
25
25
  "openai": true,
26
26
  "proxy": true,
@@ -42,9 +42,7 @@ export async function loadUsageConfig() {
42
42
  }
43
43
  try {
44
44
  const content = await file.text();
45
- // Remove comments first (both // and /* */)
46
45
  const withoutComments = content.replace(/(\".*?\"|\'.*?\')|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (m, g1) => g1 ?? "");
47
- // Remove trailing commas before closing brackets/braces
48
46
  const cleanJson = withoutComments.replace(/,(\s*[}\]])/g, "$1");
49
47
  const config = JSON.parse(cleanJson);
50
48
  return config;
@@ -1 +1 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/usage/fetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAI7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAe5C,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAWlD,CAAA;AAED,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIzE;AAED,wBAAsB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA6CnF;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CASrD"}
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/usage/fetch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAI7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAe5C,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAWlD,CAAA;AAED,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIzE;AAED,wBAAsB,mBAAmB,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CA4EnF;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CA8BrD"}
@@ -5,7 +5,7 @@
5
5
  import z from "zod";
6
6
  import { providers } from "../providers";
7
7
  import { loadUsageConfig } from "./config";
8
- import { getAuthFilePath } from "../utils";
8
+ import { getPossibleAuthPaths } from "../utils";
9
9
  import { resolveProviderAuths } from "./registry";
10
10
  const authEntrySchema = z
11
11
  .object({
@@ -51,7 +51,8 @@ export async function fetchUsageSnapshots(filter) {
51
51
  const auths = await loadAuths();
52
52
  const entries = resolveProviderAuths(auths, null);
53
53
  const snapshots = [];
54
- // Fetch from auth-based providers
54
+ const coreProviders = ["codex", "proxy", "copilot"];
55
+ const fetchedProviders = new Set();
55
56
  const fetches = entries
56
57
  .filter((entry) => !targetProvider || entry.providerID === targetProvider)
57
58
  .filter((entry) => isProviderEnabled(entry.providerID))
@@ -59,11 +60,16 @@ export async function fetchUsageSnapshots(filter) {
59
60
  const provider = providers[entry.providerID];
60
61
  if (!provider?.fetchUsage)
61
62
  return;
62
- const snapshot = await provider.fetchUsage(entry.auth).catch(() => null);
63
- if (snapshot)
64
- snapshots.push(snapshot);
63
+ try {
64
+ const snapshot = await provider.fetchUsage(entry.auth);
65
+ if (snapshot) {
66
+ snapshots.push(snapshot);
67
+ fetchedProviders.add(entry.providerID);
68
+ }
69
+ }
70
+ catch {
71
+ }
65
72
  });
66
- // Always include special providers (no auth entries needed) if no filter or filter matches
67
73
  const specialProviders = ["proxy", "copilot"];
68
74
  for (const id of specialProviders) {
69
75
  if ((!targetProvider || targetProvider === id) && isProviderEnabled(id)) {
@@ -72,27 +78,84 @@ export async function fetchUsageSnapshots(filter) {
72
78
  fetches.push(provider
73
79
  .fetchUsage(undefined)
74
80
  .then((snapshot) => {
75
- if (snapshot)
81
+ if (snapshot) {
76
82
  snapshots.push(snapshot);
83
+ fetchedProviders.add(id);
84
+ }
77
85
  })
78
86
  .catch(() => { }));
79
87
  }
80
88
  }
81
89
  }
82
90
  await Promise.race([Promise.all(fetches), timeout(5000)]);
91
+ for (const id of coreProviders) {
92
+ if (isProviderEnabled(id) && !fetchedProviders.has(id)) {
93
+ if (!targetProvider || targetProvider === id) {
94
+ snapshots.push({
95
+ timestamp: Date.now(),
96
+ provider: id,
97
+ planType: null,
98
+ primary: null,
99
+ secondary: null,
100
+ codeReview: null,
101
+ credits: null,
102
+ updatedAt: Date.now(),
103
+ isMissing: true,
104
+ });
105
+ }
106
+ }
107
+ }
83
108
  return snapshots;
84
109
  }
85
110
  export async function loadAuths() {
86
- const authPath = getAuthFilePath();
87
- const data = await Bun.file(authPath)
88
- .json()
89
- .catch(() => ({}));
90
- if (!data || typeof data !== "object")
91
- return {};
92
- const parsed = authRecordSchema.safeParse(data);
111
+ const possiblePaths = getPossibleAuthPaths();
112
+ const mergedAuth = {};
113
+ for (const authPath of possiblePaths.reverse()) {
114
+ try {
115
+ const file = Bun.file(authPath);
116
+ if (!(await file.exists()))
117
+ continue;
118
+ const data = await file.json();
119
+ if (data && typeof data === "object") {
120
+ if (authPath.includes(".codex")) {
121
+ const codexAuth = transformCodexAuth(data);
122
+ if (codexAuth) {
123
+ Object.assign(mergedAuth, codexAuth);
124
+ }
125
+ continue;
126
+ }
127
+ const parsed = authRecordSchema.safeParse(data);
128
+ if (parsed.success) {
129
+ Object.assign(mergedAuth, parsed.data);
130
+ }
131
+ }
132
+ }
133
+ catch {
134
+ continue;
135
+ }
136
+ }
137
+ return mergedAuth;
138
+ }
139
+ function transformCodexAuth(data) {
140
+ const codexAuthSchema = z.object({
141
+ tokens: z.object({
142
+ access_token: z.string(),
143
+ account_id: z.string().optional(),
144
+ refresh_token: z.string().optional(),
145
+ }),
146
+ });
147
+ const parsed = codexAuthSchema.safeParse(data);
93
148
  if (!parsed.success)
94
- return {};
95
- return parsed.data;
149
+ return null;
150
+ const { access_token, account_id, refresh_token } = parsed.data.tokens;
151
+ return {
152
+ openai: {
153
+ type: "oauth",
154
+ access: access_token,
155
+ accountId: account_id,
156
+ refresh: refresh_token,
157
+ },
158
+ };
96
159
  }
97
160
  function timeout(ms) {
98
161
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -1,3 +1,3 @@
1
1
  export { parseBooleanHeader, parseIntegerHeader, parseNumberHeader } from "./headers";
2
- export { getAppDataPath, getAuthFilePath } from "./paths";
2
+ export { getAppDataPath, getAuthFilePath, getPossibleAuthPaths } from "./paths";
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AACrF,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AACrF,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA"}
@@ -1,2 +1,2 @@
1
1
  export { parseBooleanHeader, parseIntegerHeader, parseNumberHeader } from "./headers";
2
- export { getAppDataPath, getAuthFilePath } from "./paths";
2
+ export { getAppDataPath, getAuthFilePath, getPossibleAuthPaths } from "./paths";
@@ -3,5 +3,13 @@
3
3
  * Centralizes platform-specific paths so callers stay simple and consistent.
4
4
  */
5
5
  export declare function getAppDataPath(): string;
6
+ /**
7
+ * Returns all possible auth file paths for the current platform.
8
+ * On macOS, OpenCode uses Linux-style paths, so we check both.
9
+ */
10
+ export declare function getPossibleAuthPaths(): string[];
11
+ /**
12
+ * Returns the first existing auth file path, or the default location if none exist.
13
+ */
6
14
  export declare function getAuthFilePath(): string;
7
15
  //# sourceMappingURL=paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,wBAAgB,cAAc,IAAI,MAAM,CAiBvC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,cAAc,IAAI,MAAM,CAiBvC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAyB/C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAYxC"}
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { homedir, platform } from "os";
6
6
  import { join } from "path";
7
+ import { existsSync } from "fs";
7
8
  export function getAppDataPath() {
8
9
  const plat = platform();
9
10
  const home = homedir();
@@ -19,6 +20,47 @@ export function getAppDataPath() {
19
20
  }
20
21
  return join(home, ".local", "share", "opencode");
21
22
  }
23
+ /**
24
+ * Returns all possible auth file paths for the current platform.
25
+ * On macOS, OpenCode uses Linux-style paths, so we check both.
26
+ */
27
+ export function getPossibleAuthPaths() {
28
+ const plat = platform();
29
+ const home = homedir();
30
+ const pathSet = new Set();
31
+ if (plat === "darwin") {
32
+ // OpenCode on macOS uses Linux-style paths
33
+ pathSet.add(join(home, ".local", "share", "opencode", "auth.json"));
34
+ // Standard macOS location (fallback)
35
+ pathSet.add(join(home, "Library", "Application Support", "opencode", "auth.json"));
36
+ // Codex-specific auth (fallback)
37
+ pathSet.add(join(home, ".codex", "auth.json"));
38
+ }
39
+ else if (plat === "win32") {
40
+ pathSet.add(join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode", "auth.json"));
41
+ }
42
+ else {
43
+ // Linux/other
44
+ const xdgData = process.env.XDG_DATA_HOME;
45
+ if (xdgData) {
46
+ pathSet.add(join(xdgData, "opencode", "auth.json"));
47
+ }
48
+ pathSet.add(join(home, ".local", "share", "opencode", "auth.json"));
49
+ pathSet.add(join(home, ".codex", "auth.json"));
50
+ }
51
+ return Array.from(pathSet);
52
+ }
53
+ /**
54
+ * Returns the first existing auth file path, or the default location if none exist.
55
+ */
22
56
  export function getAuthFilePath() {
23
- return join(getAppDataPath(), "auth.json");
57
+ const possiblePaths = getPossibleAuthPaths();
58
+ // Return the first existing path
59
+ for (const path of possiblePaths) {
60
+ if (existsSync(path)) {
61
+ return path;
62
+ }
63
+ }
64
+ // Return the default (first) path if none exist
65
+ return possiblePaths[0];
24
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/opencode-usage-plugin",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "opencode plugin for tracking AI provider usage, rate limits, and quotas",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",