@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,115 @@
1
+ // Generated by tools/vendor-codexbar-providers.cjs; see LICENSE.
2
+ globalThis.__usagestat_bundled_options = {"labels":["Plan credits","Premium weekly","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
+ defineProvider({
32
+ id: "devpass",
33
+ name: "DevPass",
34
+ endpoints: ["https://api.llmgateway.io"],
35
+ auth: { type: "bearer", secret: "DEVPASS_API_KEY" },
36
+ settings: [{ key: "DEVPASS_API_KEY", title: "DevPass API key", type: "secure" }],
37
+ capabilities: ["http-status"],
38
+ async fetchUsage(ctx) {
39
+ const response = await ctx.http.get("https://api.llmgateway.io/v1/key");
40
+ if (response.status === 401) throw ctx.fail.authenticationExpired("DevPass API key was rejected or is inactive.");
41
+ if (response.status === 403) throw ctx.fail.permissionDenied("DevPass requires a regular gateway API key.");
42
+ if (response.status === 429) {
43
+ const delay = Number(_nullishCoalesce(response.headers["retry-after"], () => 1));
44
+ throw ctx.fail.rateLimited("DevPass usage requests are rate limited.", {
45
+ retryAfterSeconds: Number.isFinite(delay) && delay >= 0 ? Math.min(delay, 10) : 1,
46
+ });
47
+ }
48
+ if (response.status >= 500) throw ctx.fail.providerUnavailable("DevPass usage is temporarily unavailable.");
49
+ if (response.status !== 200) throw ctx.fail.apiFailure(`DevPass returned HTTP ${response.status}.`);
50
+ function fail() {
51
+ throw ctx.fail.parseFailure("DevPass returned an unrecognized usage response.");
52
+ }
53
+ let body;
54
+ try {
55
+ body = JSON.parse(response.bodyText);
56
+ } catch (error) {
57
+ void error;
58
+ return fail();
59
+ }
60
+ const data = _optionalChain([body, "optionalAccess", (_) => _.data]);
61
+ if (!data || !["none", "lite", "pro", "max"].includes(data.devPlan)) return fail();
62
+ function money(key) {
63
+ const value = data[key];
64
+ if (typeof value !== "string" || !/^\d+(?:\.\d+)?$/.test(value)) return fail();
65
+ const number = Number(value);
66
+ if (!Number.isFinite(number)) return fail();
67
+ return number;
68
+ }
69
+ const keyUsed = money("usage");
70
+ const keyLimit = data.limit === null ? null : money("limit");
71
+ const keyRows = [{ label: "All-time key usage", value: ctx.format.usd(keyUsed) }];
72
+ if (keyLimit !== null) keyRows.push({ label: "Key spending limit", value: ctx.format.usd(keyLimit) });
73
+ if (data.devPlan === "none") {
74
+ return {
75
+ details: [{ title: "API key (all time)", rows: keyRows }],
76
+ identity: { loginMethod: "Pay as you go" },
77
+ dataConfidence: "exact",
78
+ };
79
+ }
80
+ const used = money("devPlanCreditsUsed");
81
+ const limit = money("devPlanCreditsLimit");
82
+ const remaining = money("devPlanCreditsRemaining");
83
+ const weeklyUsed = money("devPlanPremiumCreditsUsed");
84
+ const weeklyLimit = money("devPlanPremiumWeeklyLimit");
85
+ const reset = data.devPlanPremiumWeekResetsAt;
86
+ let resetsAt = null;
87
+ if (reset !== null) {
88
+ if (
89
+ typeof reset !== "string" ||
90
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(reset)
91
+ )
92
+ return fail();
93
+ resetsAt = new Date(reset);
94
+ if (!Number.isFinite(resetsAt.getTime())) return fail();
95
+ }
96
+ return {
97
+ primary: limit > 0 ? { usedPercent: ctx.pct(used, limit) } : null,
98
+ secondary:
99
+ weeklyLimit > 0 ? { usedPercent: ctx.pct(weeklyUsed, weeklyLimit), windowMinutes: 10080, resetsAt } : null,
100
+ details: [
101
+ {
102
+ title: "DevPass credits",
103
+ rows: [
104
+ { label: "Cycle used", value: `${ctx.format.usd(used)} / ${ctx.format.usd(limit)}` },
105
+ { label: "Cycle remaining", value: ctx.format.usd(remaining) },
106
+ { label: "Premium weekly", value: `${ctx.format.usd(weeklyUsed)} / ${ctx.format.usd(weeklyLimit)}` },
107
+ ],
108
+ },
109
+ { title: "API key (all time)", rows: keyRows },
110
+ ],
111
+ identity: { loginMethod: `DevPass ${data.devPlan.charAt(0).toUpperCase()}${data.devPlan.slice(1)}` },
112
+ dataConfidence: "exact",
113
+ };
114
+ },
115
+ });
@@ -0,0 +1,23 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "devpass",
4
+ "name": "DevPass",
5
+ "version": "0.1.0",
6
+ "entry": "plugin.js",
7
+ "enabledByDefault": false,
8
+ "supportedModes": [
9
+ "api"
10
+ ],
11
+ "autoMode": "api",
12
+ "usageDashboardUrl": "https://devpass.llmgateway.io/dashboard",
13
+ "envVars": [
14
+ "DEVPASS_API_KEY"
15
+ ],
16
+ "upstream": {
17
+ "repository": "https://github.com/steipete/CodexBar",
18
+ "revision": "78ba5a1e65e6859da59a21b799df95ab91bc0b89",
19
+ "path": "Sources/CodexBarCore/Resources/Plugins/devpass.js",
20
+ "sha256": "6fdfab17f543fc88f28b90f76fbe6edc6db3f4d3c7fa657a899c573d363a0886",
21
+ "license": "MIT"
22
+ }
23
+ }
@@ -0,0 +1,4 @@
1
+ <svg viewBox="0 0 32 32" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M7 6h4v20H7V6Zm7 0h4v20h-4V6Zm7 0h4v20h-4V6Z"/>
3
+ <path d="M3.5 15h25v2h-25v-2Z" opacity=".35"/>
4
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 67 65" id="mobile-nav-logo" class="h-auto w-10 md:hidden"><path fill="currentColor" d="M47.75 11.15a.867.867 0 0 1-.671-.806.84.84 0 0 1 .067-.362c1.688-4.007 2.433-7.213 1.23-8.555-3.183-3.56-15.952 3.52-20.024 5.919a.9.9 0 0 1-1.273-.41c-1.711-3.998-3.51-6.78-5.334-6.9-4.833-.323-8.73 13.49-9.87 17.992a.85.85 0 0 1-.459.563.9.9 0 0 1-.737.027c-4.109-1.647-7.398-2.373-8.773-1.2-3.651 3.104 3.609 15.557 6.068 19.528a.85.85 0 0 1-.11 1.031.9.9 0 0 1-.31.21C3.455 39.856.604 41.61.478 43.389c-.329 4.713 13.834 8.513 18.452 9.625q.186.046.337.163a.87.87 0 0 1 .332.642.84.84 0 0 1-.067.362c-1.688 4.007-2.433 7.214-1.23 8.555 3.183 3.561 15.954-3.519 20.025-5.917a.9.9 0 0 1 1.058.107.9.9 0 0 1 .215.302c1.711 3.997 3.509 6.779 5.334 6.9 4.833.322 8.73-13.49 9.868-17.993a.85.85 0 0 1 .168-.33.88.88 0 0 1 .659-.324.9.9 0 0 1 .371.066c4.109 1.647 7.397 2.372 8.773 1.2 3.651-3.105-3.61-15.559-6.07-19.53a.85.85 0 0 1 .111-1.03.9.9 0 0 1 .31-.21c4.1-1.67 6.952-3.424 7.075-5.203.331-4.713-13.833-8.513-18.45-9.623m-5.546-4.518c.93 1.624-3.858 12.446-7.42 20.015a.7.7 0 0 1-.28.303.71.71 0 0 1-.796-.059.7.7 0 0 1-.23-.341c-1.439-4.921-3.082-10.704-4.841-15.612a.84.84 0 0 1 .01-.594.87.87 0 0 1 .401-.446c4.392-2.34 11.908-5.446 13.156-3.266m-21.048 1.34c1.833.507 6.294 11.46 9.264 19.268a.67.67 0 0 1-.2.754.71.71 0 0 1-.794.08c-4.589-2.485-9.94-5.444-14.743-7.702a.87.87 0 0 1-.422-.427.84.84 0 0 1-.04-.591c1.414-4.679 4.471-12.063 6.935-11.383M7.243 23.433c1.664-.906 12.762 3.763 20.522 7.235.13.058.239.154.311.274a.67.67 0 0 1-.06.776.7.7 0 0 1-.35.225c-5.045 1.403-10.976 3.006-16.01 4.721a.9.9 0 0 1-.607-.01.88.88 0 0 1-.456-.391c-2.395-4.284-5.586-11.613-3.35-12.83M8.617 43.96c.519-1.788 11.752-6.14 19.758-9.035a.72.72 0 0 1 .773.195.67.67 0 0 1 .081.774c-2.548 4.475-5.582 9.694-7.898 14.377a.87.87 0 0 1-.437.413.9.9 0 0 1-.607.039c-4.797-1.37-12.37-4.36-11.67-6.763m15.855 13.568c-.93-1.623 3.859-12.446 7.42-20.014a.7.7 0 0 1 .28-.303.715.715 0 0 1 .796.059.7.7 0 0 1 .23.34c1.439 4.92 3.083 10.705 4.841 15.613a.84.84 0 0 1-.01.593.87.87 0 0 1-.402.445c-4.391 2.335-11.908 5.447-13.15 3.267zm21.049-1.34c-1.836-.506-6.297-11.461-9.266-19.269a.67.67 0 0 1 .2-.755.71.71 0 0 1 .795-.078c4.587 2.484 9.94 5.445 14.742 7.703.189.088.339.24.423.426a.84.84 0 0 1 .039.592c-1.413 4.686-4.47 12.063-6.933 11.381m13.912-15.462c-1.665.907-12.762-3.763-20.523-7.236a.7.7 0 0 1-.311-.273.67.67 0 0 1 .06-.777.7.7 0 0 1 .35-.225c5.046-1.402 10.975-3.005 16.009-4.72a.9.9 0 0 1 .609.01.88.88 0 0 1 .457.392c2.393 4.282 5.584 11.613 3.349 12.829M58.06 20.2c-.521 1.79-11.753 6.14-19.759 9.036a.72.72 0 0 1-.774-.195.67.67 0 0 1-.08-.776c2.547-4.474 5.581-9.694 7.897-14.377a.87.87 0 0 1 .437-.412.9.9 0 0 1 .607-.038c4.797 1.377 12.37 4.359 11.672 6.762"></path></svg>
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Fireworks</title><path clip-rule="evenodd" d="M14.8 5l-2.801 6.795L9.195 5H7.397l3.072 7.428a1.64 1.64 0 003.038.002L16.598 5H14.8zm1.196 10.352l5.124-5.244-.699-1.669-5.596 5.739a1.664 1.664 0 00-.343 1.807 1.642 1.642 0 001.516 1.012L16 17l8-.02-.699-1.669-7.303.041h-.002zM2.88 10.104l.699-1.669 5.596 5.739c.468.479.603 1.189.343 1.807a1.643 1.643 0 01-1.516 1.012l-8-.018-.002.002.699-1.669 7.303.042-5.122-5.246z" fill="#5019C5" fill-rule="evenodd"></path></svg>
@@ -1,6 +1 @@
1
- <svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true">
2
- <path
3
- d="M314.333 110.167L255.98 251.729l-58.416-141.562h-37.459l64 154.75c5.23 12.854 17.771 21.312 31.646 21.312s26.417-8.437 31.646-21.27l64.396-154.792h-37.459zm24.917 215.666L446 216.583l-14.562-34.77-116.584 119.562c-9.708 9.958-12.541 24.833-7.146 37.646 5.292 12.73 17.792 21.083 31.584 21.083l.042.063L506 359.75l-14.562-34.77-152.146.853h-.042zM66 216.5l14.563-34.77 116.583 119.562a34.592 34.592 0 017.146 37.646C199 351.667 186.5 360.02 172.708 360.02l-166.666-.375-.042.042 14.563-34.771 152.145.875L66 216.5z"
4
- fill="currentColor"
5
- />
6
- </svg>
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>Fireworks</title><path clip-rule="evenodd" d="M14.8 5l-2.801 6.795L9.195 5H7.397l3.072 7.428a1.64 1.64 0 003.038.002L16.598 5H14.8zm1.196 10.352l5.124-5.244-.699-1.669-5.596 5.739a1.664 1.664 0 00-.343 1.807 1.642 1.642 0 001.516 1.012L16 17l8-.02-.699-1.669-7.303.041h-.002zM2.88 10.104l.699-1.669 5.596 5.739c.468.479.603 1.189.343 1.807a1.643 1.643 0 01-1.516 1.012l-8-.018-.002.002.699-1.669 7.303.042-5.122-5.246z"></path></svg>
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robin Ebers (original macOS creator)
4
+ Copyright (c) 2026 barramee kottanawadee
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ <svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="#3186FF"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-0-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-1-_R_0_)"></path><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" fill="url(#lobe-icons-gemini-2-_R_0_)"></path><defs><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-0-_R_0_" x1="7" x2="11" y1="15.5" y2="12"><stop stop-color="#08B962"></stop><stop offset="1" stop-color="#08B962" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-1-_R_0_" x1="8" x2="11.5" y1="5.5" y2="11"><stop stop-color="#F94543"></stop><stop offset="1" stop-color="#F94543" stop-opacity="0"></stop></linearGradient><linearGradient gradientUnits="userSpaceOnUse" id="lobe-icons-gemini-2-_R_0_" x1="3.5" x2="17.5" y1="13.5" y2="12"><stop stop-color="#FABC12"></stop><stop offset=".46" stop-color="#FABC12" stop-opacity="0"></stop></linearGradient></defs></svg>
@@ -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>Gemini</title><path d="M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z"></path></svg>
@@ -0,0 +1,470 @@
1
+ (function () {
2
+ const APP_URL = "https://gemini.google.com/app"
3
+ const USAGE_URL = "https://gemini.google.com/usage"
4
+ const BATCH_URL = "https://gemini.google.com/_/BardChatUi/data/batchexecute"
5
+ const ORIGIN = "https://gemini.google.com"
6
+ const RPC_USAGE = "jSf9Qc"
7
+ const RPC_PLAN = "sJBwce"
8
+ const CURRENT_MS = 5 * 60 * 60 * 1000
9
+ const WEEKLY_MS = 7 * 24 * 60 * 60 * 1000
10
+ const BRAND = "#3186FF"
11
+ const COOKIE_HOSTS = [".google.com", "google.com", ".gemini.google.com", "gemini.google.com"]
12
+ const COOKIE_NAMES = [
13
+ "SID",
14
+ "HSID",
15
+ "SSID",
16
+ "APISID",
17
+ "SAPISID",
18
+ "__Secure-1PSID",
19
+ "__Secure-1PSIDTS",
20
+ "__Secure-3PSID",
21
+ "__Secure-1PAPISID",
22
+ ]
23
+
24
+ function readString(value) {
25
+ if (typeof value !== "string") return null
26
+ const trimmed = value.trim()
27
+ return trimmed ? trimmed : null
28
+ }
29
+
30
+ function getEnv(ctx, name) {
31
+ try {
32
+ return readString(ctx.host.env.get(name))
33
+ } catch (e) {
34
+ ctx.host.log.warn("env read failed for " + name + ": " + String(e))
35
+ return null
36
+ }
37
+ }
38
+
39
+ function parseCookieHeader(raw) {
40
+ const text = readString(raw)
41
+ if (!text) return null
42
+ const header = text.replace(/^Cookie:\s*/i, "")
43
+ const map = {}
44
+ const parts = header.split(";")
45
+ for (let i = 0; i < parts.length; i += 1) {
46
+ const part = parts[i].trim()
47
+ const eq = part.indexOf("=")
48
+ if (eq === -1) continue
49
+ const name = part.slice(0, eq).trim()
50
+ const value = part.slice(eq + 1).trim()
51
+ if (name && value) map[name] = value
52
+ }
53
+ return Object.keys(map).length > 0 ? map : null
54
+ }
55
+
56
+ function hasGeminiSession(map) {
57
+ if (!map || typeof map !== "object") return false
58
+ const hasPsid = readString(map["__Secure-1PSID"])
59
+ const hasSapisid = readString(map.SAPISID) || readString(map["__Secure-1PAPISID"])
60
+ return !!(hasPsid && hasSapisid)
61
+ }
62
+
63
+ function readChromeCookies(ctx) {
64
+ if (!ctx.host.chromiumCookies || typeof ctx.host.chromiumCookies.read !== "function") {
65
+ return null
66
+ }
67
+ try {
68
+ const map = ctx.host.chromiumCookies.read({ hosts: COOKIE_HOSTS, names: COOKIE_NAMES })
69
+ if (!hasGeminiSession(map)) return null
70
+ return map
71
+ } catch (e) {
72
+ ctx.host.log.warn("chromium cookie read failed: " + String(e))
73
+ return null
74
+ }
75
+ }
76
+
77
+ function loadCookies(ctx) {
78
+ const provider = ctx.provider || {}
79
+ if (Object.prototype.hasOwnProperty.call(provider, "cookieHeader")) {
80
+ const configured = parseCookieHeader(provider.cookieHeader)
81
+ return configured ? { map: configured, source: "Configured cookie" } : null
82
+ }
83
+ if (provider.instanceId && provider.instanceId !== "gemini-apps") return null
84
+ const explicit = parseCookieHeader(getEnv(ctx, "GEMINI_COOKIE"))
85
+ if (explicit) return { map: explicit, source: "GEMINI_COOKIE" }
86
+ const chrome = readChromeCookies(ctx)
87
+ if (chrome) return { map: chrome, source: "Chrome" }
88
+
89
+ const envMap = parseCookieHeader(getEnv(ctx, "GEMINI_COOKIE"))
90
+ if (envMap) return { map: envMap, source: "GEMINI_COOKIE" }
91
+
92
+ return null
93
+ }
94
+
95
+ function headerSafe(value) {
96
+ const text = readString(value)
97
+ if (!text) return null
98
+ for (let i = 0; i < text.length; i += 1) {
99
+ const code = text.charCodeAt(i)
100
+ if (code < 32 || code > 126) return null
101
+ }
102
+ return text
103
+ }
104
+
105
+ function cookieHeader(map) {
106
+ const parts = []
107
+ for (let i = 0; i < COOKIE_NAMES.length; i += 1) {
108
+ const name = COOKIE_NAMES[i]
109
+ const value = headerSafe(map[name])
110
+ if (value) parts.push(name + "=" + value)
111
+ }
112
+ const keys = Object.keys(map)
113
+ for (let i = 0; i < keys.length; i += 1) {
114
+ const name = keys[i]
115
+ if (COOKIE_NAMES.indexOf(name) !== -1) continue
116
+ const value = headerSafe(map[name])
117
+ if (value) parts.push(name + "=" + value)
118
+ }
119
+ return parts.join("; ")
120
+ }
121
+
122
+ function sapisidHash(ctx, map) {
123
+ const sapisid = readString(map.SAPISID) || readString(map["__Secure-1PAPISID"])
124
+ if (!sapisid) return null
125
+ if (!ctx.host.crypto || typeof ctx.host.crypto.sha1Hex !== "function") {
126
+ ctx.host.log.warn("sha1Hex missing; SAPISIDHASH skipped")
127
+ return null
128
+ }
129
+ const ts = Math.floor(Date.parse(ctx.nowIso || "") / 1000)
130
+ const timestamp = Number.isFinite(ts) && ts > 0 ? ts : Math.floor(Date.now() / 1000)
131
+ const digest = ctx.host.crypto.sha1Hex(timestamp + " " + sapisid + " " + ORIGIN)
132
+ return "SAPISIDHASH " + timestamp + "_" + digest
133
+ }
134
+
135
+ function geminiHeaders(map, extra) {
136
+ const headers = {
137
+ Cookie: cookieHeader(map),
138
+ Origin: ORIGIN,
139
+ Referer: USAGE_URL,
140
+ "X-Same-Domain": "1",
141
+ "User-Agent":
142
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36",
143
+ }
144
+ if (extra) {
145
+ const keys = Object.keys(extra)
146
+ for (let i = 0; i < keys.length; i += 1) headers[keys[i]] = extra[keys[i]]
147
+ }
148
+ return headers
149
+ }
150
+
151
+ function extractMeta(html) {
152
+ function field(name) {
153
+ const re = new RegExp('"' + name + '"\\s*:\\s*"([^"]+)"')
154
+ const m = re.exec(html)
155
+ if (m && m[1]) return m[1]
156
+ const re2 = new RegExp("'" + name + "'\\s*:\\s*'([^']+)'")
157
+ const m2 = re2.exec(html)
158
+ return m2 && m2[1] ? m2[1] : null
159
+ }
160
+ return {
161
+ at: field("SNlM0e"),
162
+ bl: field("cfb2h"),
163
+ sid: field("FdrFJe"),
164
+ }
165
+ }
166
+
167
+ function looksLikeLogin(html, status) {
168
+ if (status === 302 || status === 303 || status === 307 || status === 308) return true
169
+ const text = String(html || "")
170
+ if (/accounts\.google\.com\/(?:ServiceLogin|v3\/signin)/i.test(text) && !/"SNlM0e"/.test(text)) {
171
+ return true
172
+ }
173
+ return false
174
+ }
175
+
176
+ function parsePlan(text) {
177
+ const s = String(text || "")
178
+ if (/\bUltra\b/i.test(s)) return "Ultra"
179
+ if (/\bPRO\b/.test(s) || /\bPro\b/.test(s)) return "Pro"
180
+ if (/\bFree\b/i.test(s)) return "Free"
181
+ return null
182
+ }
183
+
184
+ function tryParseJson(ctx, text) {
185
+ return ctx.util.tryParseJson(text)
186
+ }
187
+
188
+ function parseBatchexecuteFrames(ctx, text) {
189
+ let s = String(text || "").replace(/^\)\]\}'\s*/, "")
190
+ const frames = []
191
+ let pos = 0
192
+ while (pos < s.length) {
193
+ while (pos < s.length && /\s/.test(s.charAt(pos))) pos += 1
194
+ if (pos >= s.length) break
195
+ if (s.charAt(pos) === "[") {
196
+ const parsed = tryParseJson(ctx, s.slice(pos))
197
+ if (parsed) frames.push(parsed)
198
+ break
199
+ }
200
+ const nl = s.indexOf("\n", pos)
201
+ if (nl < 0) break
202
+ const n = Number(s.slice(pos, nl).trim())
203
+ if (!Number.isFinite(n) || n <= 0) {
204
+ pos = nl + 1
205
+ continue
206
+ }
207
+ const frame = s.slice(nl + 1, nl + 1 + n)
208
+ const parsed = tryParseJson(ctx, frame)
209
+ if (parsed) frames.push(parsed)
210
+ pos = nl + 1 + n
211
+ }
212
+ return frames
213
+ }
214
+
215
+ function extractRpcPayload(ctx, text, rpcId) {
216
+ const marker = '"' + rpcId + '",'
217
+ const idx = String(text || "").indexOf(marker)
218
+ if (idx === -1) return findWrbPayload(ctx, parseBatchexecuteFrames(ctx, text), rpcId)
219
+ let p = idx + marker.length
220
+ while (p < text.length && /\s/.test(text.charAt(p))) p += 1
221
+ if (text.charAt(p) !== '"') {
222
+ return findWrbPayload(ctx, parseBatchexecuteFrames(ctx, text), rpcId)
223
+ }
224
+ let i = p + 1
225
+ while (i < text.length) {
226
+ const ch = text.charAt(i)
227
+ if (ch === "\\") {
228
+ i += 2
229
+ continue
230
+ }
231
+ if (ch === '"') {
232
+ const sliced = text.slice(p, i + 1)
233
+ const inner = tryParseJson(ctx, sliced)
234
+ if (typeof inner === "string") return tryParseJson(ctx, inner) || inner
235
+ return inner
236
+ }
237
+ i += 1
238
+ }
239
+ return findWrbPayload(ctx, parseBatchexecuteFrames(ctx, text), rpcId)
240
+ }
241
+
242
+ function findWrbPayload(ctx, frames, rpcId) {
243
+ function walk(node) {
244
+ if (!node) return null
245
+ if (Array.isArray(node)) {
246
+ if (node[0] === "wrb.fr" && node[1] === rpcId) {
247
+ const payload = node[2]
248
+ if (typeof payload === "string") return tryParseJson(ctx, payload) || payload
249
+ return payload
250
+ }
251
+ for (let i = 0; i < node.length; i += 1) {
252
+ const found = walk(node[i])
253
+ if (found != null) return found
254
+ }
255
+ }
256
+ return null
257
+ }
258
+ for (let i = 0; i < frames.length; i += 1) {
259
+ const found = walk(frames[i])
260
+ if (found != null) return found
261
+ }
262
+ return null
263
+ }
264
+
265
+ function resetUnix(row) {
266
+ if (!row || !Array.isArray(row[3])) return null
267
+ const first = row[3][0]
268
+ if (Array.isArray(first)) {
269
+ const n = Number(first[0])
270
+ return Number.isFinite(n) && n > 0 ? n : null
271
+ }
272
+ const n = Number(first)
273
+ return Number.isFinite(n) && n > 0 ? n : null
274
+ }
275
+
276
+ function parseUsagePayload(payload) {
277
+ let rows = null
278
+ if (Array.isArray(payload) && Array.isArray(payload[1])) rows = payload[1]
279
+ else if (Array.isArray(payload) && payload.length && Array.isArray(payload[0])) rows = payload
280
+ if (!rows) return null
281
+
282
+ let current = null
283
+ let weekly = null
284
+ for (let i = 0; i < rows.length; i += 1) {
285
+ const row = rows[i]
286
+ if (!Array.isArray(row) || row.length < 3) continue
287
+ if (typeof row[1] !== "number" || row[1] < 0 || row[1] > 1) continue
288
+ const frac = row[1]
289
+ const type = Number(row[2])
290
+ if (!Number.isFinite(frac) || !Number.isFinite(type)) continue
291
+ const used = Math.max(0, Math.min(100, Math.round(frac * 100)))
292
+ const bucket = { used: used, resetsAtUnix: resetUnix(row) }
293
+ if (type === 1) current = bucket
294
+ else if (type === 2) weekly = bucket
295
+ }
296
+ if (!current || !weekly) return null
297
+ return { current: current, weekly: weekly }
298
+ }
299
+
300
+ function encodeForm(fields) {
301
+ const parts = []
302
+ const keys = Object.keys(fields)
303
+ for (let i = 0; i < keys.length; i += 1) {
304
+ const key = keys[i]
305
+ if (fields[key] == null) continue
306
+ parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(fields[key])))
307
+ }
308
+ return parts.join("&")
309
+ }
310
+
311
+ function batchExecute(ctx, cookies, meta, rpcId, innerPayload) {
312
+ const params = [
313
+ "rpcids=" + encodeURIComponent(rpcId),
314
+ "rt=c",
315
+ "source-path=" + encodeURIComponent("/usage"),
316
+ ]
317
+ if (meta.bl) params.push("bl=" + encodeURIComponent(meta.bl))
318
+ if (meta.sid) params.push("f.sid=" + encodeURIComponent(meta.sid))
319
+ params.push("_reqid=" + String(100000 + Math.floor(Math.random() * 900000)))
320
+
321
+ const headers = geminiHeaders(cookies.map, {
322
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
323
+ })
324
+ const auth = sapisidHash(ctx, cookies.map)
325
+ if (auth) headers.Authorization = auth
326
+
327
+ const form = encodeForm({
328
+ "f.req": JSON.stringify([[[rpcId, innerPayload, null, "generic"]]]),
329
+ at: meta.at,
330
+ })
331
+
332
+ let resp
333
+ try {
334
+ resp = ctx.util.request({
335
+ method: "POST",
336
+ url: BATCH_URL + "?" + params.join("&"),
337
+ headers: headers,
338
+ bodyText: form,
339
+ timeoutMs: 15000,
340
+ http1Only: true,
341
+ })
342
+ } catch (e) {
343
+ ctx.host.log.warn("batchexecute failed (" + rpcId + "): " + String(e))
344
+ return null
345
+ }
346
+ if (looksLikeLogin(resp.bodyText, resp.status)) {
347
+ throw "Gemini Apps session expired. Log into gemini.google.com in Chrome."
348
+ }
349
+ if (resp.status < 200 || resp.status >= 300) {
350
+ ctx.host.log.warn("batchexecute HTTP " + resp.status + " for " + rpcId)
351
+ return null
352
+ }
353
+ return extractRpcPayload(ctx, resp.bodyText, rpcId)
354
+ }
355
+
356
+ function fetchHtml(ctx, cookies, url) {
357
+ const headers = geminiHeaders(cookies.map, { Accept: "text/html" })
358
+ const auth = sapisidHash(ctx, cookies.map)
359
+ if (auth) headers.Authorization = auth
360
+ let resp
361
+ try {
362
+ resp = ctx.util.request({
363
+ method: "GET",
364
+ url: url,
365
+ headers: headers,
366
+ timeoutMs: 15000,
367
+ http1Only: true,
368
+ })
369
+ } catch (e) {
370
+ ctx.host.log.warn("html request failed (" + url + "): " + String(e))
371
+ throw "Could not reach gemini.google.com. Check your connection."
372
+ }
373
+ if (looksLikeLogin(resp.bodyText, resp.status)) {
374
+ throw "Gemini Apps session expired. Log into gemini.google.com in Chrome."
375
+ }
376
+ if (ctx.util.isAuthStatus(resp.status)) {
377
+ throw "Gemini Apps session expired. Log into gemini.google.com in Chrome."
378
+ }
379
+ if (resp.status < 200 || resp.status >= 300) {
380
+ throw "Gemini Apps page failed (HTTP " + resp.status + ")."
381
+ }
382
+ return resp.bodyText
383
+ }
384
+
385
+ function fetchUsagePage(ctx, cookies) {
386
+ let html = fetchHtml(ctx, cookies, APP_URL)
387
+ let meta = extractMeta(html)
388
+ if (!meta.at) {
389
+ ctx.host.log.info("SNlM0e missing on /app; trying /usage")
390
+ html = fetchHtml(ctx, cookies, USAGE_URL)
391
+ meta = extractMeta(html)
392
+ }
393
+ ctx.host.log.info(
394
+ "htmlChars=" +
395
+ String(html.length) +
396
+ " hasAt=" +
397
+ (meta.at ? "yes" : "no") +
398
+ " hasBl=" +
399
+ (meta.bl ? "yes" : "no"),
400
+ )
401
+ if (!meta.at) {
402
+ throw "Could not read Gemini Apps session token. Log into gemini.google.com in Chrome."
403
+ }
404
+ return { html: html, meta: meta }
405
+ }
406
+
407
+ function probe(ctx) {
408
+ const cookies = loadCookies(ctx)
409
+ if (!cookies) {
410
+ throw "Gemini Apps auth missing. Log into gemini.google.com in Chrome."
411
+ }
412
+ ctx.host.log.info("cookie names=" + Object.keys(cookies.map).sort().join(","))
413
+ if (!hasGeminiSession(cookies.map)) {
414
+ throw "Gemini Apps auth missing. Log into gemini.google.com in Chrome."
415
+ }
416
+
417
+ const page = fetchUsagePage(ctx, cookies)
418
+ const payload = batchExecute(ctx, cookies, page.meta, RPC_USAGE, "[]")
419
+ const usage = parseUsagePayload(payload)
420
+ if (!usage) throw "Could not parse Gemini Apps usage."
421
+
422
+ let plan = null
423
+ try {
424
+ const planPayload = batchExecute(ctx, cookies, page.meta, RPC_PLAN, "[]")
425
+ if (planPayload != null) {
426
+ plan = parsePlan(JSON.stringify(planPayload))
427
+ }
428
+ } catch (e) {
429
+ ctx.host.log.warn("plan RPC skipped: " + String(e))
430
+ }
431
+
432
+ const currentResetsAt = usage.current.resetsAtUnix
433
+ ? ctx.util.toIso(usage.current.resetsAtUnix)
434
+ : undefined
435
+ const weeklyResetsAt = usage.weekly.resetsAtUnix
436
+ ? ctx.util.toIso(usage.weekly.resetsAtUnix)
437
+ : undefined
438
+
439
+ const lines = [
440
+ ctx.line.progress({
441
+ label: "Current",
442
+ used: usage.current.used,
443
+ limit: 100,
444
+ format: { kind: "percent" },
445
+ resetsAt: currentResetsAt,
446
+ periodDurationMs: CURRENT_MS,
447
+ color: BRAND,
448
+ }),
449
+ ctx.line.progress({
450
+ label: "Weekly",
451
+ used: usage.weekly.used,
452
+ limit: 100,
453
+ format: { kind: "percent" },
454
+ resetsAt: weeklyResetsAt,
455
+ periodDurationMs: WEEKLY_MS,
456
+ color: BRAND,
457
+ }),
458
+ ctx.line.text({ label: "Source", value: cookies.source }),
459
+ ]
460
+
461
+ const result = { lines: lines, source: "web" }
462
+ if (plan) result.plan = ctx.fmt.planLabel(plan)
463
+ return result
464
+ }
465
+
466
+ globalThis.__openusage_plugin = {
467
+ id: "gemini-apps",
468
+ probe: probe,
469
+ }
470
+ })()