@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.3 → 2.0.0-alpha.4

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 (142) hide show
  1. package/native-manifest.json +731 -65
  2. package/package.json +112 -1
  3. package/plugins/UPSTREAM-LICENSES.md +6 -2
  4. package/plugins/_provider-icons/LICENSE +21 -0
  5. package/plugins/_provider-icons/NOTICE +12 -0
  6. package/plugins/_provider-icons/licenses/CodexBar-MIT.txt +21 -0
  7. package/plugins/_provider-icons/licenses/LobeHub-MIT.txt +21 -0
  8. package/plugins/_provider-icons/licenses/UsageStat-Bar-MIT.txt +21 -0
  9. package/plugins/_provider-icons/manifest.json +1219 -0
  10. package/plugins/_provider-icons/provenance.json +269 -0
  11. package/plugins/_provider-icons/source.json +4 -0
  12. package/plugins/abacus-ai/icon-color.svg +18 -0
  13. package/plugins/aixy/LICENSE +21 -0
  14. package/plugins/aixy/plugin.js +261 -0
  15. package/plugins/aixy/plugin.json +24 -0
  16. package/plugins/antigravity/plugin.js +136 -46
  17. package/plugins/antigravity-cli/icon-color.svg +1 -0
  18. package/plugins/antigravity-cli/icon.svg +1 -3
  19. package/plugins/antigravity-cli/plugin.js +11 -15
  20. package/plugins/antigravity-ide/icon-color.svg +1 -0
  21. package/plugins/antigravity-ide/icon.svg +1 -3
  22. package/plugins/atlascloud/LICENSE +21 -0
  23. package/plugins/atlascloud/icon-color.svg +1 -0
  24. package/plugins/atlascloud/icon.svg +1 -0
  25. package/plugins/atlascloud/plugin.js +49 -0
  26. package/plugins/atlascloud/plugin.json +23 -0
  27. package/plugins/augment/icon-color.svg +14 -0
  28. package/plugins/bifrost/LICENSE +21 -0
  29. package/plugins/bifrost/plugin.js +361 -0
  30. package/plugins/bifrost/plugin.json +23 -0
  31. package/plugins/claude/plugin.js +302 -152
  32. package/plugins/claude/plugin.json +1 -1
  33. package/plugins/clinepass/plugin.js +19 -3
  34. package/plugins/codebuff/icon-color.svg +92 -0
  35. package/plugins/coderabbit/plugin.js +25 -0
  36. package/plugins/coderabbit/plugin.json +12 -0
  37. package/plugins/codex/plugin.js +1 -0
  38. package/plugins/command-code/icon-color.svg +3 -0
  39. package/plugins/command-code/plugin.js +7 -3
  40. package/plugins/copilot/icon.svg +1 -1
  41. package/plugins/crof/icon-color.svg +3 -0
  42. package/plugins/crofai/icon-color.svg +3 -0
  43. package/plugins/crofai/icon.svg +2 -15
  44. package/plugins/cursor/icon-color.svg +1 -0
  45. package/plugins/deepinfra/icon-color.svg +1 -0
  46. package/plugins/deepinfra/icon.svg +1 -4
  47. package/plugins/deepseek/plugin.js +150 -59
  48. package/plugins/deepseek/plugin.json +3 -1
  49. package/plugins/devin/plugin.js +3 -0
  50. package/plugins/devpass/LICENSE +21 -0
  51. package/plugins/devpass/plugin.js +115 -0
  52. package/plugins/devpass/plugin.json +23 -0
  53. package/plugins/elevenlabs/icon-color.svg +4 -0
  54. package/plugins/factory/icon-color.svg +1 -0
  55. package/plugins/fireworks-ai/icon-color.svg +1 -0
  56. package/plugins/fireworks-ai/icon.svg +1 -6
  57. package/plugins/gemini-apps/LICENSE +22 -0
  58. package/plugins/gemini-apps/icon-color.svg +1 -0
  59. package/plugins/gemini-apps/icon.svg +1 -0
  60. package/plugins/gemini-apps/plugin.js +470 -0
  61. package/plugins/gemini-apps/plugin.json +19 -0
  62. package/plugins/gitkraken/LICENSE +21 -0
  63. package/plugins/gitkraken/plugin.js +153 -0
  64. package/plugins/gitkraken/plugin.json +24 -0
  65. package/plugins/grok/icon-color.svg +1 -0
  66. package/plugins/groqcloud/icon-color.svg +1 -0
  67. package/plugins/helmcode/LICENSE +21 -0
  68. package/plugins/helmcode/plugin.js +188 -0
  69. package/plugins/helmcode/plugin.json +24 -0
  70. package/plugins/huggingface/LICENSE +21 -0
  71. package/plugins/huggingface/icon-color.svg +1 -0
  72. package/plugins/huggingface/icon.svg +1 -0
  73. package/plugins/huggingface/plugin.js +293 -0
  74. package/plugins/huggingface/plugin.json +25 -0
  75. package/plugins/hyper/LICENSE +21 -0
  76. package/plugins/hyper/plugin.js +131 -0
  77. package/plugins/hyper/plugin.json +26 -0
  78. package/plugins/kilo/icon-color.svg +1 -0
  79. package/plugins/litellm/plugin.js +54 -3
  80. package/plugins/llmman/LICENSE +21 -0
  81. package/plugins/llmman/plugin.js +133 -0
  82. package/plugins/llmman/plugin.json +23 -0
  83. package/plugins/longcat/icon-color.svg +1 -0
  84. package/plugins/longcat/icon.svg +1 -4
  85. package/plugins/manus/icon-color.svg +6 -0
  86. package/plugins/mimo/icon-color.svg +4 -0
  87. package/plugins/mimo/icon.svg +4 -1
  88. package/plugins/mistral/plugin.js +110 -5
  89. package/plugins/moonshot/icon-color.svg +1 -0
  90. package/plugins/muse/LICENSE +21 -0
  91. package/plugins/muse/plugin.js +319 -0
  92. package/plugins/muse/plugin.json +26 -0
  93. package/plugins/nous/LICENSE +21 -0
  94. package/plugins/nous/icon-color.svg +1 -0
  95. package/plugins/nous/icon.svg +1 -0
  96. package/plugins/nous/plugin.js +124 -0
  97. package/plugins/nous/plugin.json +24 -0
  98. package/plugins/ollama/icon-color.svg +1 -0
  99. package/plugins/openai-api/icon-color.svg +1 -0
  100. package/plugins/opencode/icon-color.svg +1 -0
  101. package/plugins/opencode-go/icon-color.svg +1 -0
  102. package/plugins/opencode-go/plugin.js +30 -12
  103. package/plugins/openrouter/icon-color.svg +1 -0
  104. package/plugins/pi/plugin.js +113 -0
  105. package/plugins/pi/plugin.json +11 -0
  106. package/plugins/qwencloud/icon-color.svg +1 -0
  107. package/plugins/qwencloud/icon.svg +1 -4
  108. package/plugins/raycast/LICENSE +21 -0
  109. package/plugins/raycast/plugin.js +174 -0
  110. package/plugins/raycast/plugin.json +24 -0
  111. package/plugins/replicate/LICENSE +21 -0
  112. package/plugins/replicate/icon-color.svg +1 -0
  113. package/plugins/replicate/icon.svg +1 -0
  114. package/plugins/replicate/plugin.js +165 -0
  115. package/plugins/replicate/plugin.json +24 -0
  116. package/plugins/synthetic/icon-color.svg +14 -0
  117. package/plugins/typesafe/LICENSE +21 -0
  118. package/plugins/typesafe/plugin.js +225 -0
  119. package/plugins/typesafe/plugin.json +24 -0
  120. package/plugins/v0/LICENSE +21 -0
  121. package/plugins/v0/icon-color.svg +1 -0
  122. package/plugins/v0/icon.svg +1 -0
  123. package/plugins/v0/plugin.js +214 -0
  124. package/plugins/v0/plugin.json +24 -0
  125. package/plugins/venice/plugin.js +50 -1
  126. package/plugins/venice/plugin.json +2 -1
  127. package/plugins/vercel/LICENSE +21 -0
  128. package/plugins/vercel/icon-color.svg +1 -0
  129. package/plugins/vercel/icon.svg +1 -0
  130. package/plugins/vercel/plugin.js +55 -0
  131. package/plugins/vercel/plugin.json +23 -0
  132. package/plugins/windsurf/icon-color.svg +1 -0
  133. package/plugins/xai/icon-color.svg +1 -0
  134. package/plugins/xai/icon.svg +1 -7
  135. package/plugins/xkiro/LICENSE +21 -0
  136. package/plugins/xkiro/plugin.js +99 -0
  137. package/plugins/xkiro/plugin.json +23 -0
  138. package/plugins/zai/icon-color.svg +1 -0
  139. package/plugins/zenmux/icon-color.svg +1 -0
  140. package/plugins/zenmux/icon.svg +1 -3
  141. package/usagestat +0 -0
  142. package/usagestatd +0 -0
