@slkiser/opencode-quota 1.0.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.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/dist/data/modelsdev-pricing.min.json +504 -0
  4. package/dist/index.d.ts +10 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +15 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/lib/cache.d.ts +49 -0
  9. package/dist/lib/cache.d.ts.map +1 -0
  10. package/dist/lib/cache.js +127 -0
  11. package/dist/lib/cache.js.map +1 -0
  12. package/dist/lib/config.d.ts +30 -0
  13. package/dist/lib/config.d.ts.map +1 -0
  14. package/dist/lib/config.js +154 -0
  15. package/dist/lib/config.js.map +1 -0
  16. package/dist/lib/copilot.d.ts +25 -0
  17. package/dist/lib/copilot.d.ts.map +1 -0
  18. package/dist/lib/copilot.js +306 -0
  19. package/dist/lib/copilot.js.map +1 -0
  20. package/dist/lib/entries.d.ts +60 -0
  21. package/dist/lib/entries.d.ts.map +1 -0
  22. package/dist/lib/entries.js +8 -0
  23. package/dist/lib/entries.js.map +1 -0
  24. package/dist/lib/firmware.d.ts +15 -0
  25. package/dist/lib/firmware.d.ts.map +1 -0
  26. package/dist/lib/firmware.js +79 -0
  27. package/dist/lib/firmware.js.map +1 -0
  28. package/dist/lib/format.d.ts +16 -0
  29. package/dist/lib/format.d.ts.map +1 -0
  30. package/dist/lib/format.js +99 -0
  31. package/dist/lib/format.js.map +1 -0
  32. package/dist/lib/google-token-cache.d.ts +30 -0
  33. package/dist/lib/google-token-cache.d.ts.map +1 -0
  34. package/dist/lib/google-token-cache.js +100 -0
  35. package/dist/lib/google-token-cache.js.map +1 -0
  36. package/dist/lib/google.d.ts +43 -0
  37. package/dist/lib/google.d.ts.map +1 -0
  38. package/dist/lib/google.js +399 -0
  39. package/dist/lib/google.js.map +1 -0
  40. package/dist/lib/markdown-table.d.ts +8 -0
  41. package/dist/lib/markdown-table.d.ts.map +1 -0
  42. package/dist/lib/markdown-table.js +56 -0
  43. package/dist/lib/markdown-table.js.map +1 -0
  44. package/dist/lib/modelsdev-pricing.d.ts +23 -0
  45. package/dist/lib/modelsdev-pricing.d.ts.map +1 -0
  46. package/dist/lib/modelsdev-pricing.js +32 -0
  47. package/dist/lib/modelsdev-pricing.js.map +1 -0
  48. package/dist/lib/openai.d.ts +33 -0
  49. package/dist/lib/openai.d.ts.map +1 -0
  50. package/dist/lib/openai.js +162 -0
  51. package/dist/lib/openai.js.map +1 -0
  52. package/dist/lib/opencode-auth.d.ts +11 -0
  53. package/dist/lib/opencode-auth.d.ts.map +1 -0
  54. package/dist/lib/opencode-auth.js +27 -0
  55. package/dist/lib/opencode-auth.js.map +1 -0
  56. package/dist/lib/opencode-storage.d.ts +45 -0
  57. package/dist/lib/opencode-storage.d.ts.map +1 -0
  58. package/dist/lib/opencode-storage.js +120 -0
  59. package/dist/lib/opencode-storage.js.map +1 -0
  60. package/dist/lib/quota-command-format.d.ts +14 -0
  61. package/dist/lib/quota-command-format.d.ts.map +1 -0
  62. package/dist/lib/quota-command-format.js +122 -0
  63. package/dist/lib/quota-command-format.js.map +1 -0
  64. package/dist/lib/quota-stats-format.d.ts +9 -0
  65. package/dist/lib/quota-stats-format.d.ts.map +1 -0
  66. package/dist/lib/quota-stats-format.js +244 -0
  67. package/dist/lib/quota-stats-format.js.map +1 -0
  68. package/dist/lib/quota-stats.d.ts +71 -0
  69. package/dist/lib/quota-stats.d.ts.map +1 -0
  70. package/dist/lib/quota-stats.js +270 -0
  71. package/dist/lib/quota-stats.js.map +1 -0
  72. package/dist/lib/quota-status.d.ts +23 -0
  73. package/dist/lib/quota-status.d.ts.map +1 -0
  74. package/dist/lib/quota-status.js +112 -0
  75. package/dist/lib/quota-status.js.map +1 -0
  76. package/dist/lib/toast-format-grouped.d.ts +25 -0
  77. package/dist/lib/toast-format-grouped.d.ts.map +1 -0
  78. package/dist/lib/toast-format-grouped.js +123 -0
  79. package/dist/lib/toast-format-grouped.js.map +1 -0
  80. package/dist/lib/types.d.ts +218 -0
  81. package/dist/lib/types.d.ts.map +1 -0
  82. package/dist/lib/types.js +39 -0
  83. package/dist/lib/types.js.map +1 -0
  84. package/dist/plugin.d.ts +13 -0
  85. package/dist/plugin.d.ts.map +1 -0
  86. package/dist/plugin.js +633 -0
  87. package/dist/plugin.js.map +1 -0
  88. package/dist/providers/copilot.d.ts +8 -0
  89. package/dist/providers/copilot.d.ts.map +1 -0
  90. package/dist/providers/copilot.js +50 -0
  91. package/dist/providers/copilot.js.map +1 -0
  92. package/dist/providers/firmware.d.ts +6 -0
  93. package/dist/providers/firmware.d.ts.map +1 -0
  94. package/dist/providers/firmware.js +52 -0
  95. package/dist/providers/firmware.js.map +1 -0
  96. package/dist/providers/google-antigravity.d.ts +6 -0
  97. package/dist/providers/google-antigravity.d.ts.map +1 -0
  98. package/dist/providers/google-antigravity.js +77 -0
  99. package/dist/providers/google-antigravity.js.map +1 -0
  100. package/dist/providers/openai.d.ts +6 -0
  101. package/dist/providers/openai.d.ts.map +1 -0
  102. package/dist/providers/openai.js +106 -0
  103. package/dist/providers/openai.js.map +1 -0
  104. package/dist/providers/registry.d.ts +8 -0
  105. package/dist/providers/registry.d.ts.map +1 -0
  106. package/dist/providers/registry.js +14 -0
  107. package/dist/providers/registry.js.map +1 -0
  108. package/package.json +71 -0
