@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,460 @@
1
+ (function () {
2
+ const SETTINGS_PATH = "~/.gemini/settings.json"
3
+ const CREDS_PATH = "~/.gemini/oauth_creds.json"
4
+ const OAUTH2_SUFFIX_FLAT = "/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"
5
+ const OAUTH2_SUFFIX_NESTED = "/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"
6
+ const BUNDLE_SUFFIX = "/@google/gemini-cli/bundle"
7
+
8
+ const STATIC_MODULE_ROOTS = [
9
+ "~/.bun/install/global/node_modules",
10
+ "~/.npm-global/lib/node_modules",
11
+ "/usr/local/lib/node_modules",
12
+ "~/Library/pnpm/global/5/node_modules",
13
+ ]
14
+
15
+ const STATIC_NESTED_ONLY = [
16
+ "/opt/homebrew/opt/gemini-cli/libexec/lib/node_modules",
17
+ "/usr/local/opt/gemini-cli/libexec/lib/node_modules",
18
+ ]
19
+
20
+ const VERSION_MANAGER_ROOTS = [
21
+ { root: "~/.nvm/versions/node", modulePath: "/lib/node_modules" },
22
+ { root: "~/Library/Application Support/fnm/node-versions", modulePath: "/installation/lib/node_modules" },
23
+ ]
24
+
25
+ function listDirSafe(ctx, path) {
26
+ try {
27
+ return ctx.host.fs.listDir(path)
28
+ } catch (e) {
29
+ return []
30
+ }
31
+ }
32
+
33
+ function buildOauthCandidatePaths(ctx) {
34
+ var paths = []
35
+
36
+ for (var i = 0; i < STATIC_MODULE_ROOTS.length; i += 1) {
37
+ paths.push(STATIC_MODULE_ROOTS[i] + OAUTH2_SUFFIX_FLAT)
38
+ paths.push(STATIC_MODULE_ROOTS[i] + OAUTH2_SUFFIX_NESTED)
39
+ }
40
+
41
+ for (var i = 0; i < STATIC_NESTED_ONLY.length; i += 1) {
42
+ paths.push(STATIC_NESTED_ONLY[i] + OAUTH2_SUFFIX_NESTED)
43
+ }
44
+
45
+ // Homebrew builds bundle everything into bundle/chunk-*.js — hash names vary per version
46
+ for (var i = 0; i < STATIC_NESTED_ONLY.length; i += 1) {
47
+ var bundleDir = STATIC_NESTED_ONLY[i] + BUNDLE_SUFFIX
48
+ var entries = listDirSafe(ctx, bundleDir)
49
+ for (var j = 0; j < entries.length; j += 1) {
50
+ var name = entries[j]
51
+ if (name.indexOf("chunk-") === 0 && name.slice(-3) === ".js") {
52
+ paths.push(bundleDir + "/" + name)
53
+ }
54
+ }
55
+ }
56
+
57
+ for (var i = 0; i < VERSION_MANAGER_ROOTS.length; i += 1) {
58
+ var versionManager = VERSION_MANAGER_ROOTS[i]
59
+ var root = versionManager.root
60
+ var versions = listDirSafe(ctx, root)
61
+ for (var j = 0; j < versions.length; j += 1) {
62
+ var base = root + "/" + versions[j] + versionManager.modulePath
63
+ paths.push(base + OAUTH2_SUFFIX_FLAT)
64
+ paths.push(base + OAUTH2_SUFFIX_NESTED)
65
+ }
66
+ }
67
+
68
+ // volta stores packages differently
69
+ paths.push("~/.volta/tools/image/packages/@google/gemini-cli/lib/node_modules" + OAUTH2_SUFFIX_NESTED)
70
+ paths.push("~/.volta/tools/image/packages/@google/gemini-cli/lib/node_modules" + OAUTH2_SUFFIX_FLAT)
71
+
72
+ return paths
73
+ }
74
+
75
+ const LOAD_CODE_ASSIST_URL = "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
76
+ const QUOTA_URL = "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota"
77
+ const PROJECTS_URL = "https://cloudresourcemanager.googleapis.com/v1/projects"
78
+ const TOKEN_URL = "https://oauth2.googleapis.com/token"
79
+ const REFRESH_BUFFER_MS = 5 * 60 * 1000
80
+
81
+ const IDE_METADATA = {
82
+ ideType: "IDE_UNSPECIFIED",
83
+ platform: "PLATFORM_UNSPECIFIED",
84
+ pluginType: "GEMINI",
85
+ duetProject: "default",
86
+ }
87
+
88
+ function loadSettings(ctx) {
89
+ if (!ctx.host.fs.exists(SETTINGS_PATH)) return null
90
+ try {
91
+ return ctx.util.tryParseJson(ctx.host.fs.readText(SETTINGS_PATH))
92
+ } catch (e) {
93
+ ctx.host.log.warn("failed reading settings: " + String(e))
94
+ return null
95
+ }
96
+ }
97
+
98
+ function assertSupportedAuthType(ctx) {
99
+ const settings = loadSettings(ctx)
100
+ const authType =
101
+ settings && typeof settings.authType === "string" ? settings.authType.trim().toLowerCase() : null
102
+
103
+ if (!authType || authType === "oauth-personal") return
104
+ if (authType === "api-key") {
105
+ throw "Gemini auth type api-key is not supported by this plugin yet."
106
+ }
107
+ if (authType === "vertex-ai") {
108
+ throw "Gemini auth type vertex-ai is not supported by this plugin yet."
109
+ }
110
+ throw "Gemini unsupported auth type: " + authType
111
+ }
112
+
113
+ function loadOauthCreds(ctx) {
114
+ if (!ctx.host.fs.exists(CREDS_PATH)) return null
115
+ try {
116
+ const parsed = ctx.util.tryParseJson(ctx.host.fs.readText(CREDS_PATH))
117
+ if (!parsed || typeof parsed !== "object") return null
118
+ if (!parsed.access_token && !parsed.refresh_token) return null
119
+ return parsed
120
+ } catch (e) {
121
+ ctx.host.log.warn("failed reading creds: " + String(e))
122
+ return null
123
+ }
124
+ }
125
+
126
+ function saveOauthCreds(ctx, creds) {
127
+ try {
128
+ ctx.host.fs.writeText(CREDS_PATH, JSON.stringify(creds, null, 2))
129
+ } catch (e) {
130
+ ctx.host.log.warn("failed persisting creds: " + String(e))
131
+ }
132
+ }
133
+
134
+ function parseOauthClientCreds(text) {
135
+ if (!text || typeof text !== "string") return null
136
+ const idMatch = text.match(/OAUTH_CLIENT_ID\s*=\s*['"]([^'"]+)['"]/)
137
+ const secretMatch = text.match(/OAUTH_CLIENT_SECRET\s*=\s*['"]([^'"]+)['"]/)
138
+ if (!idMatch || !secretMatch) return null
139
+ return { clientId: idMatch[1], clientSecret: secretMatch[1] }
140
+ }
141
+
142
+ function loadOauthClientCreds(ctx) {
143
+ const candidates = buildOauthCandidatePaths(ctx)
144
+ for (let i = 0; i < candidates.length; i += 1) {
145
+ const path = candidates[i]
146
+ if (!ctx.host.fs.exists(path)) continue
147
+ try {
148
+ const parsed = parseOauthClientCreds(ctx.host.fs.readText(path))
149
+ if (parsed) return parsed
150
+ } catch (e) {
151
+ ctx.host.log.warn("failed reading oauth candidate at " + path + ": " + String(e))
152
+ }
153
+ }
154
+ ctx.host.log.warn("Gemini OAuth client credentials not found in any known install path")
155
+ return null
156
+ }
157
+
158
+ function readNumber(value) {
159
+ const n = Number(value)
160
+ return Number.isFinite(n) ? n : null
161
+ }
162
+
163
+ function decodeIdToken(ctx, token) {
164
+ if (typeof token !== "string" || !token) return null
165
+ try {
166
+ const payload = ctx.jwt.decodePayload(token)
167
+ return payload && typeof payload === "object" ? payload : null
168
+ } catch {
169
+ return null
170
+ }
171
+ }
172
+
173
+ function needsRefresh(creds) {
174
+ if (!creds.access_token) return true
175
+ const expiry = readNumber(creds.expiry_date)
176
+ if (expiry === null) return false
177
+ const expiryMs = expiry > 10_000_000_000 ? expiry : expiry * 1000
178
+ return Date.now() + REFRESH_BUFFER_MS >= expiryMs
179
+ }
180
+
181
+ function refreshToken(ctx, creds) {
182
+ if (!creds.refresh_token) return null
183
+ const clientCreds = loadOauthClientCreds(ctx)
184
+ if (!clientCreds) return null
185
+
186
+ let resp
187
+ try {
188
+ resp = ctx.util.request({
189
+ method: "POST",
190
+ url: TOKEN_URL,
191
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
192
+ bodyText:
193
+ "client_id=" +
194
+ encodeURIComponent(clientCreds.clientId) +
195
+ "&client_secret=" +
196
+ encodeURIComponent(clientCreds.clientSecret) +
197
+ "&refresh_token=" +
198
+ encodeURIComponent(creds.refresh_token) +
199
+ "&grant_type=refresh_token",
200
+ timeoutMs: 15000,
201
+ })
202
+ } catch (e) {
203
+ ctx.host.log.warn("refresh request failed: " + String(e))
204
+ return null
205
+ }
206
+
207
+ if (ctx.util.isAuthStatus(resp.status)) {
208
+ throw "Gemini session expired. Run `gemini` and re-authenticate when prompted."
209
+ }
210
+ if (resp.status < 200 || resp.status >= 300) return null
211
+
212
+ const data = ctx.util.tryParseJson(resp.bodyText)
213
+ if (!data || typeof data.access_token !== "string" || !data.access_token) return null
214
+
215
+ creds.access_token = data.access_token
216
+ if (typeof data.id_token === "string" && data.id_token) creds.id_token = data.id_token
217
+ if (typeof data.refresh_token === "string" && data.refresh_token) creds.refresh_token = data.refresh_token
218
+ if (typeof data.expires_in === "number") {
219
+ creds.expiry_date = Date.now() + data.expires_in * 1000
220
+ }
221
+
222
+ saveOauthCreds(ctx, creds)
223
+ return creds.access_token
224
+ }
225
+
226
+ function postJson(ctx, url, accessToken, body) {
227
+ return ctx.util.request({
228
+ method: "POST",
229
+ url,
230
+ headers: {
231
+ Authorization: "Bearer " + accessToken,
232
+ Accept: "application/json",
233
+ "Content-Type": "application/json",
234
+ },
235
+ bodyText: JSON.stringify(body || {}),
236
+ timeoutMs: 10000,
237
+ })
238
+ }
239
+
240
+ function readFirstStringDeep(value, keys) {
241
+ if (!value || typeof value !== "object") return null
242
+
243
+ for (let i = 0; i < keys.length; i += 1) {
244
+ const v = value[keys[i]]
245
+ if (typeof v === "string" && v.trim()) return v.trim()
246
+ }
247
+
248
+ const nested = Object.values(value)
249
+ for (let i = 0; i < nested.length; i += 1) {
250
+ const found = readFirstStringDeep(nested[i], keys)
251
+ if (found) return found
252
+ }
253
+ return null
254
+ }
255
+
256
+ function extractTier(data) {
257
+ if (!data || typeof data !== "object") return null
258
+ const paidId = data.paidTier && typeof data.paidTier.id === "string" ? data.paidTier.id : null
259
+ if (paidId) return paidId
260
+ const currentId = data.currentTier && typeof data.currentTier.id === "string" ? data.currentTier.id : null
261
+ if (currentId) return currentId
262
+ return readFirstStringDeep(data, ["tier", "userTier", "subscriptionTier"])
263
+ }
264
+
265
+ function mapTierToPlan(tier, idTokenPayload) {
266
+ if (!tier) return null
267
+ const normalized = String(tier).trim().toLowerCase()
268
+ if (normalized === "standard-tier") return "Paid"
269
+ if (normalized === "legacy-tier") return "Legacy"
270
+ if (normalized === "free-tier") return idTokenPayload && idTokenPayload.hd ? "Workspace" : "Free"
271
+ if (normalized === "google ai pro" || normalized === "g1-pro-tier") return "AI Pro"
272
+ if (normalized === "google ai ultra" || normalized === "g1-ultra-tier") return "AI Ultra"
273
+ return null
274
+ }
275
+
276
+ function discoverProjectId(ctx, accessToken, loadCodeAssistData) {
277
+ const fromLoadCodeAssist = readFirstStringDeep(loadCodeAssistData, ["cloudaicompanionProject"])
278
+ if (fromLoadCodeAssist) return fromLoadCodeAssist
279
+
280
+ let projectsResp
281
+ try {
282
+ projectsResp = ctx.util.request({
283
+ method: "GET",
284
+ url: PROJECTS_URL,
285
+ headers: { Authorization: "Bearer " + accessToken, Accept: "application/json" },
286
+ timeoutMs: 10000,
287
+ })
288
+ } catch (e) {
289
+ ctx.host.log.warn("project discovery failed: " + String(e))
290
+ return null
291
+ }
292
+
293
+ if (projectsResp.status < 200 || projectsResp.status >= 300) return null
294
+ const projectsData = ctx.util.tryParseJson(projectsResp.bodyText)
295
+ const projects = projectsData && Array.isArray(projectsData.projects) ? projectsData.projects : []
296
+ for (let i = 0; i < projects.length; i += 1) {
297
+ const project = projects[i]
298
+ const projectId = project && typeof project.projectId === "string" ? project.projectId : null
299
+ if (!projectId) continue
300
+ if (projectId.indexOf("gen-lang-client") === 0) return projectId
301
+ const labels = project && project.labels && typeof project.labels === "object" ? project.labels : null
302
+ if (labels && labels["generative-language"] !== undefined) return projectId
303
+ }
304
+ return null
305
+ }
306
+
307
+ function collectQuotaBuckets(value, out) {
308
+ if (Array.isArray(value)) {
309
+ for (let i = 0; i < value.length; i += 1) collectQuotaBuckets(value[i], out)
310
+ return
311
+ }
312
+ if (!value || typeof value !== "object") return
313
+
314
+ if (typeof value.remainingFraction === "number") {
315
+ const modelId =
316
+ typeof value.modelId === "string"
317
+ ? value.modelId
318
+ : typeof value.model_id === "string"
319
+ ? value.model_id
320
+ : "unknown"
321
+ out.push({
322
+ modelId,
323
+ remainingFraction: value.remainingFraction,
324
+ resetTime: value.resetTime || value.reset_time || null,
325
+ })
326
+ }
327
+
328
+ const nested = Object.values(value)
329
+ for (let i = 0; i < nested.length; i += 1) collectQuotaBuckets(nested[i], out)
330
+ }
331
+
332
+ function pickLowestRemainingBucket(buckets) {
333
+ let best = null
334
+ for (let i = 0; i < buckets.length; i += 1) {
335
+ const bucket = buckets[i]
336
+ if (!Number.isFinite(bucket.remainingFraction)) continue
337
+ if (!best || bucket.remainingFraction < best.remainingFraction) best = bucket
338
+ }
339
+ return best
340
+ }
341
+
342
+ function toUsageLine(ctx, label, bucket) {
343
+ const clampedRemaining = Math.max(0, Math.min(1, Number(bucket.remainingFraction)))
344
+ const used = Math.round((1 - clampedRemaining) * 100)
345
+ const resetsAt = ctx.util.toIso(bucket.resetTime)
346
+ const opts = {
347
+ label,
348
+ used,
349
+ limit: 100,
350
+ format: { kind: "percent" },
351
+ }
352
+ if (resetsAt) opts.resetsAt = resetsAt
353
+ return ctx.line.progress(opts)
354
+ }
355
+
356
+ function parseQuotaLines(ctx, quotaData) {
357
+ const buckets = []
358
+ collectQuotaBuckets(quotaData, buckets)
359
+ if (!buckets.length) return []
360
+
361
+ const proBuckets = []
362
+ const flashBuckets = []
363
+ for (let i = 0; i < buckets.length; i += 1) {
364
+ const bucket = buckets[i]
365
+ const lower = String(bucket.modelId || "").toLowerCase()
366
+ if (lower.indexOf("gemini") !== -1 && lower.indexOf("pro") !== -1) {
367
+ proBuckets.push(bucket)
368
+ } else if (lower.indexOf("gemini") !== -1 && lower.indexOf("flash") !== -1) {
369
+ flashBuckets.push(bucket)
370
+ }
371
+ }
372
+
373
+ const lines = []
374
+ const pro = pickLowestRemainingBucket(proBuckets)
375
+ if (pro) lines.push(toUsageLine(ctx, "Pro", pro))
376
+ const flash = pickLowestRemainingBucket(flashBuckets)
377
+ if (flash) lines.push(toUsageLine(ctx, "Flash", flash))
378
+ return lines
379
+ }
380
+
381
+ function fetchLoadCodeAssist(ctx, accessToken, creds) {
382
+ let currentToken = accessToken
383
+ const resp = ctx.util.retryOnceOnAuth({
384
+ request: function (token) {
385
+ return postJson(ctx, LOAD_CODE_ASSIST_URL, token || currentToken, { metadata: IDE_METADATA })
386
+ },
387
+ refresh: function () {
388
+ const refreshed = refreshToken(ctx, creds)
389
+ if (refreshed) currentToken = refreshed
390
+ return refreshed
391
+ },
392
+ })
393
+
394
+ if (ctx.util.isAuthStatus(resp.status)) {
395
+ throw "Gemini session expired. Run `gemini` and re-authenticate when prompted."
396
+ }
397
+ if (resp.status < 200 || resp.status >= 300) return { data: null, accessToken: currentToken }
398
+ return { data: ctx.util.tryParseJson(resp.bodyText), accessToken: currentToken }
399
+ }
400
+
401
+ function fetchQuotaWithRetry(ctx, accessToken, creds, projectId) {
402
+ let currentToken = accessToken
403
+ const resp = ctx.util.retryOnceOnAuth({
404
+ request: function (token) {
405
+ const body = projectId ? { project: projectId } : {}
406
+ return postJson(ctx, QUOTA_URL, token || currentToken, body)
407
+ },
408
+ refresh: function () {
409
+ const refreshed = refreshToken(ctx, creds)
410
+ if (refreshed) currentToken = refreshed
411
+ return refreshed
412
+ },
413
+ })
414
+
415
+ if (ctx.util.isAuthStatus(resp.status)) {
416
+ throw "Gemini session expired. Run `gemini` and re-authenticate when prompted."
417
+ }
418
+ if (resp.status < 200 || resp.status >= 300) {
419
+ throw "Gemini quota request failed (HTTP " + String(resp.status) + "). Try again later."
420
+ }
421
+ return resp
422
+ }
423
+
424
+ function probe(ctx) {
425
+ assertSupportedAuthType(ctx)
426
+
427
+ const creds = loadOauthCreds(ctx)
428
+ if (!creds) throw "Not logged in. Run `gemini` and complete the OAuth prompt."
429
+
430
+ let accessToken = creds.access_token
431
+ if (needsRefresh(creds)) {
432
+ const refreshed = refreshToken(ctx, creds)
433
+ if (refreshed) accessToken = refreshed
434
+ else if (!accessToken) throw "Not logged in. Run `gemini` and complete the OAuth prompt."
435
+ }
436
+
437
+ const idTokenPayload = decodeIdToken(ctx, creds.id_token)
438
+ const loadCodeAssistResult = fetchLoadCodeAssist(ctx, accessToken, creds)
439
+ accessToken = loadCodeAssistResult.accessToken
440
+
441
+ const tier = extractTier(loadCodeAssistResult.data)
442
+ const plan = mapTierToPlan(tier, idTokenPayload)
443
+
444
+ const projectId = discoverProjectId(ctx, accessToken, loadCodeAssistResult.data)
445
+ const quotaResp = fetchQuotaWithRetry(ctx, accessToken, creds, projectId)
446
+ const quotaData = ctx.util.tryParseJson(quotaResp.bodyText)
447
+ if (!quotaData || typeof quotaData !== "object") {
448
+ throw "Gemini quota response invalid. Try again later."
449
+ }
450
+
451
+ const lines = parseQuotaLines(ctx, quotaData)
452
+ const email = idTokenPayload && typeof idTokenPayload.email === "string" ? idTokenPayload.email : null
453
+ if (email) lines.push(ctx.line.text({ label: "Account", value: email }))
454
+ if (!lines.length) lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" }))
455
+
456
+ return { plan: plan || undefined, lines }
457
+ }
458
+
459
+ globalThis.__openusage_plugin = { id: "gemini", probe }
460
+ })()
@@ -0,0 +1,16 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "gemini",
4
+ "name": "Gemini",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#4285F4",
9
+ "supportedModes": ["oauth", "local"],
10
+ "autoMode": "oauth",
11
+ "lines": [
12
+ { "type": "progress", "label": "Pro", "scope": "overview", "primaryOrder": 1 },
13
+ { "type": "progress", "label": "Flash", "scope": "overview", "primaryOrder": 2 },
14
+ { "type": "text", "label": "Account", "scope": "detail" }
15
+ ]
16
+ }
@@ -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>Grok</title><path d="M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z"></path></svg>