@@ -0,0 +1,293 @@
1
+ // Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
2
+ globalThis.__usagestat_bundled_options = {"labels":["Inference","ZeroGPU","Additional"],"defaults":{},"autoMode":"api"};
3
+ function _nullishCoalesce(lhs, rhsFn) {
4
+ if (lhs != null) {
5
+ return lhs;
6
+ } else {
7
+ return rhsFn();
8
+ }
9
+ }
10
+ function _optionalChain(ops) {
11
+ let lastAccessLHS = undefined;
12
+ let value = ops[0];
13
+ let i = 1;
14
+ while (i < ops.length) {
15
+ const op = ops[i];
16
+ const fn = ops[i + 1];
17
+ i += 2;
18
+ if ((op === "optionalAccess" || op === "optionalCall") && value == null) {
19
+ return undefined;
20
+ }
21
+ if (op === "access" || op === "optionalAccess") {
22
+ lastAccessLHS = value;
23
+ value = fn(value);
24
+ } else if (op === "call" || op === "optionalCall") {
25
+ value = fn((...args) => value.call(lastAccessLHS, ...args));
26
+ lastAccessLHS = undefined;
27
+ }
28
+ }
29
+ return value;
30
+ }
31
+
32
+ defineProvider({
33
+ id: "huggingface",
34
+ name: "Hugging Face",
35
+ endpoints: ["https://huggingface.co"],
36
+ settings: [{ key: "HF_TOKEN", title: "Access token", type: "secure" }],
37
+ capabilities: ["browser-cookies", "http-status"],
38
+ cookieDomains: ["huggingface.co"],
39
+ async fetchUsage(ctx) {
40
+ const fail = (field) => {
41
+ throw ctx.fail.parseFailure(`Hugging Face billing response format changed: ${field}`);
42
+ };
43
+ const object = (value, field) => {
44
+ if (!value || typeof value !== "object" || Array.isArray(value)) return fail(field);
45
+ return value;
46
+ };
47
+ const number = (value, field) => {
48
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return fail(field);
49
+ return value;
50
+ };
51
+ const optionalNumber = (value, field) => (value === undefined || value === null ? undefined : number(value, field));
52
+ const parse = (body) => {
53
+ let value;
54
+ try {
55
+ value = JSON.parse(body);
56
+ } catch (error) {
57
+ void error;
58
+ return fail("invalid JSON");
59
+ }
60
+ return object(value, "expected an object");
61
+ };
62
+ const date = (value) => {
63
+ if (value === undefined || value === null) return undefined;
64
+ if (typeof value === "number") {
65
+ if (!Number.isFinite(value) || value <= 0 || value > 64092211200) return undefined;
66
+ return ctx.date.unixSeconds(value);
67
+ }
68
+ if (typeof value === "string") {
69
+ try {
70
+ return ctx.date.iso(value);
71
+ } catch (error) {
72
+ void error;
73
+ }
74
+ }
75
+ return undefined;
76
+ };
77
+ const text = (value) => (typeof value === "string" ? value.trim() || undefined : undefined);
78
+ const userID = (profile) =>
79
+ profile.type === "user" && typeof profile.id === "string" && profile.id.trim() ? profile.id : undefined;
80
+ // Keep bearer authority off requests that verify the browser session.
81
+ const token = ctx.settings.getSecret("HF_TOKEN");
82
+ if (!token) throw ctx.fail.missingCredential("Missing Hugging Face access token.");
83
+ const apiHeaders = { Authorization: `Bearer ${token}`, "User-Agent": "CodexBar" };
84
+ const now = ctx.date.now();
85
+ const start = Math.floor(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1) / 1000);
86
+ const end = Math.floor(now.getTime() / 1000);
87
+ const response = await ctx.http.get(
88
+ `https://huggingface.co/api/settings/billing/usage-v2?startDate=${start}&endDate=${end}`,
89
+ { timeoutSeconds: 15, headers: apiHeaders },
90
+ );
91
+ if (response.status === 401) {
92
+ throw ctx.fail.authenticationExpired("Hugging Face rejected the token. Check that it is valid and not expired.");
93
+ }
94
+ if (response.status === 403) {
95
+ throw ctx.fail.permissionDenied(
96
+ "The Hugging Face token lacks billing access. Use a classic read token or enable Billing read on a fine-grained token.",
97
+ );
98
+ }
99
+ if (response.status === 429) throw ctx.fail.rateLimited("Hugging Face rate limit reached. Retry later.");
100
+ if (response.status >= 500)
101
+ throw ctx.fail.providerUnavailable(`Hugging Face API returned HTTP ${response.status}.`);
102
+ if (response.status < 200 || response.status >= 300) {
103
+ throw ctx.fail.apiFailure(`Hugging Face API returned HTTP ${response.status}.`);
104
+ }
105
+ const usage = object(parse(response.bodyText).usage, "missing usage");
106
+ const inference = object(usage.inferenceProviders, "missing inferenceProviders usage");
107
+ const gross = number(inference.usedNanoUsd, "usedNanoUsd") / 1e9;
108
+ const included = number(inference.includedNanoUsd, "includedNanoUsd") / 1e9;
109
+ const billable = Math.max(0, gross - included);
110
+ const limit = _nullishCoalesce(optionalNumber(inference.limitNanoUsd, "limitNanoUsd"), () => 0) / 1e9;
111
+ const requests = optionalNumber(inference.numRequests, "numRequests");
112
+ if (requests !== undefined && !Number.isSafeInteger(requests)) return fail("numRequests");
113
+ let secondary;
114
+ let gpuRows = [];
115
+ try {
116
+ const gpuResponse = await ctx.http.get("https://huggingface.co/api/spaces/zero-gpu/quota", {
117
+ timeoutSeconds: 2,
118
+ headers: apiHeaders,
119
+ });
120
+ if (gpuResponse.status >= 200 && gpuResponse.status < 300) {
121
+ const gpu = parse(gpuResponse.bodyText);
122
+ const total = number(gpu.base, "ZeroGPU base");
123
+ const remaining = number(gpu.current, "ZeroGPU current");
124
+ if (total > 0) {
125
+ const consumed = Math.max(0, total - remaining);
126
+ secondary = {
127
+ usedPercent: ctx.pct(consumed, total),
128
+ resetsAt: date(gpu.resetsAt),
129
+ resetDescription: "ZeroGPU quota",
130
+ };
131
+ const minutes = (seconds) =>
132
+ `${ctx.format.number(seconds / 60, {
133
+ minimumFractionDigits: seconds >= 600 ? 0 : 1,
134
+ maximumFractionDigits: seconds >= 600 ? 0 : 1,
135
+ })} min`;
136
+ gpuRows = [
137
+ { label: "GPU time used", value: minutes(consumed) },
138
+ { label: "GPU time remaining", value: minutes(remaining) },
139
+ ];
140
+ }
141
+ }
142
+ } catch (error) {
143
+ void error;
144
+ }
145
+ const cacheKey = "whoami-v2:" + token;
146
+ let identity = ctx.cache.get(cacheKey);
147
+ if (!identity || now.getTime() - identity.fetchedAt < 0 || now.getTime() - identity.fetchedAt >= 43200000) {
148
+ identity = undefined;
149
+ try {
150
+ const whoami = await ctx.http.get("https://huggingface.co/api/whoami-v2", {
151
+ timeoutSeconds: 2,
152
+ headers: apiHeaders,
153
+ });
154
+ if (whoami.status >= 200 && whoami.status < 300) {
155
+ const profile = parse(whoami.bodyText);
156
+ const username = text(profile.name);
157
+ const email = text(profile.email);
158
+ if (username || email || userID(profile)) {
159
+ identity = {
160
+ fetchedAt: now.getTime(),
161
+ userID: userID(profile),
162
+ username,
163
+ email,
164
+ plan: typeof profile.isPro === "boolean" ? (profile.isPro ? "PRO" : "Free") : undefined,
165
+ };
166
+ ctx.cache.set(cacheKey, identity, 43200);
167
+ }
168
+ }
169
+ } catch (error) {
170
+ void error;
171
+ }
172
+ }
173
+ let balance;
174
+ const availability = ctx.browser.availability("huggingface.co");
175
+ if (
176
+ _optionalChain([identity, "optionalAccess", (_2) => _2.userID]) &&
177
+ (availability === "available" || availability === "manual")
178
+ ) {
179
+ try {
180
+ const cookie = await ctx.browser.cookieHeader("huggingface.co");
181
+ const web = async (path, accept) => {
182
+ const result = await ctx.http.get(`https://huggingface.co${path}`, {
183
+ headers: { Cookie: cookie, Accept: accept },
184
+ timeoutSeconds: 2,
185
+ });
186
+ if (result.status === 401 || result.status === 403) ctx.browser.rejectCookie("huggingface.co");
187
+ if (result.status !== 200) return fail("wallet unavailable");
188
+ return result;
189
+ };
190
+ const billing = await web("/settings/billing", "text/html");
191
+ if (
192
+ !_optionalChain([
193
+ billing,
194
+ "access",
195
+ (_3) => _3.headers,
196
+ "access",
197
+ (_4) => _4["content-type"],
198
+ "optionalAccess",
199
+ (_5) => _5.toLowerCase,
200
+ "call",
201
+ (_6) => _6(),
202
+ "access",
203
+ (_7) => _7.includes,
204
+ "call",
205
+ (_8) => _8("text/html"),
206
+ ])
207
+ )
208
+ return fail("wallet content type");
209
+ const current = [];
210
+ const legacy = [];
211
+ const entities = new Map([
212
+ ["amp", "&"],
213
+ ["apos", "'"],
214
+ ["gt", ">"],
215
+ ["lt", "<"],
216
+ ["nbsp", "\u00a0"],
217
+ ["quot", '"'],
218
+ ]);
219
+ const decode = (raw) =>
220
+ raw.replace(/&([^;]*);/g, (_, entity) => {
221
+ const named = entities.get(entity);
222
+ if (named !== undefined) return named;
223
+ const scalar = /^#x[0-9a-f]+$/i.test(entity)
224
+ ? parseInt(entity.slice(2), 16)
225
+ : /^#[0-9]+$/.test(entity)
226
+ ? Number(entity.slice(1))
227
+ : NaN;
228
+ if (!Number.isInteger(scalar) || scalar > 0x10ffff || (scalar >= 0xd800 && scalar <= 0xdfff))
229
+ return fail("wallet HTML entity");
230
+ return String.fromCodePoint(scalar);
231
+ });
232
+ for (const tag of _nullishCoalesce(billing.bodyText.match(/<div\b[^>]*>/gi), () => [])) {
233
+ const props = /\bdata-props\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i.exec(tag);
234
+ if (!props) continue;
235
+ const payload = JSON.parse(
236
+ decode(
237
+ _nullishCoalesce(
238
+ _nullishCoalesce(props[1], () => props[2]),
239
+ () => props[3],
240
+ ),
241
+ ),
242
+ );
243
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) continue;
244
+ const entity = payload.entity;
245
+ if (entity && Object.prototype.hasOwnProperty.call(entity, "currentBalanceUsd")) {
246
+ if (entity.type !== "user") return fail("wallet entity type");
247
+ current.push(entity.currentBalanceUsd);
248
+ }
249
+ if (Object.prototype.hasOwnProperty.call(payload, "invoiceCreditsCents"))
250
+ legacy.push(payload.invoiceCreditsCents);
251
+ }
252
+ let candidate;
253
+ if (current.length) {
254
+ if (current.length !== 1) return fail("ambiguous current wallet");
255
+ candidate = number(current[0], "currentBalanceUsd");
256
+ } else {
257
+ if (legacy.length !== 1) return fail("missing or ambiguous legacy wallet");
258
+ const cents = number(legacy[0], "invoiceCreditsCents");
259
+ if (!Number.isSafeInteger(cents)) return fail("invoiceCreditsCents");
260
+ candidate = cents / 100;
261
+ }
262
+ // The billing page's display names are not ownership proof. Resolve the same cookie without a bearer.
263
+ const profile = parse((await web("/api/whoami-v2", "application/json")).bodyText);
264
+ if (userID(profile) === identity.userID) balance = candidate;
265
+ } catch (error) {
266
+ void error;
267
+ }
268
+ }
269
+ // usage-v2 reports a query interval; its cutoff is not a quota reset.
270
+ // HF's billing UI deducts includedNanoUsd from usedNanoUsd to calculate the charge.
271
+ const rows = [{ label: "Billable usage", value: ctx.format.usd(billable) }];
272
+ if (included > 0) {
273
+ rows.push({ label: "Gross inference usage", value: ctx.format.usd(gross) });
274
+ rows.push({ label: "Included inference amount", value: ctx.format.usd(included) });
275
+ }
276
+ if (limit > 0) rows.push({ label: "Spending limit", value: ctx.format.usd(limit) });
277
+ if (requests !== undefined) rows.push({ label: "Requests", value: String(requests) });
278
+ const details = [{ title: "Inference Providers", rows }];
279
+ if (gpuRows.length) details.push({ title: "ZeroGPU", rows: gpuRows });
280
+ if (balance !== undefined)
281
+ details.push({ title: "Credits", rows: [{ label: "Prepaid balance", value: ctx.format.usd(balance) }] });
282
+ return {
283
+ secondary,
284
+ cost: { used: billable, balance, limit: limit > 0 ? limit : undefined, currency: "USD", period: "This month" },
285
+ details,
286
+ identity:
287
+ identity && (identity.username || identity.email || identity.plan)
288
+ ? { email: identity.email, accountID: identity.username, loginMethod: identity.plan }
289
+ : undefined,
290
+ dataConfidence: "exact",
291
+ };
292
+ },
293
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "huggingface",
4
+ "name": "Hugging Face",
5
+ "version": "0.1.0",
6
+ "entry": "plugin.js",
7
+ "enabledByDefault": false,
8
+ "supportedModes": [
9
+ "api"
10
+ ],
11
+ "autoMode": "api",
12
+ "usageDashboardUrl": "https://huggingface.co/settings/billing",
13
+ "webUrl": "https://huggingface.co",
14
+ "envVars": [
15
+ "HF_TOKEN",
16
+ "HUGGINGFACE_COOKIE"
17
+ ],
18
+ "upstream": {
19
+ "repository": "https://github.com/steipete/CodexBar",
20
+ "revision": "78ba5a1e65e6859da59a21b799df95ab91bc0b89",
21
+ "path": "Sources/CodexBarCore/Resources/Plugins/huggingface.js",
22
+ "sha256": "78cb1bb09535c71f7cb9d1a6336efb4ead44b36a1d4716602c1f7a1e35821291",
23
+ "license": "MIT"
24
+ }
25
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Steinberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,131 @@
1
+ // Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
2
+ globalThis.__usagestat_bundled_options = {"labels":["Balance","Balance","Additional"],"defaults":{},"autoMode":"api"};
3
+ function _optionalChain(ops) {
4
+ let lastAccessLHS = undefined;
5
+ let value = ops[0];
6
+ let i = 1;
7
+ while (i < ops.length) {
8
+ const op = ops[i];
9
+ const fn = ops[i + 1];
10
+ i += 2;
11
+ if ((op === "optionalAccess" || op === "optionalCall") && value == null) {
12
+ return undefined;
13
+ }
14
+ if (op === "access" || op === "optionalAccess") {
15
+ lastAccessLHS = value;
16
+ value = fn(value);
17
+ } else if (op === "call" || op === "optionalCall") {
18
+ value = fn((...args) => value.call(lastAccessLHS, ...args));
19
+ lastAccessLHS = undefined;
20
+ }
21
+ }
22
+ return value;
23
+ }
24
+ defineProvider({
25
+ id: "hyper",
26
+ name: "Charm Hyper",
27
+ endpoints: ["https://hyper.charm.land"],
28
+ capabilities: ["browser-cookies", "http-status"],
29
+ cookieDomains: ["hyper.charm.land"],
30
+ settings: [
31
+ { key: "HYPER_API_KEY", title: "API key", type: "secure" },
32
+ { key: "SOURCE_MODE", title: "Source", type: "plain" },
33
+ ],
34
+ async fetchUsage(ctx) {
35
+ const domain = "hyper.charm.land";
36
+ const key = ctx.settings.getSecret("HYPER_API_KEY");
37
+ const webOnly = ctx.settings.get("SOURCE_MODE") === "web";
38
+ const missing = () => {
39
+ throw ctx.fail.missingCredential("Sign in to hyper.charm.land or configure a Charm Hyper API key.");
40
+ };
41
+ const request = (headers, timeoutSeconds = 10) =>
42
+ ctx.http.get(`https://${domain}/v1/credits`, {
43
+ headers,
44
+ timeoutSeconds,
45
+ });
46
+ let response;
47
+ let session = false;
48
+ if (ctx.browser.availability(domain) !== "off") {
49
+ try {
50
+ const cookie = await ctx.browser.cookieHeader(domain);
51
+ session = true;
52
+ response = await request({ Cookie: cookie }, 5);
53
+ } catch (error) {
54
+ if (error.transportClass === "cancelled") throw error;
55
+ if (webOnly || !key) {
56
+ if (session) throw ctx.fail.networkFailure("Charm Hyper session request failed.");
57
+ return missing();
58
+ }
59
+ }
60
+ if (
61
+ response &&
62
+ (response.status === 401 ||
63
+ response.status === 403 ||
64
+ (response.status >= 300 && response.status < 400) ||
65
+ (response.status === 200 &&
66
+ _optionalChain([
67
+ response,
68
+ "access",
69
+ (_) => _.headers,
70
+ "access",
71
+ (_2) => _2["content-type"],
72
+ "optionalAccess",
73
+ (_3) => _3.toLowerCase,
74
+ "call",
75
+ (_4) => _4(),
76
+ "access",
77
+ (_5) => _5.includes,
78
+ "call",
79
+ (_6) => _6("text/html"),
80
+ ])))
81
+ ) {
82
+ ctx.browser.rejectCookie(domain);
83
+ if (webOnly || !key) {
84
+ throw ctx.fail.authenticationExpired(
85
+ "Charm Hyper session expired. Sign in again or paste a fresh Cookie header.",
86
+ );
87
+ }
88
+ response = undefined;
89
+ }
90
+ }
91
+ if (!response) {
92
+ if (webOnly || !key) return missing();
93
+ response = await request({ Authorization: `Bearer ${key}` });
94
+ session = false;
95
+ }
96
+ if (response.status === 401) throw ctx.fail.authenticationExpired("Charm Hyper API key rejected (HTTP 401).");
97
+ if (response.status === 403)
98
+ throw ctx.fail.permissionDenied("Charm Hyper API key cannot access credits (HTTP 403).");
99
+ if (response.status === 429) throw ctx.fail.rateLimited("Charm Hyper credits requests are rate limited.");
100
+ if (response.status >= 500) throw ctx.fail.providerUnavailable(`Charm Hyper API error: HTTP ${response.status}`);
101
+ if (response.status !== 200) throw ctx.fail.apiFailure(`Charm Hyper API error: HTTP ${response.status}`);
102
+
103
+ let payload;
104
+ try {
105
+ payload = JSON.parse(response.bodyText);
106
+ } catch (error) {
107
+ void error;
108
+ throw ctx.fail.parseFailure("Charm Hyper credits response is not valid JSON.");
109
+ }
110
+ const balance = _optionalChain([payload, "optionalAccess", (_7) => _7.balance]);
111
+ if (typeof balance !== "number" || !Number.isFinite(balance) || balance < 0) {
112
+ throw ctx.fail.parseFailure("Charm Hyper balance must be a non-negative number.");
113
+ }
114
+ return {
115
+ details: [
116
+ {
117
+ title: "Hypercredits",
118
+ rows: [
119
+ {
120
+ label: "Balance",
121
+ value: `${ctx.format.number(balance, { maximumFractionDigits: 2 })} HC`,
122
+ usageValue: balance,
123
+ },
124
+ ],
125
+ },
126
+ ],
127
+ identity: { loginMethod: session ? "Browser session" : "API key" },
128
+ dataConfidence: "exact",
129
+ };
130
+ },
131
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "hyper",
4
+ "name": "Charm Hyper",
5
+ "version": "0.1.0",
6
+ "entry": "plugin.js",
7
+ "enabledByDefault": false,
8
+ "supportedModes": [
9
+ "api",
10
+ "web"
11
+ ],
12
+ "autoMode": "api",
13
+ "usageDashboardUrl": "https://hyper.charm.land",
14
+ "webUrl": "https://hyper.charm.land",
15
+ "envVars": [
16
+ "HYPER_API_KEY",
17
+ "HYPER_COOKIE"
18
+ ],
19
+ "upstream": {
20
+ "repository": "https://github.com/steipete/CodexBar",
21
+ "revision": "78ba5a1e65e6859da59a21b799df95ab91bc0b89",
22
+ "path": "Sources/CodexBarCore/Resources/Plugins/hyper.js",
23
+ "sha256": "893e1a9a91e7af57522bcc39791d77d6fe3b8ae0d61384aee1768f5f419636cd",
24
+ "license": "MIT"
25
+ }
26
+ }
@@ -0,0 +1 @@
1
+ <svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Kilo Code</title><path d="M0 0v24h24V0H0zm22.222 22.222H1.778V1.778h20.444v20.444zm-7.555-4.964h2.222v1.778h-2.794L12.89 17.83v-2.794h1.778v2.222zm4 0h-1.778v-2.222h-2.222v-1.778h2.793l1.207 1.207v2.793zm-7.556-2.591H9.333v-1.778h1.778v1.778zm-5.778-1.778h1.778v4h4v1.778H6.54L5.333 17.46V12.89zm13.334-3.556v1.778h-5.778V9.333h1.987V7.111h-1.987V5.333h2.558l1.206 1.207v2.793h2.014zm-11.556-2h2.222l1.778 1.778v2H9.333v-2H7.111v2H5.333V5.333h1.778v2zm4 0H9.333v-2h1.778v2z"></path></svg>
@@ -9,6 +9,8 @@
9
9
  }