@@ -0,0 +1,306 @@
1
+ /**
2
+ * GitHub Copilot quota fetcher
3
+ *
4
+ * Strategy (new Copilot API reality):
5
+ *
6
+ * 1) Preferred: GitHub public billing API using a fine-grained PAT
7
+ * configured in ~/.config/opencode/copilot-quota-token.json.
8
+ * 2) Best-effort: internal endpoint using OpenCode's stored OAuth token
9
+ * (legacy formats or via token exchange).
10
+ */
11
+ import { REQUEST_TIMEOUT_MS } from "./types.js";
12
+ import { readAuthFile } from "./opencode-auth.js";
13
+ import { existsSync, readFileSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { join } from "path";
16
+ // =============================================================================
17
+ // Constants
18
+ // =============================================================================
19
+ const GITHUB_API_BASE_URL = "https://api.github.com";
20
+ const COPILOT_INTERNAL_USER_URL = `${GITHUB_API_BASE_URL}/copilot_internal/user`;
21
+ const COPILOT_TOKEN_EXCHANGE_URL = `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`;
22
+ // Keep these aligned with current Copilot/VSC versions to avoid API heuristics.
23
+ const COPILOT_VERSION = "0.35.0";
24
+ const EDITOR_VERSION = "vscode/1.107.0";
25
+ const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}`;
26
+ const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}`;
27
+ const COPILOT_QUOTA_CONFIG_PATH = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode", "copilot-quota-token.json");
28
+ // =============================================================================
29
+ // Helpers
30
+ // =============================================================================
31
+ /**
32
+ * Fetch with timeout
33
+ */
34
+ async function fetchWithTimeout(url, options, timeoutMs = REQUEST_TIMEOUT_MS) {
35
+ const controller = new AbortController();
36
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
37
+ try {
38
+ const response = await fetch(url, {
39
+ ...options,
40
+ signal: controller.signal,
41
+ });
42
+ return response;
43
+ }
44
+ catch (err) {
45
+ if (err instanceof Error && err.name === "AbortError") {
46
+ throw new Error(`Request timeout after ${Math.round(timeoutMs / 1000)}s`);
47
+ }
48
+ throw err;
49
+ }
50
+ finally {
51
+ clearTimeout(timeoutId);
52
+ }
53
+ }
54
+ /**
55
+ * Build headers for GitHub API requests
56
+ */
57
+ const COPILOT_HEADERS = {
58
+ "User-Agent": USER_AGENT,
59
+ "Editor-Version": EDITOR_VERSION,
60
+ "Editor-Plugin-Version": EDITOR_PLUGIN_VERSION,
61
+ "Copilot-Integration-Id": "vscode-chat",
62
+ };
63
+ function buildBearerHeaders(token) {
64
+ return {
65
+ Accept: "application/json",
66
+ Authorization: `Bearer ${token}`,
67
+ ...COPILOT_HEADERS,
68
+ };
69
+ }
70
+ function buildLegacyTokenHeaders(token) {
71
+ return {
72
+ Accept: "application/json",
73
+ Authorization: `token ${token}`,
74
+ ...COPILOT_HEADERS,
75
+ };
76
+ }
77
+ /**
78
+ * Read Copilot auth data from auth.json
79
+ */
80
+ async function readCopilotAuth() {
81
+ const authData = await readAuthFile();
82
+ const copilotAuth = authData?.["github-copilot"];
83
+ if (!copilotAuth || copilotAuth.type !== "oauth" || !copilotAuth.refresh) {
84
+ return null;
85
+ }
86
+ return copilotAuth;
87
+ }
88
+ /**
89
+ * Read optional Copilot quota config from user's config file.
90
+ * Returns null if file doesn't exist or is invalid.
91
+ */
92
+ function readQuotaConfig() {
93
+ try {
94
+ if (!existsSync(COPILOT_QUOTA_CONFIG_PATH)) {
95
+ return null;
96
+ }
97
+ const content = readFileSync(COPILOT_QUOTA_CONFIG_PATH, "utf-8");
98
+ const parsed = JSON.parse(content);
99
+ if (!parsed || typeof parsed !== "object")
100
+ return null;
101
+ if (!parsed.token || !parsed.username || !parsed.tier)
102
+ return null;
103
+ const validTiers = ["free", "pro", "pro+", "business", "enterprise"];
104
+ if (!validTiers.includes(parsed.tier))
105
+ return null;
106
+ return parsed;
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ }
112
+ const COPILOT_PLAN_LIMITS = {
113
+ free: 50,
114
+ pro: 300,
115
+ "pro+": 1500,
116
+ business: 300,
117
+ enterprise: 1000,
118
+ };
119
+ function getApproxNextResetIso(nowMs = Date.now()) {
120
+ const now = new Date(nowMs);
121
+ const year = now.getUTCFullYear();
122
+ const month = now.getUTCMonth();
123
+ return new Date(Date.UTC(year, month + 1, 1, 0, 0, 0, 0)).toISOString();
124
+ }
125
+ async function fetchPublicBillingUsage(config) {
126
+ const response = await fetchWithTimeout(`${GITHUB_API_BASE_URL}/users/${config.username}/settings/billing/premium_request/usage`, {
127
+ headers: {
128
+ Accept: "application/vnd.github+json",
129
+ Authorization: `Bearer ${config.token}`,
130
+ "X-GitHub-Api-Version": "2022-11-28",
131
+ },
132
+ });
133
+ if (!response.ok) {
134
+ const errorText = await response.text();
135
+ throw new Error(`GitHub API error ${response.status}: ${errorText.slice(0, 160)}`);
136
+ }
137
+ return response.json();
138
+ }
139
+ function toQuotaResultFromBilling(data, tier) {
140
+ const items = Array.isArray(data.usageItems) ? data.usageItems : [];
141
+ const premiumItems = items.filter((item) => item &&
142
+ typeof item === "object" &&
143
+ typeof item.sku === "string" &&
144
+ (item.sku === "Copilot Premium Request" || item.sku.includes("Premium")));
145
+ const used = premiumItems.reduce((sum, item) => sum + (item.grossQuantity || 0), 0);
146
+ const total = COPILOT_PLAN_LIMITS[tier];
147
+ if (!total || total <= 0) {
148
+ throw new Error(`Unsupported Copilot tier: ${tier}`);
149
+ }
150
+ const remaining = Math.max(0, total - used);
151
+ const percentRemaining = Math.max(0, Math.min(100, Math.round((remaining / total) * 100)));
152
+ return {
153
+ success: true,
154
+ used,
155
+ total,
156
+ percentRemaining,
157
+ resetTimeIso: getApproxNextResetIso(),
158
+ };
159
+ }
160
+ async function exchangeForCopilotToken(oauthToken) {
161
+ try {
162
+ const response = await fetchWithTimeout(COPILOT_TOKEN_EXCHANGE_URL, {
163
+ headers: {
164
+ Accept: "application/json",
165
+ Authorization: `Bearer ${oauthToken}`,
166
+ ...COPILOT_HEADERS,
167
+ },
168
+ });
169
+ if (!response.ok) {
170
+ return null;
171
+ }
172
+ const tokenData = (await response.json());
173
+ if (!tokenData || typeof tokenData.token !== "string")
174
+ return null;
175
+ return tokenData.token;
176
+ }
177
+ catch {
178
+ return null;
179
+ }
180
+ }
181
+ /**
182
+ * Fetch Copilot usage from GitHub internal API.
183
+ * Tries multiple authentication methods to handle old/new token formats.
184
+ */
185
+ async function fetchCopilotUsage(authData) {
186
+ const oauthToken = authData.refresh || authData.access;
187
+ if (!oauthToken) {
188
+ throw new Error("No OAuth token found in auth data");
189
+ }
190
+ const cachedAccessToken = authData.access;
191
+ const tokenExpiry = authData.expires || 0;
192
+ // Strategy 1: If we have a valid cached access token (from previous exchange), use it.
193
+ if (cachedAccessToken && cachedAccessToken !== oauthToken && tokenExpiry > Date.now()) {
194
+ const response = await fetchWithTimeout(COPILOT_INTERNAL_USER_URL, {
195
+ headers: buildBearerHeaders(cachedAccessToken),
196
+ });
197
+ if (response.ok) {
198
+ return response.json();
199
+ }
200
+ }
201
+ // Strategy 2: Try direct call with OAuth token using legacy format.
202
+ const directResponse = await fetchWithTimeout(COPILOT_INTERNAL_USER_URL, {
203
+ headers: buildLegacyTokenHeaders(oauthToken),
204
+ });
205
+ if (directResponse.ok) {
206
+ return directResponse.json();
207
+ }
208
+ // Strategy 3: Exchange OAuth token for Copilot session token (new auth flow).
209
+ const copilotToken = await exchangeForCopilotToken(oauthToken);
210
+ if (!copilotToken) {
211
+ const errorText = await directResponse.text();
212
+ throw new Error(`GitHub Copilot quota unavailable: ${errorText.slice(0, 160)}`);
213
+ }
214
+ const exchangedResponse = await fetchWithTimeout(COPILOT_INTERNAL_USER_URL, {
215
+ headers: buildBearerHeaders(copilotToken),
216
+ });
217
+ if (!exchangedResponse.ok) {
218
+ const errorText = await exchangedResponse.text();
219
+ throw new Error(`GitHub API error ${exchangedResponse.status}: ${errorText.slice(0, 160)}`);
220
+ }
221
+ return exchangedResponse.json();
222
+ }
223
+ // =============================================================================
224
+ // Export
225
+ // =============================================================================
226
+ /**
227
+ * Query GitHub Copilot premium requests quota
228
+ *
229
+ * @returns Quota result, error, or null if not configured
230
+ */
231
+ export async function queryCopilotQuota() {
232
+ // Strategy 1: Try public billing API with user's fine-grained PAT.
233
+ const quotaConfig = readQuotaConfig();
234
+ if (quotaConfig) {
235
+ try {
236
+ const billing = await fetchPublicBillingUsage(quotaConfig);
237
+ return toQuotaResultFromBilling(billing, quotaConfig.tier);
238
+ }
239
+ catch (err) {
240
+ return {
241
+ success: false,
242
+ error: err instanceof Error ? err.message : String(err),
243
+ };
244
+ }
245
+ }
246
+ // Strategy 2: Best-effort internal API using OpenCode auth.
247
+ const auth = await readCopilotAuth();
248
+ if (!auth) {
249
+ return null; // Not configured
250
+ }
251
+ try {
252
+ const data = await fetchCopilotUsage(auth);
253
+ const premium = data.quota_snapshots.premium_interactions;
254
+ if (!premium) {
255
+ return {
256
+ success: false,
257
+ error: "No premium quota data",
258
+ };
259
+ }
260
+ if (premium.unlimited) {
261
+ return {
262
+ success: true,
263
+ used: 0,
264
+ total: -1, // Indicate unlimited
265
+ percentRemaining: 100,
266
+ resetTimeIso: data.quota_reset_date,
267
+ };
268
+ }
269
+ const total = premium.entitlement;
270
+ const used = total - premium.remaining;
271
+ const percentRemaining = Math.round(premium.percent_remaining);
272
+ return {
273
+ success: true,
274
+ used,
275
+ total,
276
+ percentRemaining,
277
+ resetTimeIso: data.quota_reset_date,
278
+ };
279
+ }
280
+ catch (err) {
281
+ return {
282
+ success: false,
283
+ error: err instanceof Error ? err.message : String(err),
284
+ };
285
+ }
286
+ }
287
+ /**
288
+ * Format Copilot quota for toast display
289
+ *
290
+ * @param result - Copilot quota result
291
+ * @returns Formatted string like "Copilot 229/300 (24%)" or null
292
+ */
293
+ export function formatCopilotQuota(result) {
294
+ if (!result) {
295
+ return null;
296
+ }
297
+ if (!result.success) {
298
+ return null;
299
+ }
300
+ if (result.total === -1) {
301
+ return "Copilot Unlimited";
302
+ }
303
+ const percentUsed = 100 - result.percentRemaining;
304
+ return `Copilot ${result.used}/${result.total} (${percentUsed}%)`;
305
+ }
306
+ //# sourceMappingURL=copilot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copilot.js","sourceRoot":"","sources":["../../src/lib/copilot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Normalized quota output model.
3
+ *
4
+ * Providers should map their internal quota shapes into these types so that
5
+ * formatting and toast display stays universal across providers.
6
+ */
7
+ export interface QuotaToastEntry {
8
+ /**
9
+ * Display label (already human-friendly), e.g. "Copilot" or "Claude (abc..gmail)".
10
+ */
11
+ name: string;
12
+ /** Remaining quota as a percentage [0..100]. */
13
+ percentRemaining: number;
14
+ /** Optional ISO reset timestamp (shown only when percentRemaining is 0). */
15
+ resetTimeIso?: string;
16
+ }
17
+ export interface QuotaToastError {
18
+ /** Short label that will be rendered as "label: message". */
19
+ label: string;
20
+ message: string;
21
+ }
22
+ export interface QuotaProviderResult {
23
+ /** True when provider had enough configuration to attempt a query. */
24
+ attempted: boolean;
25
+ entries: QuotaToastEntry[];
26
+ errors: QuotaToastError[];
27
+ }
28
+ export interface QuotaProviderContext {
29
+ client: {
30
+ config: {
31
+ providers: () => Promise<{
32
+ data?: {
33
+ providers: Array<{
34
+ id: string;
35
+ }>;
36
+ };
37
+ }>;
38
+ get: () => Promise<{
39
+ data?: {
40
+ model?: string;
41
+ };
42
+ }>;
43
+ };
44
+ };
45
+ config: {
46
+ googleModels: string[];
47
+ toastStyle?: "classic" | "grouped";
48
+ };
49
+ }
50
+ export interface QuotaProvider {
51
+ /** Stable id used by config.enabledProviders */
52
+ id: string;
53
+ /** Best-effort availability check (no network if possible) */
54
+ isAvailable: (ctx: QuotaProviderContext) => Promise<boolean>;
55
+ /** Fetch and normalize quota for this provider */
56
+ fetch: (ctx: QuotaProviderContext) => Promise<QuotaProviderResult>;
57
+ /** Optional provider match for onlyCurrentModel filtering */
58
+ matchesCurrentModel?: (model: string) => boolean;
59
+ }
60
+ //# sourceMappingURL=entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../src/lib/entries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb,gDAAgD;IAChD,gBAAgB,EAAE,MAAM,CAAC;IAEzB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QACN,MAAM,EAAE;YACN,SAAS,EAAE,MAAM,OAAO,CAAC;gBAAE,IAAI,CAAC,EAAE;oBAAE,SAAS,EAAE,KAAK,CAAC;wBAAE,EAAE,EAAE,MAAM,CAAA;qBAAE,CAAC,CAAA;iBAAE,CAAA;aAAE,CAAC,CAAC;YAC1E,GAAG,EAAE,MAAM,OAAO,CAAC;gBAAE,IAAI,CAAC,EAAE;oBAAE,KAAK,CAAC,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAC,CAAC;SACnD,CAAC;KACH,CAAC;IACF,MAAM,EAAE;QACN,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,UAAU,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KACpC,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,EAAE,EAAE,MAAM,CAAC;IAEX,8DAA8D;IAC9D,WAAW,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7D,kDAAkD;IAClD,KAAK,EAAE,CAAC,GAAG,EAAE,oBAAoB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAEnE,6DAA6D;IAC7D,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CAClD"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Normalized quota output model.
3
+ *
4
+ * Providers should map their internal quota shapes into these types so that
5
+ * formatting and toast display stays universal across providers.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=entries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.js","sourceRoot":"","sources":["../../src/lib/entries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Firmware AI quota fetcher
3
+ *
4
+ * Uses OpenCode's auth.json (firmware api key) and queries:
5
+ * https://app.firmware.ai/api/v1/quota
6
+ */
7
+ import type { QuotaError } from "./types.js";
8
+ export type FirmwareResult = {
9
+ success: true;
10
+ percentRemaining: number;
11
+ resetTimeIso?: string;
12
+ } | QuotaError | null;
13
+ export declare function hasFirmwareApiKeyConfigured(): Promise<boolean>;
14
+ export declare function queryFirmwareQuota(): Promise<FirmwareResult>;
15
+ //# sourceMappingURL=firmware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firmware.d.ts","sourceRoot":"","sources":["../../src/lib/firmware.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA0C7C,MAAM,MAAM,cAAc,GACtB;IACE,OAAO,EAAE,IAAI,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACD,UAAU,GACV,IAAI,CAAC;AAIT,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC,CAGpE;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,cAAc,CAAC,CAwClE"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Firmware AI quota fetcher
3
+ *
4
+ * Uses OpenCode's auth.json (firmware api key) and queries:
5
+ * https://app.firmware.ai/api/v1/quota
6
+ */
7
+ import { REQUEST_TIMEOUT_MS } from "./types.js";
8
+ import { readAuthFile } from "./opencode-auth.js";
9
+ function clampPercent(n) {
10
+ if (!Number.isFinite(n))
11
+ return 0;
12
+ return Math.max(0, Math.min(100, Math.round(n)));
13
+ }
14
+ async function fetchWithTimeout(url, options) {
15
+ const controller = new AbortController();
16
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
17
+ try {
18
+ return await fetch(url, { ...options, signal: controller.signal });
19
+ }
20
+ catch (err) {
21
+ if (err instanceof Error && err.name === "AbortError") {
22
+ throw new Error(`Request timeout after ${Math.round(REQUEST_TIMEOUT_MS / 1000)}s`);
23
+ }
24
+ throw err;
25
+ }
26
+ finally {
27
+ clearTimeout(timeoutId);
28
+ }
29
+ }
30
+ async function readFirmwareAuth() {
31
+ const auth = await readAuthFile();
32
+ const fw = auth?.firmware;
33
+ if (!fw || fw.type !== "api" || !fw.key)
34
+ return null;
35
+ return fw;
36
+ }
37
+ const FIRMWARE_QUOTA_URL = "https://app.firmware.ai/api/v1/quota";
38
+ export async function hasFirmwareApiKeyConfigured() {
39
+ const auth = await readFirmwareAuth();
40
+ return !!auth?.key;
41
+ }
42
+ export async function queryFirmwareQuota() {
43
+ const auth = await readFirmwareAuth();
44
+ if (!auth)
45
+ return null;
46
+ try {
47
+ const resp = await fetchWithTimeout(FIRMWARE_QUOTA_URL, {
48
+ method: "GET",
49
+ headers: {
50
+ Authorization: `Bearer ${auth.key}`,
51
+ "User-Agent": "OpenCode-Quota-Toast/1.0",
52
+ },
53
+ });
54
+ if (!resp.ok) {
55
+ const text = await resp.text();
56
+ return {
57
+ success: false,
58
+ error: `Firmware API error ${resp.status}: ${text.slice(0, 120)}`,
59
+ };
60
+ }
61
+ const data = (await resp.json());
62
+ // Firmware returns used ratio [0..1]. We convert to remaining %.
63
+ const used = typeof data.used === "number" ? data.used : NaN;
64
+ const percentRemaining = clampPercent(100 - used * 100);
65
+ const resetIso = typeof data.reset === "string" && data.reset.length > 0 ? data.reset : undefined;
66
+ return {
67
+ success: true,
68
+ percentRemaining,
69
+ resetTimeIso: resetIso,
70
+ };
71
+ }
72
+ catch (err) {
73
+ return {
74
+ success: false,
75
+ error: err instanceof Error ? err.message : String(err),
76
+ };
77
+ }
78
+ }
79
+ //# sourceMappingURL=firmware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"firmware.js","sourceRoot":"","sources":["../../src/lib/firmware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Formatting helpers for quota toast output
3
+ */
4
+ import type { QuotaToastEntry, QuotaToastError } from "./entries.js";
5
+ export declare function formatQuotaRows(params: {
6
+ version: string;
7
+ layout?: {
8
+ maxWidth: number;
9
+ narrowAt: number;
10
+ tinyAt: number;
11
+ };
12
+ entries?: QuotaToastEntry[];
13
+ errors?: QuotaToastError[];
14
+ style?: "classic" | "grouped";
15
+ }): string;
16
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/lib/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAwCrE,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B,GAAG,MAAM,CAuET"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Formatting helpers for quota toast output
3
+ */
4
+ import { formatQuotaRowsGrouped } from "./toast-format-grouped.js";
5
+ function clampInt(n, min, max) {
6
+ return Math.max(min, Math.min(max, Math.trunc(n)));
7
+ }
8
+ function padRight(str, width) {
9
+ if (str.length >= width)
10
+ return str.slice(0, width);
11
+ return str + " ".repeat(width - str.length);
12
+ }
13
+ function padLeft(str, width) {
14
+ if (str.length >= width)
15
+ return str.slice(str.length - width);
16
+ return " ".repeat(width - str.length) + str;
17
+ }
18
+ function formatResetCountdown(iso) {
19
+ if (!iso)
20
+ return "-";
21
+ const resetDate = new Date(iso);
22
+ const now = new Date();
23
+ const diffMs = resetDate.getTime() - now.getTime();
24
+ if (!Number.isFinite(diffMs) || diffMs <= 0)
25
+ return "reset";
26
+ const diffMinutes = Math.floor(diffMs / 60000);
27
+ const days = Math.floor(diffMinutes / 1440);
28
+ const hours = Math.floor((diffMinutes % 1440) / 60);
29
+ const minutes = diffMinutes % 60;
30
+ if (days > 0)
31
+ return `${days}d ${hours}h`;
32
+ return `${hours}h ${minutes}m`;
33
+ }
34
+ function bar(percentRemaining, width) {
35
+ const p = clampInt(percentRemaining, 0, 100);
36
+ const filled = Math.round((p / 100) * width);
37
+ const empty = width - filled;
38
+ return "█".repeat(filled) + "░".repeat(empty);
39
+ }
40
+ export function formatQuotaRows(params) {
41
+ if (params.style === "grouped") {
42
+ return formatQuotaRowsGrouped({
43
+ layout: params.layout,
44
+ entries: params.entries,
45
+ errors: params.errors,
46
+ });
47
+ }
48
+ const layout = params.layout ?? { maxWidth: 50, narrowAt: 42, tinyAt: 32 };
49
+ const maxWidth = layout.maxWidth;
50
+ // Responsive columns.
51
+ // - default: name + time on one line, then bar on next line
52
+ // - narrow: shorter name/time cols
53
+ // - tiny: no bars, just "Name time XX%"
54
+ const isTiny = maxWidth <= layout.tinyAt;
55
+ const isNarrow = !isTiny && maxWidth <= layout.narrowAt;
56
+ const separator = " ";
57
+ const percentCol = 4; // "100%"
58
+ const timeCol = isTiny ? 6 : isNarrow ? 7 : 7;
59
+ // Bar width: use most of maxWidth, leaving room for separator + percent on line 2
60
+ // Line 1 (name + time) spans exactly barWidth
61
+ // Line 2 (bar + percent) spans barWidth + separator + percentCol
62
+ const barWidth = Math.max(10, maxWidth - separator.length - percentCol);
63
+ const lines = [];
64
+ const addEntry = (name, resetIso, remaining) => {
65
+ // Only show reset countdown when quota is depleted (0%)
66
+ const timeStr = remaining === 0 ? formatResetCountdown(resetIso) : "";
67
+ if (isTiny) {
68
+ // In tiny mode: single line with name + time + percent
69
+ const tinyNameCol = maxWidth - separator.length - timeCol - separator.length - percentCol;
70
+ const line = [
71
+ padRight(name, tinyNameCol),
72
+ padLeft(timeStr, timeCol),
73
+ padLeft(`${clampInt(remaining, 0, 100)}%`, percentCol),
74
+ ].join(separator);
75
+ lines.push(line.slice(0, maxWidth));
76
+ return;
77
+ }
78
+ // Line 1: label + time (total width = barWidth only)
79
+ // Time is right-aligned to end of bar
80
+ const timeWidth = Math.max(timeStr.length, timeCol);
81
+ const nameWidth = Math.max(1, barWidth - separator.length - timeWidth);
82
+ const timeLine = padRight(name, nameWidth) + separator + padLeft(timeStr, timeWidth);
83
+ lines.push(timeLine.slice(0, barWidth));
84
+ // Line 2: bar + percent (percent extends beyond bar width)
85
+ const barCell = bar(remaining, barWidth);
86
+ const percentCell = padLeft(`${clampInt(remaining, 0, 100)}%`, percentCol);
87
+ const barLine = [barCell, percentCell].join(separator);
88
+ lines.push(barLine);
89
+ };
90
+ for (const entry of params.entries ?? []) {
91
+ addEntry(entry.name, entry.resetTimeIso, entry.percentRemaining);
92
+ }
93
+ // Add error rows (rendered as "label: message")
94
+ for (const err of params.errors ?? []) {
95
+ lines.push(`${err.label}: ${err.message}`);
96
+ }
97
+ return lines.join("\n");
98
+ }
99
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/lib/format.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,sBAAsB,EAAwB,MAAM,2BAA2B,CAAC;AAEzF,SAAS,QAAQ,CAAC,CAAS,EAAE,GAAW,EAAE,GAAW;IACnD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,KAAa;IAC1C,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACpD,OAAO,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,OAAO,CAAC,GAAW,EAAE,KAAa;IACzC,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAC9D,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC;AAC9C,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAY;IACxC,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,CAAC;IACrB,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IACnD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IAE5D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,WAAW,GAAG,EAAE,CAAC;IAEjC,IAAI,IAAI,GAAG,CAAC;QAAE,OAAO,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC;IAC1C,OAAO,GAAG,KAAK,KAAK,OAAO,GAAG,CAAC;AACjC,CAAC;AAED,SAAS,GAAG,CAAC,gBAAwB,EAAE,KAAa;IAClD,MAAM,CAAC,GAAG,QAAQ,CAAC,gBAAgB,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7B,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAU/B;IACC,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,sBAAsB,CAAC;YAC5B,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,MAAM,CAAC,OAAwC;YACxD,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAC3E,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEjC,sBAAsB;IACtB,4DAA4D;IAC5D,mCAAmC;IACnC,0CAA0C;IAC1C,MAAM,MAAM,GAAG,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC;IAExD,MAAM,SAAS,GAAG,IAAI,CAAC;IACvB,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,SAAS;IAE/B,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9C,kFAAkF;IAClF,8CAA8C;IAC9C,iEAAiE;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,GAAG,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC;IAExE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,QAA4B,EAAE,SAAiB,EAAE,EAAE;QACjF,wDAAwD;QACxD,MAAM,OAAO,GAAG,SAAS,KAAK,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAEtE,IAAI,MAAM,EAAE,CAAC;YACX,uDAAuD;YACvD,MAAM,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,MAAM,GAAG,UAAU,CAAC;YAC1F,MAAM,IAAI,GAAG;gBACX,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;gBAC3B,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC;gBACzB,OAAO,CAAC,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC;aACvD,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;YACpC,OAAO;QACT,CAAC;QAED,qDAAqD;QACrD,sCAAsC;QACtC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QACrF,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;QAExC,2DAA2D;QAC3D,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACzC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC3E,MAAM,OAAO,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACzC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACnE,CAAC;IAED,gDAAgD;IAChD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACtC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Persistent access-token cache for Google Antigravity accounts.
3
+ *
4
+ * Why:
5
+ * - Antigravity quota is multi-account; each account needs its own access token.
6
+ * - Refreshing on every toast is noisy and increases timeout risk.
7
+ * - We persist access tokens so restarts don't force immediate refresh.
8
+ */
9
+ export interface GoogleAccessTokenCacheEntry {
10
+ accessToken: string;
11
+ expiresAt: number;
12
+ projectId: string;
13
+ email?: string;
14
+ }
15
+ export declare function getGoogleTokenCachePath(): string;
16
+ export declare function makeAccountCacheKey(params: {
17
+ refreshToken: string;
18
+ projectId: string;
19
+ email?: string;
20
+ }): string;
21
+ export declare function getCachedAccessToken(params: {
22
+ key: string;
23
+ skewMs: number;
24
+ }): Promise<GoogleAccessTokenCacheEntry | null>;
25
+ export declare function setCachedAccessToken(params: {
26
+ key: string;
27
+ entry: GoogleAccessTokenCacheEntry;
28
+ }): Promise<void>;
29
+ export declare function clearGoogleTokenCache(): Promise<void>;
30
+ //# sourceMappingURL=google-token-cache.d.ts.map