@howaboua/opencode-usage-plugin 0.1.3 → 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 +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,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;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(),
package/dist/types.d.ts CHANGED
@@ -64,6 +64,8 @@ export interface UsageSnapshot {
64
64
  copilotQuota?: CopilotQuota;
65
65
  updatedAt: number;
66
66
  isMissing?: boolean;
67
+ missingReason?: string;
68
+ missingDetails?: string[];
67
69
  }
68
70
  export interface UsageEntry {
69
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;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"}
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
@@ -84,21 +84,28 @@ function formatResetSuffixISO(isoString) {
84
84
  }
85
85
  function formatProxySnapshot(snapshot) {
86
86
  const proxy = snapshot.proxyQuota;
87
- if (!proxy)
87
+ if (!proxy || !proxy.providers || proxy.providers.length === 0)
88
88
  return ["→ [proxy] No data"];
89
89
  const lines = ["→ [Google] Mirrowel Proxy"];
90
90
  for (const provider of proxy.providers) {
91
- lines.push("");
92
- lines.push(` ${provider.name}:`);
91
+ const providerLines = [];
93
92
  for (const tierInfo of provider.tiers) {
93
+ if (!tierInfo.quotaGroups || tierInfo.quotaGroups.length === 0)
94
+ continue;
94
95
  const tierLabel = tierInfo.tier === "paid" ? "Paid" : "Free";
95
- lines.push(` ${tierLabel}:`);
96
+ providerLines.push(` ${tierLabel}:`);
96
97
  for (const group of tierInfo.quotaGroups) {
97
98
  const resetSuffix = group.resetTime ? formatResetSuffixISO(group.resetTime) : "";
98
99
  const label = `${group.name}:`.padEnd(9);
99
- lines.push(` ${label} ${formatBar(group.remainingPct)} ${group.remaining}/${group.max}${resetSuffix}`);
100
+ providerLines.push(` ${label} ${formatBar(group.remainingPct)} ${group.remaining}/${group.max}${resetSuffix}`);
100
101
  }
101
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
+ }
102
109
  }
103
110
  return lines;
104
111
  }
@@ -133,7 +140,6 @@ function getAppDataPath() {
133
140
  }
134
141
  function formatMissingSnapshot(snapshot) {
135
142
  const provider = snapshot.provider;
136
- const label = provider === "codex" ? "codex" : provider === "proxy" ? "proxy" : "gh";
137
143
  const configPath = join(getAppDataPath(), "usage-config.jsonc");
138
144
  let providerInstruction = "";
139
145
  if (provider === "codex") {
@@ -145,13 +151,20 @@ function formatMissingSnapshot(snapshot) {
145
151
  else if (provider === "copilot") {
146
152
  providerInstruction = "if you are not running GitHub Copilot, please set your usage-config.jsonc to copilot: false";
147
153
  }
148
- return [
154
+ const lines = [
149
155
  `→ [${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
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;
155
168
  }
156
169
  function formatSnapshot(snapshot) {
157
170
  if (snapshot.isMissing) {
@@ -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,CA4EnF;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,CA8BrD"}
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"}
@@ -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,7 +49,7 @@ 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 = [];
54
55
  const coreProviders = ["codex", "proxy", "copilot"];
@@ -72,7 +73,7 @@ export async function fetchUsageSnapshots(filter) {
72
73
  });
73
74
  const specialProviders = ["proxy", "copilot"];
74
75
  for (const id of specialProviders) {
75
- if ((!targetProvider || targetProvider === id) && isProviderEnabled(id)) {
76
+ if ((!targetProvider || targetProvider === id) && isProviderEnabled(id) && !fetchedProviders.has(id)) {
76
77
  const provider = providers[id];
77
78
  if (provider?.fetchUsage) {
78
79
  fetches.push(provider
@@ -91,6 +92,7 @@ export async function fetchUsageSnapshots(filter) {
91
92
  for (const id of coreProviders) {
92
93
  if (isProviderEnabled(id) && !fetchedProviders.has(id)) {
93
94
  if (!targetProvider || targetProvider === id) {
95
+ const codexDetails = id === "codex" && codexDiagnostics.length > 0 ? codexDiagnostics : undefined;
94
96
  snapshots.push({
95
97
  timestamp: Date.now(),
96
98
  provider: id,
@@ -101,6 +103,8 @@ export async function fetchUsageSnapshots(filter) {
101
103
  credits: null,
102
104
  updatedAt: Date.now(),
103
105
  isMissing: true,
106
+ missingReason: codexDetails ? "Codex auth was not resolved from auth.json." : undefined,
107
+ missingDetails: codexDetails,
104
108
  });
105
109
  }
106
110
  }
@@ -108,55 +112,104 @@ export async function fetchUsageSnapshots(filter) {
108
112
  return snapshots;
109
113
  }
110
114
  export async function loadAuths() {
115
+ const { auths } = await loadAuthsWithDiagnostics();
116
+ return auths;
117
+ }
118
+ export async function loadAuthsWithDiagnostics() {
111
119
  const possiblePaths = getPossibleAuthPaths();
112
120
  const mergedAuth = {};
113
- for (const authPath of possiblePaths.reverse()) {
121
+ const codexDiagnostics = [];
122
+ const orderedPaths = [...possiblePaths].reverse();
123
+ codexDiagnostics.push(`Auth paths checked: ${possiblePaths.join(", ")}`);
124
+ for (const authPath of orderedPaths) {
114
125
  try {
115
126
  const file = Bun.file(authPath);
116
- if (!(await file.exists()))
127
+ const exists = await file.exists();
128
+ if (!exists) {
129
+ codexDiagnostics.push(`Missing auth file: ${authPath}`);
117
130
  continue;
131
+ }
118
132
  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
- }
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}`);
131
161
  }
132
162
  }
133
- catch {
163
+ catch (error) {
164
+ const message = error instanceof Error ? error.message : String(error);
165
+ codexDiagnostics.push(`Failed to read auth file ${authPath}: ${message}`);
134
166
  continue;
135
167
  }
136
168
  }
137
- return mergedAuth;
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 };
138
182
  }
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);
148
- if (!parsed.success)
149
- return null;
150
- const { access_token, account_id, refresh_token } = parsed.data.tokens;
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}`);
151
198
  return {
152
- openai: {
153
- type: "oauth",
154
- access: access_token,
155
- accountId: account_id,
156
- refresh: refresh_token,
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
+ },
157
206
  },
207
+ diagnostics,
158
208
  };
159
209
  }
210
+ function isRecord(value) {
211
+ return typeof value === "object" && value !== null;
212
+ }
160
213
  function timeout(ms) {
161
214
  return new Promise((resolve) => setTimeout(resolve, ms));
162
215
  }
@@ -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 +1 @@
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"}
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"}
@@ -8,17 +8,11 @@ import { existsSync } from "fs";
8
8
  export function getAppDataPath() {
9
9
  const plat = platform();
10
10
  const home = homedir();
11
- if (plat === "darwin") {
12
- return join(home, "Library", "Application Support", "opencode");
13
- }
14
11
  if (plat === "win32") {
15
12
  return join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode");
16
13
  }
17
- const xdgData = process.env.XDG_DATA_HOME;
18
- if (xdgData) {
19
- return join(xdgData, "opencode");
20
- }
21
- return join(home, ".local", "share", "opencode");
14
+ const dataHome = process.env.XDG_DATA_HOME || join(home, ".local", "share");
15
+ return join(dataHome, "opencode");
22
16
  }
23
17
  /**
24
18
  * Returns all possible auth file paths for the current platform.
@@ -28,24 +22,13 @@ export function getPossibleAuthPaths() {
28
22
  const plat = platform();
29
23
  const home = homedir();
30
24
  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") {
25
+ if (plat === "win32") {
40
26
  pathSet.add(join(process.env.APPDATA || join(home, "AppData", "Roaming"), "opencode", "auth.json"));
41
27
  }
42
28
  else {
43
29
  // 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"));
30
+ const dataHome = process.env.XDG_DATA_HOME || join(home, ".local", "share");
31
+ pathSet.add(join(dataHome, "opencode", "auth.json"));
49
32
  pathSet.add(join(home, ".codex", "auth.json"));
50
33
  }
51
34
  return Array.from(pathSet);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@howaboua/opencode-usage-plugin",
3
- "version": "0.1.3",
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"