10
10
 
11
11
  function apiKey(ctx) {
12
+ if (Object.prototype.hasOwnProperty.call(ctx.provider || {}, 'apiKey')) return ctx.provider.apiKey;
13
+ if (ctx.provider && ctx.provider.instanceId && ctx.provider.instanceId !== 'litellm') return null;
12
14
  var configured = ctx.provider && typeof ctx.provider.apiKey === "string" ? ctx.provider.apiKey.trim() : "";
13
15
  return configured || env(ctx, "LITELLM_API_KEY");
14
16
  }
@@ -51,13 +53,14 @@
51
53
  return Number.isFinite(ms) ? new Date(ms) : null;
52
54
  }
53
55
 
54
- function requestJson(ctx, base, path, key) {
56
+ function requestJson(ctx, base, path, key, optional) {
55
57
  var resp = ctx.util.request({
56
58
  method: "GET",
57
59
  url: base + path,
58
60
  headers: { Authorization: "Bearer " + key, Accept: "application/json" },
59
61
  timeoutMs: 15000,
60
62
  });
63
+ if (optional && [401,403,404].includes(resp.status)) return null;
61
64
  if (ctx.util.isAuthStatus(resp.status)) throw "LiteLLM API key was rejected.";
62
65
  if (resp.status < 200 || resp.status >= 300) {
63
66
  throw "LiteLLM API request failed (HTTP " + resp.status + ").";
@@ -189,10 +192,58 @@
189
192
  var key = apiKey(ctx);
190
193
  if (!key) throw "Missing LiteLLM API key. Set LITELLM_API_KEY or provider apiKey.";
191
194
  var base = baseUrl(ctx);
192
- var info = keyInfo(requestJson(ctx, base, "/key/info", key));
195
+ var keyData = requestJson(ctx, base, "/key/info", key, true);
196
+ if (keyData === null) {
197
+ var end = ctx.nowIso.slice(0,10), start = end.slice(0,7)+'-01';
198
+ var query = '?start_date='+start+'&end_date='+end;
199
+ var rows = requestJson(ctx, base, '/key/spend/report'+query, key, true);
200
+ if (rows === null) rows = requestJson(ctx, base, '/user/spend/report'+query, key);
201
+ if (!Array.isArray(rows) || !rows.length) throw new Error('LiteLLM spend report is empty or invalid.');
202
+ var cost = 0;
203
+ for (var row of rows) {
204
+ if (typeof row.total_cost !== 'number' || !Number.isFinite(row.total_cost) || row.total_cost < 0) throw new Error('Invalid LiteLLM spend.');
205
+ cost += row.total_cost;
206
+ }
207
+ if (!Number.isFinite(cost)) throw new Error('Invalid LiteLLM spend total.');
208
+ return {source:'api',lines:[ctx.line.text({label:'Month to date',value:usd(cost),subtitle:start+' to '+end+' UTC'})]};
209
+ }
210
+ var info = keyInfo(keyData);
193
211
  var snapshot = info.userID ? userSnapshot(ctx, base, key, info) : teamSnapshot(ctx, base, key, info);
194
212
  var plan = snapshot.team && snapshot.team.alias ? snapshot.team.alias : null;
195
- return { displayName: "LiteLLM", source: "api", plan: plan, lines: linesFor(snapshot) };
213
+ var lines = linesFor(snapshot);
214
+ if (info.userID && (ctx.provider?.settings?.modelUsage === true || ctx.provider?.settings?.LITELLM_MODEL_USAGE_ENABLED === 'true')) {
215
+ try { lines.push.apply(lines, modelActivity(ctx,base,key,info.userID)); } catch (_) { ctx.host.log.warn('LiteLLM optional model activity unavailable.'); }
216
+ }
217
+ return { displayName: "LiteLLM", source: "api", plan: plan, lines: lines };
218
+ }
219
+
220
+ function modelActivity(ctx,base,key,user) {
221
+ var end=ctx.nowIso.slice(0,10),start=new Date(Date.parse(end)-29*86400000).toISOString().slice(0,10),totals={};
222
+ for(var page=1;page<=3;page++) {
223
+ var response=ctx.host.http.request({method:'GET',url:base+'/user/daily/activity?user_id='+encodeURIComponent(user)+'&start_date='+start+'&end_date='+end+'&page='+page+'&page_size=1000',
224
+ headers:{Authorization:'Bearer '+key,Accept:'application/json'},timeoutMs:2000});
225
+ if(response.status!==200) throw new Error('Unavailable');
226
+ var data=ctx.util.tryParseJson(response.bodyText);
227
+ if(!Array.isArray(data?.results)||data.results.length>31) throw new Error('Invalid activity');
228
+ for(var row of data.results) {
229
+ if(!/^\d{4}-\d{2}-\d{2}$/.test(row.date)||row.date<start||row.date>end||!row.breakdown?.models) throw new Error('Invalid day');
230
+ for(var [name, value] of Object.entries(row.breakdown.models)) {
231
+ if(!name.trim()||name.length>256||/[\x00-\x1f]/.test(name)||['__proto__','constructor','prototype'].includes(name)) throw new Error('Invalid model');
232
+ var metrics=value.metrics||value,prior=totals[name]||[0,0,0,0];
233
+ totals[name]=['prompt_tokens','completion_tokens','total_tokens','api_requests'].map(function(field,i){
234
+ var n=metrics[field]; if(!Number.isSafeInteger(n)||n<0||!Number.isSafeInteger(n+prior[i])) throw new Error('Invalid count'); return n+prior[i];
235
+ });
236
+ if(Object.keys(totals).length>1000) throw new Error('Too many models');
237
+ }
238
+ }
239
+ var metadata=data.metadata||{};
240
+ if(metadata.page!=null&&metadata.page!==page) throw new Error('Repeated page');
241
+ if(metadata.has_more!=null&&typeof metadata.has_more!=='boolean') throw new Error('Invalid page');
242
+ if(metadata.total_pages!=null&&(!Number.isSafeInteger(metadata.total_pages)||metadata.total_pages<1)) throw new Error('Invalid page');
243
+ if(metadata.has_more!==true&&page>=(metadata.total_pages||1)) return Object.entries(totals).sort((a,b)=>b[1][2]-a[1][2]).slice(0,20)
244
+ .map(([name,n])=>ctx.line.text({label:name,value:n[2]+' tokens / '+n[3]+' requests',subtitle:'30d UTC: '+n[0]+' input / '+n[1]+' output'}));
245
+ }
246
+ throw new Error('Incomplete activity');
196
247
  }
197
248
 
198
249
  globalThis.__openusage_plugin = { id: "litellm", probe: probe };
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Steinberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.