@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.1

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 (225) hide show
  1. package/LICENSE +57744 -0
  2. package/native-manifest.json +1414 -0
  3. package/package.json +254 -0
  4. package/plugins/abacus-ai/icon.svg +18 -0
  5. package/plugins/abacus-ai/plugin.js +121 -0
  6. package/plugins/abacus-ai/plugin.json +17 -0
  7. package/plugins/alibaba/icon-color.svg +1 -0
  8. package/plugins/alibaba/icon.svg +1 -0
  9. package/plugins/alibaba/plugin.js +322 -0
  10. package/plugins/alibaba/plugin.json +20 -0
  11. package/plugins/alibaba-token-plan/icon-color.svg +1 -0
  12. package/plugins/alibaba-token-plan/icon.svg +1 -0
  13. package/plugins/alibaba-token-plan/plugin.js +544 -0
  14. package/plugins/alibaba-token-plan/plugin.json +19 -0
  15. package/plugins/amp/icon-color.svg +1 -0
  16. package/plugins/amp/icon-conflict.svg +6 -0
  17. package/plugins/amp/icon.svg +1 -0
  18. package/plugins/amp/plugin.js +175 -0
  19. package/plugins/amp/plugin.json +16 -0
  20. package/plugins/antigravity/icon-color.svg +1 -0
  21. package/plugins/antigravity/icon-conflict.svg +3 -0
  22. package/plugins/antigravity/icon.svg +1 -0
  23. package/plugins/antigravity/plugin.js +739 -0
  24. package/plugins/antigravity/plugin.json +31 -0
  25. package/plugins/antigravity-cli/icon.svg +3 -0
  26. package/plugins/antigravity-cli/plugin.js +570 -0
  27. package/plugins/antigravity-cli/plugin.json +16 -0
  28. package/plugins/antigravity-ide/icon.svg +3 -0
  29. package/plugins/antigravity-ide/plugin.js +257 -0
  30. package/plugins/antigravity-ide/plugin.json +29 -0
  31. package/plugins/augment/icon.svg +14 -0
  32. package/plugins/augment/plugin.js +59 -0
  33. package/plugins/augment/plugin.json +18 -0
  34. package/plugins/aws-bedrock/icon-color.svg +1 -0
  35. package/plugins/aws-bedrock/icon.svg +1 -0
  36. package/plugins/aws-bedrock/plugin.js +206 -0
  37. package/plugins/aws-bedrock/plugin.json +18 -0
  38. package/plugins/azure-openai/icon-color.svg +1 -0
  39. package/plugins/azure-openai/icon.svg +1 -0
  40. package/plugins/azure-openai/plugin.js +145 -0
  41. package/plugins/azure-openai/plugin.json +17 -0
  42. package/plugins/chutes/icon.svg +7 -0
  43. package/plugins/chutes/plugin.js +236 -0
  44. package/plugins/chutes/plugin.json +19 -0
  45. package/plugins/claude/icon-color.svg +1 -0
  46. package/plugins/claude/icon.svg +1 -0
  47. package/plugins/claude/plugin.js +1723 -0
  48. package/plugins/claude/plugin.json +30 -0
  49. package/plugins/codebuff/icon.svg +92 -0
  50. package/plugins/codebuff/plugin.js +113 -0
  51. package/plugins/codebuff/plugin.json +17 -0
  52. package/plugins/codex/icon-color.svg +5 -0
  53. package/plugins/codex/icon.svg +1 -0
  54. package/plugins/codex/plugin.js +1009 -0
  55. package/plugins/codex/plugin.json +82 -0
  56. package/plugins/command-code/icon.svg +3 -0
  57. package/plugins/command-code/plugin.js +184 -0
  58. package/plugins/command-code/plugin.json +20 -0
  59. package/plugins/copilot/icon-color.svg +1 -0
  60. package/plugins/copilot/icon.svg +1 -0
  61. package/plugins/copilot/plugin.js +684 -0
  62. package/plugins/copilot/plugin.json +21 -0
  63. package/plugins/crof/icon.svg +3 -0
  64. package/plugins/crof/plugin.js +53 -0
  65. package/plugins/crof/plugin.json +17 -0
  66. package/plugins/crofai/icon.svg +16 -0
  67. package/plugins/crofai/plugin.js +81 -0
  68. package/plugins/crofai/plugin.json +18 -0
  69. package/plugins/cursor/icon-conflict.svg +3 -0
  70. package/plugins/cursor/icon.svg +1 -0
  71. package/plugins/cursor/plugin.js +1379 -0
  72. package/plugins/cursor/plugin.json +23 -0
  73. package/plugins/cursor-nightly/icon.png +0 -0
  74. package/plugins/cursor-nightly/plugin.js +1379 -0
  75. package/plugins/cursor-nightly/plugin.json +23 -0
  76. package/plugins/deepgram/icon.svg +1 -0
  77. package/plugins/deepgram/plugin.js +20 -0
  78. package/plugins/deepgram/plugin.json +17 -0
  79. package/plugins/deepseek/icon-color.svg +1 -0
  80. package/plugins/deepseek/icon.svg +1 -0
  81. package/plugins/deepseek/plugin.js +71 -0
  82. package/plugins/deepseek/plugin.json +17 -0
  83. package/plugins/devin/icon.svg +5 -0
  84. package/plugins/devin/plugin.js +341 -0
  85. package/plugins/devin/plugin.json +16 -0
  86. package/plugins/doubao/icon-color.svg +1 -0
  87. package/plugins/doubao/icon.svg +1 -0
  88. package/plugins/doubao/plugin.js +92 -0
  89. package/plugins/doubao/plugin.json +17 -0
  90. package/plugins/droid/icon.svg +1 -0
  91. package/plugins/droid/plugin.json +20 -0
  92. package/plugins/elevenlabs/icon.svg +4 -0
  93. package/plugins/elevenlabs/plugin.js +48 -0
  94. package/plugins/elevenlabs/plugin.json +18 -0
  95. package/plugins/factory/icon-conflict.svg +1 -0
  96. package/plugins/factory/icon.svg +1 -0
  97. package/plugins/factory/plugin.js +487 -0
  98. package/plugins/factory/plugin.json +15 -0
  99. package/plugins/fireworks-ai/icon.svg +6 -0
  100. package/plugins/fireworks-ai/plugin.js +370 -0
  101. package/plugins/fireworks-ai/plugin.json +19 -0
  102. package/plugins/gemini/icon-color.svg +1 -0
  103. package/plugins/gemini/icon.svg +1 -0
  104. package/plugins/gemini/plugin.js +460 -0
  105. package/plugins/gemini/plugin.json +16 -0
  106. package/plugins/grok/icon.svg +1 -0
  107. package/plugins/grok/plugin.js +297 -0
  108. package/plugins/grok/plugin.json +19 -0
  109. package/plugins/groqcloud/icon.svg +1 -0
  110. package/plugins/groqcloud/plugin.js +117 -0
  111. package/plugins/groqcloud/plugin.json +19 -0
  112. package/plugins/jetbrains-ai-assistant/icon-color.svg +14 -0
  113. package/plugins/jetbrains-ai-assistant/icon-conflict.svg +3 -0
  114. package/plugins/jetbrains-ai-assistant/icon.svg +14 -0
  115. package/plugins/jetbrains-ai-assistant/plugin.js +313 -0
  116. package/plugins/jetbrains-ai-assistant/plugin.json +16 -0
  117. package/plugins/kilo/icon.svg +1 -0
  118. package/plugins/kilo/plugin.js +111 -0
  119. package/plugins/kilo/plugin.json +19 -0
  120. package/plugins/kimi/icon-color.svg +1 -0
  121. package/plugins/kimi/icon-conflict.svg +3 -0
  122. package/plugins/kimi/icon.svg +1 -0
  123. package/plugins/kimi/plugin.js +358 -0
  124. package/plugins/kimi/plugin.json +15 -0
  125. package/plugins/kimi-k2/icon-color.svg +1 -0
  126. package/plugins/kimi-k2/icon.svg +1 -0
  127. package/plugins/kimi-k2/plugin.js +58 -0
  128. package/plugins/kimi-k2/plugin.json +18 -0
  129. package/plugins/kiro/icon-color.svg +5 -0
  130. package/plugins/kiro/icon-conflict.svg +1 -0
  131. package/plugins/kiro/icon.svg +4 -0
  132. package/plugins/kiro/plugin.js +390 -0
  133. package/plugins/kiro/plugin.json +16 -0
  134. package/plugins/litellm/icon.svg +16 -0
  135. package/plugins/litellm/plugin.js +199 -0
  136. package/plugins/litellm/plugin.json +16 -0
  137. package/plugins/llm-proxy/icon.svg +1 -0
  138. package/plugins/llm-proxy/plugin.js +40 -0
  139. package/plugins/llm-proxy/plugin.json +17 -0
  140. package/plugins/manus/icon.svg +6 -0
  141. package/plugins/manus/plugin.js +50 -0
  142. package/plugins/manus/plugin.json +17 -0
  143. package/plugins/mimo/icon.svg +1 -0
  144. package/plugins/mimo/plugin.js +177 -0
  145. package/plugins/mimo/plugin.json +19 -0
  146. package/plugins/minimax/icon-color.svg +1 -0
  147. package/plugins/minimax/icon-conflict.svg +4 -0
  148. package/plugins/minimax/icon.svg +1 -0
  149. package/plugins/minimax/plugin.js +453 -0
  150. package/plugins/minimax/plugin.json +14 -0
  151. package/plugins/mistral/icon-color.svg +1 -0
  152. package/plugins/mistral/icon.svg +1 -0
  153. package/plugins/mistral/plugin.js +152 -0
  154. package/plugins/mistral/plugin.json +18 -0
  155. package/plugins/moonshot/icon.svg +1 -0
  156. package/plugins/moonshot/plugin.js +43 -0
  157. package/plugins/moonshot/plugin.json +16 -0
  158. package/plugins/nanogpt/icon-color.svg +17 -0
  159. package/plugins/nanogpt/icon.svg +10 -0
  160. package/plugins/nanogpt/plugin.js +80 -0
  161. package/plugins/nanogpt/plugin.json +17 -0
  162. package/plugins/neuralwatt/icon.svg +3 -0
  163. package/plugins/neuralwatt/plugin.js +152 -0
  164. package/plugins/neuralwatt/plugin.json +17 -0
  165. package/plugins/ollama/icon.svg +1 -0
  166. package/plugins/ollama/plugin.js +92 -0
  167. package/plugins/ollama/plugin.json +20 -0
  168. package/plugins/openai-api/icon.svg +1 -0
  169. package/plugins/openai-api/plugin.js +533 -0
  170. package/plugins/openai-api/plugin.json +26 -0
  171. package/plugins/opencode/icon.svg +1 -0
  172. package/plugins/opencode/plugin.js +342 -0
  173. package/plugins/opencode/plugin.json +19 -0
  174. package/plugins/opencode-go/icon-conflict.svg +1 -0
  175. package/plugins/opencode-go/icon.svg +1 -0
  176. package/plugins/opencode-go/plugin.js +617 -0
  177. package/plugins/opencode-go/plugin.json +50 -0
  178. package/plugins/openrouter/icon.svg +1 -0
  179. package/plugins/openrouter/plugin.js +156 -0
  180. package/plugins/openrouter/plugin.json +8 -0
  181. package/plugins/perplexity/icon-color.svg +1 -0
  182. package/plugins/perplexity/icon-conflict.svg +1 -0
  183. package/plugins/perplexity/icon.svg +1 -0
  184. package/plugins/perplexity/plugin.js +425 -0
  185. package/plugins/perplexity/plugin.json +19 -0
  186. package/plugins/poe/icon.svg +1 -0
  187. package/plugins/poe/plugin.js +190 -0
  188. package/plugins/poe/plugin.json +19 -0
  189. package/plugins/stepfun/icon-color.svg +1 -0
  190. package/plugins/stepfun/icon.svg +1 -0
  191. package/plugins/stepfun/plugin.js +155 -0
  192. package/plugins/stepfun/plugin.json +18 -0
  193. package/plugins/synthetic/icon-conflict.svg +12 -0
  194. package/plugins/synthetic/icon.svg +14 -0
  195. package/plugins/synthetic/plugin.js +331 -0
  196. package/plugins/synthetic/plugin.json +23 -0
  197. package/plugins/t3chat/icon-color.svg +28 -0
  198. package/plugins/t3chat/icon.svg +4 -0
  199. package/plugins/t3chat/plugin.js +243 -0
  200. package/plugins/t3chat/plugin.json +20 -0
  201. package/plugins/venice/icon-color.svg +1 -0
  202. package/plugins/venice/icon.svg +1 -0
  203. package/plugins/venice/plugin.js +69 -0
  204. package/plugins/venice/plugin.json +16 -0
  205. package/plugins/vertex-ai/icon-color.svg +1 -0
  206. package/plugins/vertex-ai/icon.svg +1 -0
  207. package/plugins/vertex-ai/plugin.js +172 -0
  208. package/plugins/vertex-ai/plugin.json +18 -0
  209. package/plugins/warp/icon-color.svg +10 -0
  210. package/plugins/warp/icon.svg +6 -0
  211. package/plugins/warp/plugin.js +108 -0
  212. package/plugins/warp/plugin.json +18 -0
  213. package/plugins/windsurf/icon-conflict.svg +3 -0
  214. package/plugins/windsurf/icon.svg +1 -0
  215. package/plugins/windsurf/plugin.js +208 -0
  216. package/plugins/windsurf/plugin.json +16 -0
  217. package/plugins/zai/icon-conflict.svg +5 -0
  218. package/plugins/zai/icon.svg +1 -0
  219. package/plugins/zai/plugin.js +188 -0
  220. package/plugins/zai/plugin.json +16 -0
  221. package/plugins/zed/icon.svg +4 -0
  222. package/plugins/zed/plugin.js +270 -0
  223. package/plugins/zed/plugin.json +19 -0
  224. package/usagestat +0 -0
  225. package/usagestatd +0 -0
