@howaboua/opencode-usage-plugin 0.1.2 → 0.1.4-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
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 standard OpenCode credentials.
5
4
  */
6
5
  import { type CopilotAuthData } from "./types.js";
7
6
  export declare function readCopilotAuth(): Promise<CopilotAuthData | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/providers/copilot/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAevE"}
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,7 +1,6 @@
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 standard OpenCode credentials.
5
4
  */
6
5
  import { existsSync } from "fs";
7
6
  import { readFile } from "fs/promises";
@@ -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 the internal API.
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;AA8D/C,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,IAAI,CAuD/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,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 the internal API.
5
4
  */
6
5
  import { readCopilotAuth } from "./auth.js";
7
6
  import { toCopilotQuotaFromInternal, } from "./response.js";
@@ -21,7 +21,7 @@ export function toCopilotQuotaFromInternal(data) {
21
21
  percentRemaining: chatTotal > 0 ? Math.round((chatRemaining / chatTotal) * 100) : 0,
22
22
  resetTime: data.limited_user_reset_date || data.quota_reset_date,
23
23
  completionsUsed: completionsRemaining,
24
- completionsTotal: completionsTotal,
24
+ completionsTotal,
25
25
  };
26
26
  }
27
27
  if (data.quota_snapshots?.premium_interactions) {
@@ -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;AAyI5C,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;AAkM5C,eAAO,MAAM,aAAa,EAAE,aAAa,CAAC,IAAI,CAwB7C,CAAA"}
@@ -39,6 +39,49 @@ function normalizeTier(tier) {
39
39
  return "paid";
40
40
  return "free";
41
41
  }
42
+ function parseQuotaGroupsFromAggregation(quotaGroups) {
43
+ if (!quotaGroups)
44
+ return [];
45
+ const tiers = {
46
+ paid: new Map(),
47
+ free: new Map(),
48
+ };
49
+ for (const [groupName, groupData] of Object.entries(quotaGroups)) {
50
+ const mappedName = GROUP_MAPPING[groupName];
51
+ if (!mappedName)
52
+ continue;
53
+ const windows = groupData.windows || {};
54
+ const windowPriority = ["daily", "5h", "1h", "15m"];
55
+ let bestWindowName = null;
56
+ for (const windowName of windowPriority) {
57
+ if (windows[windowName]) {
58
+ bestWindowName = windowName;
59
+ break;
60
+ }
61
+ }
62
+ if (!bestWindowName && Object.keys(windows).length > 0) {
63
+ bestWindowName = Object.keys(windows)[0];
64
+ }
65
+ if (!bestWindowName)
66
+ continue;
67
+ const window = windows[bestWindowName];
68
+ // Since these are already aggregated by provider, we split by tier if possible.
69
+ // However, the aggregate API provides tier-agnostic totals.
70
+ // For now, we put them into "paid" as the default high-level view.
71
+ tiers.paid.set(mappedName, {
72
+ name: mappedName,
73
+ remaining: window.total_remaining,
74
+ max: window.total_max,
75
+ remainingPct: Math.round(window.remaining_pct),
76
+ resetTime: null, // Aggregated windows don't have a single reset_at
77
+ });
78
+ }
79
+ const result = [];
80
+ if (tiers.paid.size > 0) {
81
+ result.push({ tier: "paid", quotaGroups: sortQuotaGroups(Array.from(tiers.paid.values())) });
82
+ }
83
+ return result;
84
+ }
42
85
  function parseQuotaGroupsFromCredential(groupUsage) {
43
86
  if (!groupUsage)
44
87
  return [];
@@ -72,6 +115,11 @@ function parseQuotaGroupsFromCredential(groupUsage) {
72
115
  return Array.from(result.values());
73
116
  }
74
117
  function aggregateByProvider(provider) {
118
+ // Try aggregated quota groups first
119
+ if (provider.quota_groups && Object.keys(provider.quota_groups).length > 0) {
120
+ return parseQuotaGroupsFromAggregation(provider.quota_groups);
121
+ }
122
+ // Fallback to manual aggregation of credentials
75
123
  const tiers = {
76
124
  paid: new Map(),
77
125
  free: new Map(),
@@ -136,7 +184,7 @@ export const ProxyProvider = {
136
184
  const config = await loadUsageConfig();
137
185
  const data = await fetchProxyLimits(config);
138
186
  return {
139
- timestamp: data.timestamp * 1000,
187
+ timestamp: (data.timestamp || Date.now() / 1000) * 1000,
140
188
  provider: "proxy",
141
189
  planType: null,
142
190
  primary: null,
package/dist/types.d.ts CHANGED
@@ -63,6 +63,9 @@ export interface UsageSnapshot {
63
63
  proxyQuota?: ProxyQuota;
64
64
  copilotQuota?: CopilotQuota;
65
65
  updatedAt: number;
66
+ isMissing?: boolean;
67
+ missingReason?: string;
68
+ missingDetails?: string[];
66
69
  }
67
70
  export interface UsageEntry {
68
71
  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":"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"}
@@ -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,CAqChB;AAyID,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;AAkMD,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,6 +1,8 @@
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
7
  // @ts-ignore
6
8
  const bus = options.client.bus;
@@ -82,21 +84,28 @@ function formatResetSuffixISO(isoString) {
82
84
  }
83
85
  function formatProxySnapshot(snapshot) {
84
86
  const proxy = snapshot.proxyQuota;
85
- if (!proxy)
87
+ if (!proxy || !proxy.providers || proxy.providers.length === 0)
86
88
  return ["→ [proxy] No data"];
87
89
  const lines = ["→ [Google] Mirrowel Proxy"];
88
90
  for (const provider of proxy.providers) {
89
- lines.push("");
90
- lines.push(` ${provider.name}:`);
91
+ const providerLines = [];
91
92
  for (const tierInfo of provider.tiers) {
93
+ if (!tierInfo.quotaGroups || tierInfo.quotaGroups.length === 0)
94
+ continue;
92
95
  const tierLabel = tierInfo.tier === "paid" ? "Paid" : "Free";
93
- lines.push(` ${tierLabel}:`);
96
+ providerLines.push(` ${tierLabel}:`);
94
97
  for (const group of tierInfo.quotaGroups) {
95
98
  const resetSuffix = group.resetTime ? formatResetSuffixISO(group.resetTime) : "";
96
99
  const label = `${group.name}:`.padEnd(9);
97
- lines.push(` ${label} ${formatBar(group.remainingPct)} ${group.remaining}/${group.max}${resetSuffix}`);
100
+ providerLines.push(` ${label} ${formatBar(group.remainingPct)} ${group.remaining}/${group.max}${resetSuffix}`);
98
101
  }
99
102
  }
103
+ if (providerLines.length > 0) {
104
+ lines.push("");
105
+ lines.push(` ${provider.name}:`);
106
+ for (const line of providerLines)
107
+ lines.push(line);
108
+ }
100
109
  }
101
110
  return lines;
102
111
  }
@@ -120,7 +129,47 @@ function formatCopilotSnapshot(snapshot) {
120
129
  }
121
130
  return lines;
122
131
  }
132
+ function getAppDataPath() {
133
+ const home = homedir();
134
+ const plat = platform();
135
+ if (plat === "darwin")
136
+ return join(home, ".config", "opencode");
137
+ if (plat === "win32")
138
+ return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode");
139
+ return join(process.env.XDG_CONFIG_HOME || join(home, ".config"), "opencode");
140
+ }
141
+ function formatMissingSnapshot(snapshot) {
142
+ const provider = snapshot.provider;
143
+ const configPath = join(getAppDataPath(), "usage-config.jsonc");
144
+ let providerInstruction = "";
145
+ if (provider === "codex") {
146
+ providerInstruction = "if you dont have codex oauth, please set your usage-config.jsonc to openai: false";
147
+ }
148
+ else if (provider === "proxy") {
149
+ providerInstruction = "if you are not running Mirrowel's proxy, please set your usage-config.jsonc to proxy: false";
150
+ }
151
+ else if (provider === "copilot") {
152
+ providerInstruction = "if you are not running GitHub Copilot, please set your usage-config.jsonc to copilot: false";
153
+ }
154
+ const lines = [
155
+ `→ [${provider.toUpperCase()}] - ${providerInstruction}`,
156
+ ];
157
+ if (snapshot.missingReason) {
158
+ lines.push("", `Reason: ${snapshot.missingReason}`);
159
+ }
160
+ if (snapshot.missingDetails && snapshot.missingDetails.length > 0) {
161
+ lines.push("", "Details:");
162
+ for (const detail of snapshot.missingDetails) {
163
+ lines.push(`- ${detail}`);
164
+ }
165
+ }
166
+ lines.push("", `The file can be found in ${configPath}. Please read the comments in the file or visit the repo for the readme.`, "", "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!");
167
+ return lines;
168
+ }
123
169
  function formatSnapshot(snapshot) {
170
+ if (snapshot.isMissing) {
171
+ return formatMissingSnapshot(snapshot);
172
+ }
124
173
  if (snapshot.provider === "proxy" && snapshot.proxyQuota) {
125
174
  return formatProxySnapshot(snapshot);
126
175
  }
@@ -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,CA6C5D"}
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,15 +2,22 @@
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
- {
20
+ const content = `{
14
21
  "endpoint": "",
15
22
  "apiKey": "",
16
23
  "timeout": 10000,
@@ -8,4 +8,8 @@ export declare const providerAliases: Record<string, string>;
8
8
  export declare function resolveProviderFilter(filter?: string): string | undefined;
9
9
  export declare function fetchUsageSnapshots(filter?: string): Promise<UsageSnapshot[]>;
10
10
  export declare function loadAuths(): Promise<AuthRecord>;
11
+ export declare function loadAuthsWithDiagnostics(): Promise<{
12
+ auths: AuthRecord;
13
+ codexDiagnostics: string[];
14
+ }>;
11
15
  //# sourceMappingURL=fetch.d.ts.map
@@ -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,CA2CnF;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;AAgB5C,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,CA+EnF;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CAGrD;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC;IACxD,KAAK,EAAE,UAAU,CAAA;IACjB,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B,CAAC,CAsED"}
@@ -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({
@@ -14,6 +14,7 @@ const authEntrySchema = z
14
14
  refresh: z.string().optional(),
15
15
  enterpriseUrl: z.string().optional(),
16
16
  accountId: z.string().optional(),
17
+ key: z.string().optional(),
17
18
  })
18
19
  .passthrough();
19
20
  const authRecordSchema = z.record(z.string(), authEntrySchema);
@@ -48,9 +49,11 @@ export async function fetchUsageSnapshots(filter) {
48
49
  return providerToggles.copilot !== false;
49
50
  return true;
50
51
  };
51
- const auths = await loadAuths();
52
+ const { auths, codexDiagnostics } = await loadAuthsWithDiagnostics();
52
53
  const entries = resolveProviderAuths(auths, null);
53
54
  const snapshots = [];
55
+ const coreProviders = ["codex", "proxy", "copilot"];
56
+ const fetchedProviders = new Set();
54
57
  const fetches = entries
55
58
  .filter((entry) => !targetProvider || entry.providerID === targetProvider)
56
59
  .filter((entry) => isProviderEnabled(entry.providerID))
@@ -58,39 +61,154 @@ export async function fetchUsageSnapshots(filter) {
58
61
  const provider = providers[entry.providerID];
59
62
  if (!provider?.fetchUsage)
60
63
  return;
61
- const snapshot = await provider.fetchUsage(entry.auth).catch(() => null);
62
- if (snapshot)
63
- snapshots.push(snapshot);
64
+ try {
65
+ const snapshot = await provider.fetchUsage(entry.auth);
66
+ if (snapshot) {
67
+ snapshots.push(snapshot);
68
+ fetchedProviders.add(entry.providerID);
69
+ }
70
+ }
71
+ catch {
72
+ }
64
73
  });
65
74
  const specialProviders = ["proxy", "copilot"];
66
75
  for (const id of specialProviders) {
67
- if ((!targetProvider || targetProvider === id) && isProviderEnabled(id)) {
76
+ if ((!targetProvider || targetProvider === id) && isProviderEnabled(id) && !fetchedProviders.has(id)) {
68
77
  const provider = providers[id];
69
78
  if (provider?.fetchUsage) {
70
79
  fetches.push(provider
71
80
  .fetchUsage(undefined)
72
81
  .then((snapshot) => {
73
- if (snapshot)
82
+ if (snapshot) {
74
83
  snapshots.push(snapshot);
84
+ fetchedProviders.add(id);
85
+ }
75
86
  })
76
87
  .catch(() => { }));
77
88
  }
78
89
  }
79
90
  }
80
91
  await Promise.race([Promise.all(fetches), timeout(5000)]);
92
+ for (const id of coreProviders) {
93
+ if (isProviderEnabled(id) && !fetchedProviders.has(id)) {
94
+ if (!targetProvider || targetProvider === id) {
95
+ const codexDetails = id === "codex" && codexDiagnostics.length > 0 ? codexDiagnostics : undefined;
96
+ snapshots.push({
97
+ timestamp: Date.now(),
98
+ provider: id,
99
+ planType: null,
100
+ primary: null,
101
+ secondary: null,
102
+ codeReview: null,
103
+ credits: null,
104
+ updatedAt: Date.now(),
105
+ isMissing: true,
106
+ missingReason: codexDetails ? "Codex auth was not resolved from auth.json." : undefined,
107
+ missingDetails: codexDetails,
108
+ });
109
+ }
110
+ }
111
+ }
81
112
  return snapshots;
82
113
  }
83
114
  export async function loadAuths() {
84
- const authPath = getAuthFilePath();
85
- const data = await Bun.file(authPath)
86
- .json()
87
- .catch(() => ({}));
88
- if (!data || typeof data !== "object")
89
- return {};
90
- const parsed = authRecordSchema.safeParse(data);
91
- if (!parsed.success)
92
- return {};
93
- return parsed.data;
115
+ const { auths } = await loadAuthsWithDiagnostics();
116
+ return auths;
117
+ }
118
+ export async function loadAuthsWithDiagnostics() {
119
+ const possiblePaths = getPossibleAuthPaths();
120
+ const mergedAuth = {};
121
+ const codexDiagnostics = [];
122
+ const orderedPaths = [...possiblePaths].reverse();
123
+ codexDiagnostics.push(`Auth paths checked: ${possiblePaths.join(", ")}`);
124
+ for (const authPath of orderedPaths) {
125
+ try {
126
+ const file = Bun.file(authPath);
127
+ const exists = await file.exists();
128
+ if (!exists) {
129
+ codexDiagnostics.push(`Missing auth file: ${authPath}`);
130
+ continue;
131
+ }
132
+ const data = await file.json();
133
+ if (!isRecord(data)) {
134
+ codexDiagnostics.push(`Auth file is not a JSON object: ${authPath}`);
135
+ continue;
136
+ }
137
+ if (authPath.includes(".codex")) {
138
+ const parsed = parseCodexAuth(data, authPath);
139
+ codexDiagnostics.push(...parsed.diagnostics);
140
+ if (parsed.auth)
141
+ Object.assign(mergedAuth, parsed.auth);
142
+ continue;
143
+ }
144
+ const parsed = authRecordSchema.safeParse(data);
145
+ if (!parsed.success) {
146
+ codexDiagnostics.push(`Auth file failed schema validation: ${authPath}`);
147
+ continue;
148
+ }
149
+ Object.assign(mergedAuth, parsed.data);
150
+ const openaiEntry = parsed.data.openai ?? parsed.data.codex;
151
+ if (!openaiEntry) {
152
+ codexDiagnostics.push(`No "openai" or "codex" entry in auth file: ${authPath}`);
153
+ continue;
154
+ }
155
+ codexDiagnostics.push(`Found OpenAI/Codex entry in auth file: ${authPath}`);
156
+ if (openaiEntry.type && openaiEntry.type !== "oauth" && openaiEntry.type !== "token") {
157
+ codexDiagnostics.push(`OpenAI/Codex entry has unsupported type "${openaiEntry.type}" in ${authPath}`);
158
+ }
159
+ if (!openaiEntry.access && !openaiEntry.key) {
160
+ codexDiagnostics.push(`OpenAI/Codex entry missing "access" or "key" in ${authPath}`);
161
+ }
162
+ }
163
+ catch (error) {
164
+ const message = error instanceof Error ? error.message : String(error);
165
+ codexDiagnostics.push(`Failed to read auth file ${authPath}: ${message}`);
166
+ continue;
167
+ }
168
+ }
169
+ const mergedEntry = mergedAuth.openai ?? mergedAuth.codex;
170
+ if (!mergedEntry) {
171
+ codexDiagnostics.push("Merged auth record has no OpenAI/Codex entry.");
172
+ }
173
+ else {
174
+ if (mergedEntry.type && mergedEntry.type !== "oauth" && mergedEntry.type !== "token") {
175
+ codexDiagnostics.push(`Merged OpenAI/Codex entry has unsupported type "${mergedEntry.type}".`);
176
+ }
177
+ if (!mergedEntry.access && !mergedEntry.key) {
178
+ codexDiagnostics.push("Merged OpenAI/Codex entry missing access or key.");
179
+ }
180
+ }
181
+ return { auths: mergedAuth, codexDiagnostics };
182
+ }
183
+ function parseCodexAuth(data, authPath) {
184
+ const diagnostics = [];
185
+ const tokens = data.tokens;
186
+ if (!isRecord(tokens)) {
187
+ diagnostics.push(`Codex auth missing "tokens" object in ${authPath}`);
188
+ return { auth: null, diagnostics };
189
+ }
190
+ const accessToken = tokens.access_token;
191
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
192
+ diagnostics.push(`Codex auth missing tokens.access_token in ${authPath}`);
193
+ return { auth: null, diagnostics };
194
+ }
195
+ const accountId = tokens.account_id;
196
+ const refreshToken = tokens.refresh_token;
197
+ diagnostics.push(`Codex CLI auth loaded from ${authPath}`);
198
+ return {
199
+ auth: {
200
+ openai: {
201
+ type: "oauth",
202
+ access: accessToken,
203
+ accountId: typeof accountId === "string" ? accountId : undefined,
204
+ refresh: typeof refreshToken === "string" ? refreshToken : undefined,
205
+ },
206
+ },
207
+ diagnostics,
208
+ };
209
+ }
210
+ function isRecord(value) {
211
+ return typeof value === "object" && value !== null;
94
212
  }
95
213
  function timeout(ms) {
96
214
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -2,17 +2,22 @@
2
2
  * Resolves provider auth entries for usage snapshots.
3
3
  */
4
4
  import type { CodexAuth } from "../providers/codex";
5
+ import type { CopilotAuthData } from "../providers/copilot/types";
5
6
  export type AuthEntry = {
6
7
  type?: string;
7
8
  access?: string;
8
9
  refresh?: string;
9
10
  enterpriseUrl?: string;
10
11
  accountId?: string;
12
+ key?: string;
11
13
  };
12
14
  export type AuthRecord = Record<string, AuthEntry>;
13
15
  type ProviderAuthEntry = {
14
16
  providerID: "codex";
15
17
  auth: CodexAuth;
18
+ } | {
19
+ providerID: "copilot";
20
+ auth: CopilotAuthData;
16
21
  };
17
22
  export declare function resolveProviderAuths(auths: AuthRecord, usageToken: string | null): ProviderAuthEntry[];
18
23
  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;AAEnD,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;CACnB,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,CAAA;AAqB5C,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;AAEjE,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,CAAA;AA8BpD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,EAAE,CActG"}
@@ -7,10 +7,19 @@ const providerDescriptors = [
7
7
  authKeys: ["codex", "openai"],
8
8
  requiresOAuth: true,
9
9
  buildAuth: (entry) => ({
10
- access: entry.access,
10
+ access: entry.access || entry.key,
11
11
  accountId: entry.accountId,
12
12
  }),
13
13
  },
14
+ {
15
+ id: "copilot",
16
+ authKeys: ["copilot", "github-copilot"],
17
+ requiresOAuth: true,
18
+ buildAuth: (entry) => ({
19
+ access: entry.access,
20
+ refresh: entry.refresh,
21
+ }),
22
+ },
14
23
  ];
15
24
  export function resolveProviderAuths(auths, usageToken) {
16
25
  const entries = [];
@@ -21,7 +30,7 @@ export function resolveProviderAuths(auths, usageToken) {
21
30
  const auth = auths[matched];
22
31
  if (!auth)
23
32
  continue;
24
- if (descriptor.requiresOAuth && auth.type && auth.type !== "oauth")
33
+ if (descriptor.requiresOAuth && auth.type && auth.type !== "oauth" && auth.type !== "token")
25
34
  continue;
26
35
  const built = descriptor.buildAuth(auth, usageToken);
27
36
  entries.push({ providerID: descriptor.id, auth: built });
@@ -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,CAUvC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAe/C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAYxC"}
@@ -4,21 +4,46 @@
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();
10
- if (plat === "darwin") {
11
- return join(home, "Library", "Application Support", "opencode");
12
- }
13
11
  if (plat === "win32") {
14
12
  return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode");
15
13
  }
16
- const xdgData = process.env.XDG_DATA_HOME;
17
- if (xdgData) {
18
- return join(xdgData, "opencode");
14
+ const dataHome = process.env.XDG_DATA_HOME || join(home, ".local", "share");
15
+ return join(dataHome, "opencode");
16
+ }
17
+ /**
18
+ * Returns all possible auth file paths for the current platform.
19
+ * On macOS, OpenCode uses Linux-style paths, so we check both.
20
+ */
21
+ export function getPossibleAuthPaths() {
22
+ const plat = platform();
23
+ const home = homedir();
24
+ const pathSet = new Set();
25
+ if (plat === "win32") {
26
+ pathSet.add(join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode", "auth.json"));
27
+ }
28
+ else {
29
+ // Linux/other
30
+ const dataHome = process.env.XDG_DATA_HOME || join(home, ".local", "share");
31
+ pathSet.add(join(dataHome, "opencode", "auth.json"));
32
+ pathSet.add(join(home, ".codex", "auth.json"));
19
33
  }
20
- return join(home, ".local", "share", "opencode");
34
+ return Array.from(pathSet);
21
35
  }
36
+ /**
37
+ * Returns the first existing auth file path, or the default location if none exist.
38
+ */
22
39
  export function getAuthFilePath() {
23
- return join(getAppDataPath(), "auth.json");
40
+ const possiblePaths = getPossibleAuthPaths();
41
+ // Return the first existing path
42
+ for (const path of possiblePaths) {
43
+ if (existsSync(path)) {
44
+ return path;
45
+ }
46
+ }
47
+ // Return the default (first) path if none exist
48
+ return possiblePaths[0];
24
49
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/opencode-usage-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.1.4-dev.0",
4
4
  "description": "opencode plugin for tracking AI provider usage, rate limits, and quotas",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,7 +31,9 @@
31
31
  "scripts": {
32
32
  "clean": "rm -rf dist",
33
33
  "build": "npm run clean && tsc",
34
- "prepublishOnly": "npm run build"
34
+ "prepublishOnly": "npm run build",
35
+ "publish:dev": "npm publish --tag dev",
36
+ "release:dev": "npm version prerelease --preid dev && npm publish --tag dev"
35
37
  },
36
38
  "publishConfig": {
37
39
  "access": "public"