@@ -0,0 +1,684 @@
1
+ (function () {
2
+ const KEYCHAIN_SERVICE = "OpenUsage-copilot";
3
+ const GH_KEYCHAIN_SERVICE = "gh:github.com";
4
+ const DEFAULT_USAGE_URL = "https://api.github.com/copilot_internal/user";
5
+ const BUDGETS_URL = "https://github.com/settings/billing/budgets";
6
+ const COPILOT_PRODUCT_ID = "copilot";
7
+ const COPILOT_PREMIUM_REQUEST_SKU = "copilot_premium_request";
8
+ const COPILOT_AGENT_PREMIUM_REQUEST_SKU = "copilot_agent_premium_request";
9
+ const SPARK_PREMIUM_REQUEST_SKU = "spark_premium_request";
10
+ const COPILOT_BUDGET_SELECTORS = [
11
+ COPILOT_PRODUCT_ID,
12
+ COPILOT_PREMIUM_REQUEST_SKU,
13
+ COPILOT_AGENT_PREMIUM_REQUEST_SKU,
14
+ SPARK_PREMIUM_REQUEST_SKU,
15
+ ];
16
+
17
+ function readJson(ctx, path) {
18
+ try {
19
+ if (!ctx.host.fs.exists(path)) return null;
20
+ const text = ctx.host.fs.readText(path);
21
+ return ctx.util.tryParseJson(text);
22
+ } catch (e) {
23
+ ctx.host.log.warn("readJson failed for " + path + ": " + String(e));
24
+ return null;
25
+ }
26
+ }
27
+
28
+ function writeJson(ctx, path, value) {
29
+ try {
30
+ ctx.host.fs.writeText(path, JSON.stringify(value));
31
+ } catch (e) {
32
+ ctx.host.log.warn("writeJson failed for " + path + ": " + String(e));
33
+ }
34
+ }
35
+
36
+ function saveToken(ctx, token) {
37
+ try {
38
+ ctx.host.keychain.writeGenericPassword(
39
+ KEYCHAIN_SERVICE,
40
+ JSON.stringify({ token: token }),
41
+ );
42
+ } catch (e) {
43
+ ctx.host.log.warn("keychain write failed: " + String(e));
44
+ }
45
+ writeJson(ctx, ctx.app.pluginDataDir + "/auth.json", { token: token });
46
+ }
47
+
48
+ function clearCachedToken(ctx) {
49
+ try {
50
+ ctx.host.keychain.deleteGenericPassword(KEYCHAIN_SERVICE);
51
+ } catch (e) {
52
+ ctx.host.log.info("keychain delete failed: " + String(e));
53
+ }
54
+ writeJson(ctx, ctx.app.pluginDataDir + "/auth.json", null);
55
+ }
56
+
57
+ function loadTokenFromKeychain(ctx) {
58
+ try {
59
+ const raw = ctx.host.keychain.readGenericPassword(KEYCHAIN_SERVICE);
60
+ if (raw) {
61
+ const parsed = ctx.util.tryParseJson(raw);
62
+ if (parsed && parsed.token) {
63
+ ctx.host.log.info("token loaded from OpenUsage keychain");
64
+ return { token: parsed.token, source: "keychain" };
65
+ }
66
+ }
67
+ } catch (e) {
68
+ ctx.host.log.info("OpenUsage keychain read failed: " + String(e));
69
+ }
70
+ return null;
71
+ }
72
+
73
+ function loadTokenFromGhCli(ctx) {
74
+ try {
75
+ const raw = ctx.host.keychain.readGenericPassword(GH_KEYCHAIN_SERVICE);
76
+ if (raw) {
77
+ let token = raw;
78
+ if (
79
+ typeof token === "string" &&
80
+ token.indexOf("go-keyring-base64:") === 0
81
+ ) {
82
+ token = ctx.base64.decode(token.slice("go-keyring-base64:".length));
83
+ }
84
+ if (token) {
85
+ ctx.host.log.info("token loaded from gh CLI keychain");
86
+ return { token: token, source: "gh-cli" };
87
+ }
88
+ }
89
+ } catch (e) {
90
+ ctx.host.log.info("gh CLI keychain read failed: " + String(e));
91
+ }
92
+ return null;
93
+ }
94
+
95
+ function loadTokenFromGhCommand(ctx) {
96
+ try {
97
+ if (!ctx.host.command || typeof ctx.host.command.run !== "function") return null;
98
+ const result = ctx.host.command.run({
99
+ program: "gh",
100
+ args: ["auth", "token"],
101
+ timeoutMs: 10000,
102
+ });
103
+ if (result && result.status === 0 && typeof result.stdout === "string") {
104
+ const token = result.stdout.trim();
105
+ if (token) {
106
+ ctx.host.log.info("token loaded from gh auth token");
107
+ return { token: token, source: "gh-command" };
108
+ }
109
+ }
110
+ if (result && result.stderr) {
111
+ ctx.host.log.info("gh auth token failed: " + String(result.stderr).trim());
112
+ }
113
+ } catch (e) {
114
+ ctx.host.log.info("gh auth token command failed: " + String(e));
115
+ }
116
+ return null;
117
+ }
118
+
119
+ function loadTokenFromStateFile(ctx) {
120
+ const data = readJson(ctx, ctx.app.pluginDataDir + "/auth.json");
121
+ if (data && data.token) {
122
+ ctx.host.log.info("token loaded from state file");
123
+ return { token: data.token, source: "state" };
124
+ }
125
+ return null;
126
+ }
127
+
128
+ function loadTokenFromEnv(ctx) {
129
+ const names = ["COPILOT_API_TOKEN", "GITHUB_TOKEN", "GH_TOKEN"];
130
+ for (let i = 0; i < names.length; i += 1) {
131
+ try {
132
+ const value = ctx.host.env.get(names[i]);
133
+ if (typeof value === "string" && value.trim()) {
134
+ ctx.host.log.info("token loaded from " + names[i]);
135
+ return { token: value.trim(), source: "env" };
136
+ }
137
+ } catch (e) {
138
+ ctx.host.log.warn("env token read failed for " + names[i] + ": " + String(e));
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+
144
+ function loadToken(ctx) {
145
+ return (
146
+ loadTokenFromEnv(ctx) ||
147
+ loadTokenFromKeychain(ctx) ||
148
+ loadTokenFromGhCli(ctx) ||
149
+ loadTokenFromGhCommand(ctx) ||
150
+ loadTokenFromStateFile(ctx)
151
+ );
152
+ }
153
+
154
+ function fetchUsage(ctx, token) {
155
+ const usageUrl = ctx.host.env.get("COPILOT_USAGE_URL") || DEFAULT_USAGE_URL;
156
+ return ctx.util.request({
157
+ method: "GET",
158
+ url: usageUrl,
159
+ headers: {
160
+ Authorization: "token " + token,
161
+ Accept: "application/json",
162
+ "Editor-Version": "vscode/1.96.2",
163
+ "Editor-Plugin-Version": "copilot-chat/0.26.7",
164
+ "User-Agent": "GitHubCopilotChat/0.26.7",
165
+ "X-Github-Api-Version": "2025-04-01",
166
+ },
167
+ timeoutMs: 10000,
168
+ });
169
+ }
170
+
171
+ function trimText(value) {
172
+ if (value === null || value === undefined) return null;
173
+ const text = String(value).trim();
174
+ return text || null;
175
+ }
176
+
177
+ function readEnv(ctx, name) {
178
+ try {
179
+ return trimText(ctx.host.env.get(name));
180
+ } catch (_) {
181
+ return null;
182
+ }
183
+ }
184
+
185
+ function providerSetting(ctx, names) {
186
+ const settings = ctx.provider && ctx.provider.settings ? ctx.provider.settings : {};
187
+ for (let i = 0; i < names.length; i += 1) {
188
+ const value = trimText(settings[names[i]]);
189
+ if (value) return value;
190
+ }
191
+ return null;
192
+ }
193
+
194
+ function normalizeCookieHeader(value) {
195
+ let text = trimText(value);
196
+ if (!text) return null;
197
+ text = text.replace(/^Cookie:\s*/i, "").trim();
198
+ return text || null;
199
+ }
200
+
201
+ function loadBudgetCookieHeader(ctx) {
202
+ return normalizeCookieHeader(ctx.provider && ctx.provider.cookieHeader) ||
203
+ normalizeCookieHeader(providerSetting(ctx, ["budgetCookieHeader", "githubCookieHeader", "cookieHeader"])) ||
204
+ normalizeCookieHeader(readEnv(ctx, "COPILOT_BUDGET_COOKIE_HEADER")) ||
205
+ normalizeCookieHeader(readEnv(ctx, "COPILOT_BUDGET_COOKIE")) ||
206
+ normalizeCookieHeader(readEnv(ctx, "GITHUB_COOKIE_HEADER")) ||
207
+ normalizeCookieHeader(readEnv(ctx, "GITHUB_COOKIE"));
208
+ }
209
+
210
+ function requestGitHubBudget(ctx, url, cookieHeader, accept, nonce) {
211
+ const headers = {
212
+ Cookie: cookieHeader,
213
+ Accept: accept,
214
+ Referer: BUDGETS_URL,
215
+ "User-Agent": "CodexBar",
216
+ };
217
+ if (accept.indexOf("json") >= 0) {
218
+ headers["X-Requested-With"] = "XMLHttpRequest";
219
+ headers["GitHub-Verified-Fetch"] = "true";
220
+ if (nonce) headers["X-Fetch-Nonce"] = nonce;
221
+ }
222
+
223
+ return ctx.util.request({
224
+ method: "GET",
225
+ url: url,
226
+ headers: headers,
227
+ timeoutMs: 15000,
228
+ });
229
+ }
230
+
231
+ function extractFetchNonce(html) {
232
+ const patterns = [
233
+ /x-fetch-nonce"\s+content="([^"]+)"/i,
234
+ /X-Fetch-Nonce"\s*:\s*"([^"]+)"/i,
235
+ /fetchNonce"\s*:\s*"([^"]+)"/i,
236
+ /data-fetch-nonce="([^"]+)"/i,
237
+ ];
238
+ for (let i = 0; i < patterns.length; i += 1) {
239
+ const match = patterns[i].exec(html);
240
+ if (match && match[1]) return match[1];
241
+ }
242
+ return null;
243
+ }
244
+
245
+ function fetchBudgetNonce(ctx, cookieHeader) {
246
+ try {
247
+ const resp = requestGitHubBudget(ctx, BUDGETS_URL, cookieHeader, "text/html,application/xhtml+xml", null);
248
+ if (resp.status !== 200) return null;
249
+ return extractFetchNonce(resp.bodyText || "");
250
+ } catch (e) {
251
+ ctx.host.log.info("Copilot budget nonce fetch failed: " + String(e));
252
+ return null;
253
+ }
254
+ }
255
+
256
+ function budgetPageUrl(page) {
257
+ return BUDGETS_URL + "?page=" + encodeURIComponent(String(page)) + "&page_size=10&scope=customer";
258
+ }
259
+
260
+ function fetchBudgetPage(ctx, cookieHeader, nonce, page) {
261
+ const resp = requestGitHubBudget(ctx, budgetPageUrl(page), cookieHeader, "application/json", nonce);
262
+ if (resp.status === 401 || resp.status === 403) {
263
+ throw "GitHub web session is not logged in.";
264
+ }
265
+ if (resp.status < 200 || resp.status >= 300) {
266
+ throw "GitHub budget request failed (HTTP " + String(resp.status) + ").";
267
+ }
268
+ const json = ctx.util.tryParseJson(resp.bodyText);
269
+ if (!json) throw "GitHub budget response invalid.";
270
+ const payload = json.payload && typeof json.payload === "object" ? json.payload : json;
271
+ let budgets = [];
272
+ if (Array.isArray(payload.budgets)) budgets = payload.budgets;
273
+ else if (Array.isArray(payload.data)) budgets = payload.data;
274
+ else if (Array.isArray(json)) budgets = json;
275
+ const hasNextPage = payload.hasNextPage === true || payload.has_next_page === true;
276
+ return { budgets: budgets, hasNextPage: hasNextPage };
277
+ }
278
+
279
+ function parseAmount(value, centsKey) {
280
+ if (typeof value === "number" && Number.isFinite(value)) return centsKey ? value / 100 : value;
281
+ if (typeof value === "string" && value.trim()) {
282
+ const trimmed = value.trim();
283
+ const negative = trimmed[0] === "-";
284
+ if (trimmed.slice(negative ? 1 : 0).indexOf("-") >= 0) return null;
285
+ const unsigned = trimmed.replace(/[^0-9.]/g, "");
286
+ if (!unsigned) return null;
287
+ const parsed = Number((negative ? "-" : "") + unsigned);
288
+ return Number.isFinite(parsed) ? parsed : null;
289
+ }
290
+ if (value && typeof value === "object") {
291
+ const keys = ["amount", "value", "total", "cents", "formatted"];
292
+ for (let i = 0; i < keys.length; i += 1) {
293
+ const key = keys[i];
294
+ const parsed = parseAmount(value[key], key === "cents");
295
+ if (parsed !== null) return parsed;
296
+ }
297
+ }
298
+ return null;
299
+ }
300
+
301
+ function readString(row, keys) {
302
+ for (let i = 0; i < keys.length; i += 1) {
303
+ const value = row && row[keys[i]];
304
+ if (typeof value === "string" && value.trim()) return value.trim();
305
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
306
+ }
307
+ return null;
308
+ }
309
+
310
+ function productSelectorsFromObject(value) {
311
+ if (!value || typeof value !== "object") return [];
312
+ const keys = ["sku", "name", "display_name", "displayName", "product", "product_name", "productName"];
313
+ const out = [];
314
+ for (let i = 0; i < keys.length; i += 1) {
315
+ const text = readString(value, [keys[i]]);
316
+ if (text) out.push(text);
317
+ }
318
+ return out;
319
+ }
320
+
321
+ function readStringArray(row, keys) {
322
+ for (let i = 0; i < keys.length; i += 1) {
323
+ const value = row && row[keys[i]];
324
+ if (Array.isArray(value) && value.length) {
325
+ const out = [];
326
+ for (let j = 0; j < value.length; j += 1) {
327
+ if (typeof value[j] === "string" && value[j].trim()) out.push(value[j].trim());
328
+ else out.push.apply(out, productSelectorsFromObject(value[j]));
329
+ }
330
+ if (out.length) return out;
331
+ }
332
+ if (typeof value === "string" && value.trim()) return [value.trim()];
333
+ }
334
+ return [];
335
+ }
336
+
337
+ function readAmount(row, keys) {
338
+ for (let i = 0; i < keys.length; i += 1) {
339
+ const parsed = parseAmount(row && row[keys[i]], false);
340
+ if (parsed !== null) return parsed;
341
+ }
342
+ return 0;
343
+ }
344
+
345
+ function slug(value) {
346
+ return String(value || "")
347
+ .toLowerCase()
348
+ .replace(/[^a-z0-9]+/g, "-")
349
+ .replace(/^-+|-+$/g, "");
350
+ }
351
+
352
+ function normalizedBillingIdentifier(value) {
353
+ const raw = slug(value);
354
+ if (!raw) return null;
355
+ const underscored = raw.replace(/-/g, "_");
356
+ if (underscored === COPILOT_PRODUCT_ID) return COPILOT_PRODUCT_ID;
357
+ if (underscored === "premium_request" || underscored === "premium_requests") {
358
+ return COPILOT_PREMIUM_REQUEST_SKU;
359
+ }
360
+ if (underscored === "coding_agent_premium_request" || underscored === "coding_agent_premium_requests") {
361
+ return COPILOT_AGENT_PREMIUM_REQUEST_SKU;
362
+ }
363
+ if (underscored.indexOf("spark") >= 0 &&
364
+ underscored.indexOf("premium") >= 0 &&
365
+ underscored.indexOf("request") >= 0) {
366
+ return SPARK_PREMIUM_REQUEST_SKU;
367
+ }
368
+ if ((underscored.indexOf("cloud") >= 0 || underscored.indexOf("coding") >= 0) &&
369
+ underscored.indexOf("agent") >= 0 &&
370
+ underscored.indexOf("premium") >= 0 &&
371
+ underscored.indexOf("request") >= 0) {
372
+ return COPILOT_AGENT_PREMIUM_REQUEST_SKU;
373
+ }
374
+ if (underscored.indexOf("bundled") >= 0 &&
375
+ underscored.indexOf("premium") >= 0 &&
376
+ underscored.indexOf("request") >= 0) {
377
+ return COPILOT_PREMIUM_REQUEST_SKU;
378
+ }
379
+ if (underscored.indexOf("copilot") >= 0 &&
380
+ underscored.indexOf("agent") >= 0 &&
381
+ underscored.indexOf("premium") >= 0 &&
382
+ underscored.indexOf("request") >= 0) {
383
+ return COPILOT_AGENT_PREMIUM_REQUEST_SKU;
384
+ }
385
+ if (underscored.indexOf("copilot") >= 0 &&
386
+ underscored.indexOf("premium") >= 0 &&
387
+ underscored.indexOf("request") >= 0) {
388
+ return COPILOT_PREMIUM_REQUEST_SKU;
389
+ }
390
+ return underscored;
391
+ }
392
+
393
+ function parseBudget(row) {
394
+ const productSkus = readStringArray(row, [
395
+ "budget_product_skus",
396
+ "budgetProductSkus",
397
+ "budget_product_sku",
398
+ "budgetProductSku",
399
+ "product_skus",
400
+ "productSkus",
401
+ "skus",
402
+ "sku",
403
+ "product",
404
+ "product_name",
405
+ "productName",
406
+ "pricing_target_id",
407
+ "pricingTargetId",
408
+ ]);
409
+ const budget = {
410
+ id: readString(row, ["id", "uuid", "budget_id", "budgetId"]),
411
+ name: readString(row, ["name", "display_name", "displayName", "title"]),
412
+ budgetType: readString(row, ["budget_type", "budgetType", "type", "pricing_target_type", "pricingTargetType"]),
413
+ budgetProductSkus: productSkus,
414
+ budgetEntityName: readString(row, [
415
+ "budget_entity_name",
416
+ "budgetEntityName",
417
+ "entity_name",
418
+ "entityName",
419
+ "target_name",
420
+ "targetName",
421
+ ]),
422
+ budgetAmount: readAmount(row, [
423
+ "budget_amount",
424
+ "budgetAmount",
425
+ "target_amount",
426
+ "targetAmount",
427
+ "spending_limit",
428
+ "spendingLimit",
429
+ "limit",
430
+ "amount",
431
+ "max",
432
+ ]),
433
+ currentAmount: readAmount(row, [
434
+ "current_usage",
435
+ "currentUsage",
436
+ "current_amount",
437
+ "currentAmount",
438
+ "usage_amount",
439
+ "usageAmount",
440
+ "usage",
441
+ "spent",
442
+ "amount_used",
443
+ "amountUsed",
444
+ ]),
445
+ };
446
+ const selectorValues = budget.budgetProductSkus.concat([
447
+ budget.budgetType,
448
+ budget.budgetEntityName,
449
+ budget.name,
450
+ ]).filter(Boolean);
451
+ budget.selectors = selectorValues
452
+ .map(normalizedBillingIdentifier)
453
+ .filter(Boolean);
454
+ return budget;
455
+ }
456
+
457
+ function hasCopilotSelector(selectors) {
458
+ for (let i = 0; i < selectors.length; i += 1) {
459
+ for (let j = 0; j < COPILOT_BUDGET_SELECTORS.length; j += 1) {
460
+ if (selectors[i] === COPILOT_BUDGET_SELECTORS[j]) return true;
461
+ }
462
+ }
463
+ return false;
464
+ }
465
+
466
+ function budgetTitle(budget) {
467
+ const selectors = budget.selectors || [];
468
+ let type = null;
469
+ if (selectors.length === 1 && selectors[0] === COPILOT_PRODUCT_ID) {
470
+ type = "Copilot";
471
+ } else if (selectors.indexOf(COPILOT_AGENT_PREMIUM_REQUEST_SKU) >= 0) {
472
+ type = "Copilot Agent Premium Requests";
473
+ } else if (selectors.indexOf(SPARK_PREMIUM_REQUEST_SKU) >= 0) {
474
+ type = "Spark Premium Requests";
475
+ } else if (selectors.indexOf(COPILOT_PREMIUM_REQUEST_SKU) >= 0) {
476
+ type = "All Premium Request SKUs";
477
+ } else {
478
+ type = budget.name || "Copilot Premium Requests";
479
+ }
480
+ return "Budget - " + type;
481
+ }
482
+
483
+ function nextMonthResetIso() {
484
+ const now = new Date();
485
+ return new Date(now.getFullYear(), now.getMonth() + 1, 1).toISOString();
486
+ }
487
+
488
+ function budgetToLine(ctx, budget, resetIso) {
489
+ if (!budget || !(budget.budgetAmount > 0) || !hasCopilotSelector(budget.selectors || [])) return null;
490
+ const used = Math.max(0, budget.currentAmount || 0);
491
+ const limit = Math.max(0, budget.budgetAmount || 0);
492
+ return ctx.line.progress({
493
+ label: budgetTitle(budget),
494
+ used: used,
495
+ limit: limit,
496
+ format: { kind: "dollars" },
497
+ resetsAt: resetIso,
498
+ detail: "$" + used.toFixed(2) + " / $" + limit.toFixed(2),
499
+ });
500
+ }
501
+
502
+ function fetchBudgetLines(ctx) {
503
+ const cookieHeader = loadBudgetCookieHeader(ctx);
504
+ if (!cookieHeader) return [];
505
+
506
+ const nonce = fetchBudgetNonce(ctx, cookieHeader);
507
+ const budgets = [];
508
+ let page = 1;
509
+ let keepGoing = true;
510
+ while (keepGoing && page <= 20) {
511
+ const response = fetchBudgetPage(ctx, cookieHeader, nonce, page);
512
+ for (let i = 0; i < response.budgets.length; i += 1) {
513
+ budgets.push(response.budgets[i]);
514
+ }
515
+ keepGoing = response.hasNextPage === true;
516
+ page += 1;
517
+ }
518
+
519
+ const resetIso = nextMonthResetIso();
520
+ const lines = [];
521
+ for (let i = 0; i < budgets.length; i += 1) {
522
+ const line = budgetToLine(ctx, parseBudget(budgets[i]), resetIso);
523
+ if (line) lines.push(line);
524
+ }
525
+ return lines;
526
+ }
527
+
528
+ function makeProgressLine(ctx, label, snapshot, resetDate) {
529
+ if (!snapshot || typeof snapshot.percent_remaining !== "number")
530
+ return null;
531
+ const usedPercent = Math.min(100, Math.max(0, 100 - snapshot.percent_remaining));
532
+ return ctx.line.progress({
533
+ label: label,
534
+ used: usedPercent,
535
+ limit: 100,
536
+ format: { kind: "percent" },
537
+ resetsAt: ctx.util.toIso(resetDate),
538
+ periodDurationMs: 30 * 24 * 60 * 60 * 1000,
539
+ });
540
+ }
541
+
542
+ function makeLimitedProgressLine(ctx, label, remaining, total, resetDate) {
543
+ if (typeof remaining !== "number" || typeof total !== "number" || total <= 0)
544
+ return null;
545
+ const used = total - remaining;
546
+ const usedPercent = Math.min(100, Math.max(0, Math.round((used / total) * 100)));
547
+ return ctx.line.progress({
548
+ label: label,
549
+ used: usedPercent,
550
+ limit: 100,
551
+ format: { kind: "percent" },
552
+ resetsAt: ctx.util.toIso(resetDate),
553
+ periodDurationMs: 30 * 24 * 60 * 60 * 1000,
554
+ });
555
+ }
556
+
557
+ function probe(ctx) {
558
+ const cred = loadToken(ctx);
559
+ if (!cred) {
560
+ throw "Not logged in. Run `gh auth login` first.";
561
+ }
562
+
563
+ let token = cred.token;
564
+ let source = cred.source;
565
+
566
+ let resp;
567
+ try {
568
+ resp = fetchUsage(ctx, token);
569
+ } catch (e) {
570
+ ctx.host.log.error("usage request exception: " + String(e));
571
+ throw "Usage request failed. Check your connection.";
572
+ }
573
+
574
+ if (resp.status === 401 || resp.status === 403) {
575
+ // If cached token is stale, clear it and try fallback sources
576
+ if (source === "keychain") {
577
+ ctx.host.log.info("cached token invalid, trying fallback sources");
578
+ clearCachedToken(ctx);
579
+ const fallback = loadTokenFromGhCli(ctx);
580
+ if (fallback) {
581
+ try {
582
+ resp = fetchUsage(ctx, fallback.token);
583
+ } catch (e) {
584
+ ctx.host.log.error("fallback usage request exception: " + String(e));
585
+ throw "Usage request failed. Check your connection.";
586
+ }
587
+ if (resp.status >= 200 && resp.status < 300) {
588
+ // Fallback worked, persist the new token
589
+ saveToken(ctx, fallback.token);
590
+ token = fallback.token;
591
+ source = fallback.source;
592
+ }
593
+ }
594
+ }
595
+ // Still failing after retry
596
+ if (resp.status === 401 || resp.status === 403) {
597
+ throw "Token invalid. Run `gh auth login` to re-authenticate.";
598
+ }
599
+ }
600
+
601
+ if (resp.status < 200 || resp.status >= 300) {
602
+ ctx.host.log.error("usage returned error: status=" + resp.status);
603
+ throw (
604
+ "Usage request failed (HTTP " +
605
+ String(resp.status) +
606
+ "). Try again later."
607
+ );
608
+ }
609
+
610
+ // Persist gh-cli token to OpenUsage keychain for future use
611
+ if (source === "gh-cli" || source === "gh-command") {
612
+ saveToken(ctx, token);
613
+ }
614
+
615
+ const data = ctx.util.tryParseJson(resp.bodyText);
616
+ if (data === null) {
617
+ throw "Usage response invalid. Try again later.";
618
+ }
619
+
620
+ ctx.host.log.info("usage fetch succeeded");
621
+
622
+ const lines = [];
623
+ let plan = null;
624
+ if (data.copilot_plan) {
625
+ plan = ctx.fmt.planLabel(data.copilot_plan);
626
+ }
627
+
628
+ // Paid tier: quota_snapshots
629
+ const snapshots = data.quota_snapshots;
630
+ if (snapshots) {
631
+ const premiumLine = makeProgressLine(
632
+ ctx,
633
+ "Premium",
634
+ snapshots.premium_interactions,
635
+ data.quota_reset_date,
636
+ );
637
+ if (premiumLine) lines.push(premiumLine);
638
+
639
+ const chatLine = makeProgressLine(
640
+ ctx,
641
+ "Chat",
642
+ snapshots.chat,
643
+ data.quota_reset_date,
644
+ );
645
+ if (chatLine) lines.push(chatLine);
646
+ }
647
+
648
+ // Free tier: limited_user_quotas
649
+ if (data.limited_user_quotas && data.monthly_quotas) {
650
+ const lq = data.limited_user_quotas;
651
+ const mq = data.monthly_quotas;
652
+ const resetDate = data.limited_user_reset_date;
653
+
654
+ const chatLine = makeLimitedProgressLine(ctx, "Chat", lq.chat, mq.chat, resetDate);
655
+ if (chatLine) lines.push(chatLine);
656
+
657
+ const completionsLine = makeLimitedProgressLine(ctx, "Completions", lq.completions, mq.completions, resetDate);
658
+ if (completionsLine) lines.push(completionsLine);
659
+ }
660
+
661
+ try {
662
+ const budgetLines = fetchBudgetLines(ctx);
663
+ for (let i = 0; i < budgetLines.length; i += 1) {
664
+ lines.push(budgetLines[i]);
665
+ }
666
+ } catch (e) {
667
+ ctx.host.log.warn("Copilot budget extras failed: " + String(e));
668
+ }
669
+
670
+ if (lines.length === 0) {
671
+ lines.push(
672
+ ctx.line.badge({
673
+ label: "Status",
674
+ text: "No usage data",
675
+ color: "#a3a3a3",
676
+ }),
677
+ );
678
+ }
679
+
680
+ return { plan: plan, lines: lines };
681
+ }
682
+
683
+ globalThis.__openusage_plugin = { id: "copilot", probe };
684
+ })();
@@ -0,0 +1,21 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "copilot",
4
+ "name": "Copilot",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#A855F7",
9
+ "supportedModes": ["oauth", "local", "cli"],
10
+ "autoMode": "local",
11
+ "lines": [
12
+ { "type": "progress", "label": "Premium", "scope": "overview", "primaryOrder": 1 },
13
+ { "type": "progress", "label": "Chat", "scope": "overview", "primaryOrder": 2 },
14
+ { "type": "progress", "label": "Completions", "scope": "overview" },
15
+ { "type": "progress", "label": "Budget - Copilot", "scope": "detail" },
16
+ { "type": "progress", "label": "Budget - All Premium Request SKUs", "scope": "detail" },
17
+ { "type": "progress", "label": "Budget - Copilot Agent Premium Requests", "scope": "detail" },
18
+ { "type": "progress", "label": "Budget - Spark Premium Requests", "scope": "detail" },
19
+ { "type": "progress", "label": "Budget - Copilot Premium Requests", "scope": "detail" }
20
+ ]
21
+ }