openusage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/openusage +91 -0
- package/package.json +33 -0
- package/plugins/amp/icon.svg +6 -0
- package/plugins/amp/plugin.js +175 -0
- package/plugins/amp/plugin.json +20 -0
- package/plugins/amp/plugin.test.js +365 -0
- package/plugins/antigravity/icon.svg +3 -0
- package/plugins/antigravity/plugin.js +484 -0
- package/plugins/antigravity/plugin.json +17 -0
- package/plugins/antigravity/plugin.test.js +1356 -0
- package/plugins/claude/icon.svg +3 -0
- package/plugins/claude/plugin.js +565 -0
- package/plugins/claude/plugin.json +28 -0
- package/plugins/claude/plugin.test.js +1012 -0
- package/plugins/codex/icon.svg +3 -0
- package/plugins/codex/plugin.js +673 -0
- package/plugins/codex/plugin.json +30 -0
- package/plugins/codex/plugin.test.js +1071 -0
- package/plugins/copilot/icon.svg +3 -0
- package/plugins/copilot/plugin.js +264 -0
- package/plugins/copilot/plugin.json +20 -0
- package/plugins/copilot/plugin.test.js +529 -0
- package/plugins/cursor/icon.svg +3 -0
- package/plugins/cursor/plugin.js +526 -0
- package/plugins/cursor/plugin.json +24 -0
- package/plugins/cursor/plugin.test.js +1168 -0
- package/plugins/factory/icon.svg +1 -0
- package/plugins/factory/plugin.js +407 -0
- package/plugins/factory/plugin.json +19 -0
- package/plugins/factory/plugin.test.js +833 -0
- package/plugins/gemini/icon.svg +4 -0
- package/plugins/gemini/plugin.js +413 -0
- package/plugins/gemini/plugin.json +20 -0
- package/plugins/gemini/plugin.test.js +735 -0
- package/plugins/jetbrains-ai-assistant/icon.svg +3 -0
- package/plugins/jetbrains-ai-assistant/plugin.js +357 -0
- package/plugins/jetbrains-ai-assistant/plugin.json +17 -0
- package/plugins/jetbrains-ai-assistant/plugin.test.js +338 -0
- package/plugins/kimi/icon.svg +3 -0
- package/plugins/kimi/plugin.js +358 -0
- package/plugins/kimi/plugin.json +19 -0
- package/plugins/kimi/plugin.test.js +619 -0
- package/plugins/minimax/icon.svg +4 -0
- package/plugins/minimax/plugin.js +388 -0
- package/plugins/minimax/plugin.json +17 -0
- package/plugins/minimax/plugin.test.js +943 -0
- package/plugins/perplexity/icon.svg +1 -0
- package/plugins/perplexity/plugin.js +378 -0
- package/plugins/perplexity/plugin.json +15 -0
- package/plugins/perplexity/plugin.test.js +602 -0
- package/plugins/windsurf/icon.svg +3 -0
- package/plugins/windsurf/plugin.js +218 -0
- package/plugins/windsurf/plugin.json +16 -0
- package/plugins/windsurf/plugin.test.js +455 -0
- package/plugins/zai/icon.svg +5 -0
- package/plugins/zai/plugin.js +156 -0
- package/plugins/zai/plugin.json +18 -0
- package/plugins/zai/plugin.test.js +396 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="100" height="100" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M96 8c3.6 0 6.8 2.4 7.7 5.9 5.4 21.2 19.2 35 40.4 40.4 3.5.9 5.9 4.1 5.9 7.7s-2.4 6.8-5.9 7.7c-21.2 5.4-35 19.2-40.4 40.4-.9 3.5-4.1 5.9-7.7 5.9s-6.8-2.4-7.7-5.9c-5.4-21.2-19.2-35-40.4-40.4C44.4 68.8 42 65.6 42 62s2.4-6.8 5.9-7.7c21.2-5.4 35-19.2 40.4-40.4C89.2 10.4 92.4 8 96 8z" fill="currentColor"/>
|
|
3
|
+
<path d="M160 88c1.8 0 3.4 1.2 3.8 2.9 2.6 10.2 9.3 16.9 19.5 19.5 1.7.4 2.9 2 2.9 3.8s-1.2 3.4-2.9 3.8c-10.2 2.6-16.9 9.3-19.5 19.5-.4 1.7-2 2.9-3.8 2.9s-3.4-1.2-3.8-2.9c-2.6-10.2-9.3-16.9-19.5-19.5-1.7-.4-2.9-2-2.9-3.8s1.2-3.4 2.9-3.8c10.2-2.6 16.9-9.3 19.5-19.5.4-1.7 2-2.9 3.8-2.9z" fill="currentColor"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const SETTINGS_PATH = "~/.gemini/settings.json"
|
|
3
|
+
const CREDS_PATH = "~/.gemini/oauth_creds.json"
|
|
4
|
+
const OAUTH2_JS_PATHS = [
|
|
5
|
+
"~/.bun/install/global/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js",
|
|
6
|
+
"~/.npm-global/lib/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js",
|
|
7
|
+
"~/.nvm/versions/node/current/lib/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js",
|
|
8
|
+
"/opt/homebrew/opt/gemini-cli/libexec/lib/node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js",
|
|
9
|
+
"/usr/local/opt/gemini-cli/libexec/lib/node_modules/@google/gemini-cli/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
// Dynamic discovery for pnpm global installs (path varies by pnpm version)
|
|
13
|
+
function discoverPnpmOauth2Paths(ctx) {
|
|
14
|
+
const pnpmGlobal = "~/.local/share/pnpm/global"
|
|
15
|
+
if (!ctx.host.fs.exists(pnpmGlobal)) return []
|
|
16
|
+
try {
|
|
17
|
+
const entries = ctx.host.fs.listDir(pnpmGlobal)
|
|
18
|
+
const paths = []
|
|
19
|
+
for (let i = 0; i < entries.length; i += 1) {
|
|
20
|
+
const versionDir = pnpmGlobal + "/" + entries[i]
|
|
21
|
+
const pnpmDir = versionDir + "/.pnpm"
|
|
22
|
+
if (!ctx.host.fs.exists(pnpmDir)) continue
|
|
23
|
+
try {
|
|
24
|
+
const pnpmEntries = ctx.host.fs.listDir(pnpmDir)
|
|
25
|
+
for (let j = 0; j < pnpmEntries.length; j += 1) {
|
|
26
|
+
if (pnpmEntries[j].indexOf("@google+gemini-cli-core") === 0) {
|
|
27
|
+
const candidate = pnpmDir + "/" + pnpmEntries[j] +
|
|
28
|
+
"/node_modules/@google/gemini-cli-core/dist/src/code_assist/oauth2.js"
|
|
29
|
+
paths.push(candidate)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch (e) { /* ignore */ }
|
|
33
|
+
}
|
|
34
|
+
return paths
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return []
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const LOAD_CODE_ASSIST_URL = "https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist"
|
|
41
|
+
const QUOTA_URL = "https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota"
|
|
42
|
+
const PROJECTS_URL = "https://cloudresourcemanager.googleapis.com/v1/projects"
|
|
43
|
+
const TOKEN_URL = "https://oauth2.googleapis.com/token"
|
|
44
|
+
const REFRESH_BUFFER_MS = 5 * 60 * 1000
|
|
45
|
+
|
|
46
|
+
const IDE_METADATA = {
|
|
47
|
+
ideType: "IDE_UNSPECIFIED",
|
|
48
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
49
|
+
pluginType: "GEMINI",
|
|
50
|
+
duetProject: "default",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function loadSettings(ctx) {
|
|
54
|
+
if (!ctx.host.fs.exists(SETTINGS_PATH)) return null
|
|
55
|
+
try {
|
|
56
|
+
return ctx.util.tryParseJson(ctx.host.fs.readText(SETTINGS_PATH))
|
|
57
|
+
} catch (e) {
|
|
58
|
+
ctx.host.log.warn("failed reading settings: " + String(e))
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function assertSupportedAuthType(ctx) {
|
|
64
|
+
const settings = loadSettings(ctx)
|
|
65
|
+
const authType =
|
|
66
|
+
settings && typeof settings.authType === "string" ? settings.authType.trim().toLowerCase() : null
|
|
67
|
+
|
|
68
|
+
if (!authType || authType === "oauth-personal") return
|
|
69
|
+
if (authType === "api-key") {
|
|
70
|
+
throw "Gemini auth type api-key is not supported by this plugin yet."
|
|
71
|
+
}
|
|
72
|
+
if (authType === "vertex-ai") {
|
|
73
|
+
throw "Gemini auth type vertex-ai is not supported by this plugin yet."
|
|
74
|
+
}
|
|
75
|
+
throw "Gemini unsupported auth type: " + authType
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function loadOauthCreds(ctx) {
|
|
79
|
+
if (!ctx.host.fs.exists(CREDS_PATH)) return null
|
|
80
|
+
try {
|
|
81
|
+
const parsed = ctx.util.tryParseJson(ctx.host.fs.readText(CREDS_PATH))
|
|
82
|
+
if (!parsed || typeof parsed !== "object") return null
|
|
83
|
+
if (!parsed.access_token && !parsed.refresh_token) return null
|
|
84
|
+
return parsed
|
|
85
|
+
} catch (e) {
|
|
86
|
+
ctx.host.log.warn("failed reading creds: " + String(e))
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function saveOauthCreds(ctx, creds) {
|
|
92
|
+
try {
|
|
93
|
+
ctx.host.fs.writeText(CREDS_PATH, JSON.stringify(creds, null, 2))
|
|
94
|
+
} catch (e) {
|
|
95
|
+
ctx.host.log.warn("failed persisting creds: " + String(e))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseOauthClientCreds(text) {
|
|
100
|
+
if (!text || typeof text !== "string") return null
|
|
101
|
+
const idMatch = text.match(/OAUTH_CLIENT_ID\s*=\s*['"]([^'"]+)['"]/)
|
|
102
|
+
const secretMatch = text.match(/OAUTH_CLIENT_SECRET\s*=\s*['"]([^'"]+)['"]/)
|
|
103
|
+
if (!idMatch || !secretMatch) return null
|
|
104
|
+
return { clientId: idMatch[1], clientSecret: secretMatch[1] }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function loadOauthClientCreds(ctx) {
|
|
108
|
+
const allPaths = OAUTH2_JS_PATHS.concat(discoverPnpmOauth2Paths(ctx))
|
|
109
|
+
for (let i = 0; i < allPaths.length; i += 1) {
|
|
110
|
+
const path = allPaths[i]
|
|
111
|
+
if (!ctx.host.fs.exists(path)) continue
|
|
112
|
+
try {
|
|
113
|
+
const parsed = parseOauthClientCreds(ctx.host.fs.readText(path))
|
|
114
|
+
if (parsed) return parsed
|
|
115
|
+
} catch (e) {
|
|
116
|
+
ctx.host.log.warn("failed reading oauth2.js: " + String(e))
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function readNumber(value) {
|
|
123
|
+
const n = Number(value)
|
|
124
|
+
return Number.isFinite(n) ? n : null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function decodeIdToken(ctx, token) {
|
|
128
|
+
if (typeof token !== "string" || !token) return null
|
|
129
|
+
try {
|
|
130
|
+
const payload = ctx.jwt.decodePayload(token)
|
|
131
|
+
return payload && typeof payload === "object" ? payload : null
|
|
132
|
+
} catch {
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function needsRefresh(creds) {
|
|
138
|
+
if (!creds.access_token) return true
|
|
139
|
+
const expiry = readNumber(creds.expiry_date)
|
|
140
|
+
if (expiry === null) return false
|
|
141
|
+
const expiryMs = expiry > 10_000_000_000 ? expiry : expiry * 1000
|
|
142
|
+
return Date.now() + REFRESH_BUFFER_MS >= expiryMs
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function refreshToken(ctx, creds) {
|
|
146
|
+
if (!creds.refresh_token) return null
|
|
147
|
+
const clientCreds = loadOauthClientCreds(ctx)
|
|
148
|
+
if (!clientCreds) return null
|
|
149
|
+
|
|
150
|
+
let resp
|
|
151
|
+
try {
|
|
152
|
+
resp = ctx.util.request({
|
|
153
|
+
method: "POST",
|
|
154
|
+
url: TOKEN_URL,
|
|
155
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
156
|
+
bodyText:
|
|
157
|
+
"client_id=" +
|
|
158
|
+
encodeURIComponent(clientCreds.clientId) +
|
|
159
|
+
"&client_secret=" +
|
|
160
|
+
encodeURIComponent(clientCreds.clientSecret) +
|
|
161
|
+
"&refresh_token=" +
|
|
162
|
+
encodeURIComponent(creds.refresh_token) +
|
|
163
|
+
"&grant_type=refresh_token",
|
|
164
|
+
timeoutMs: 15000,
|
|
165
|
+
})
|
|
166
|
+
} catch (e) {
|
|
167
|
+
ctx.host.log.warn("refresh request failed: " + String(e))
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (ctx.util.isAuthStatus(resp.status)) {
|
|
172
|
+
throw "Gemini session expired. Run `gemini auth login` to authenticate."
|
|
173
|
+
}
|
|
174
|
+
if (resp.status < 200 || resp.status >= 300) return null
|
|
175
|
+
|
|
176
|
+
const data = ctx.util.tryParseJson(resp.bodyText)
|
|
177
|
+
if (!data || typeof data.access_token !== "string" || !data.access_token) return null
|
|
178
|
+
|
|
179
|
+
creds.access_token = data.access_token
|
|
180
|
+
if (typeof data.id_token === "string" && data.id_token) creds.id_token = data.id_token
|
|
181
|
+
if (typeof data.refresh_token === "string" && data.refresh_token) creds.refresh_token = data.refresh_token
|
|
182
|
+
if (typeof data.expires_in === "number") {
|
|
183
|
+
creds.expiry_date = Date.now() + data.expires_in * 1000
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
saveOauthCreds(ctx, creds)
|
|
187
|
+
return creds.access_token
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function postJson(ctx, url, accessToken, body) {
|
|
191
|
+
return ctx.util.request({
|
|
192
|
+
method: "POST",
|
|
193
|
+
url,
|
|
194
|
+
headers: {
|
|
195
|
+
Authorization: "Bearer " + accessToken,
|
|
196
|
+
Accept: "application/json",
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
},
|
|
199
|
+
bodyText: JSON.stringify(body || {}),
|
|
200
|
+
timeoutMs: 10000,
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function readFirstStringDeep(value, keys) {
|
|
205
|
+
if (!value || typeof value !== "object") return null
|
|
206
|
+
|
|
207
|
+
for (let i = 0; i < keys.length; i += 1) {
|
|
208
|
+
const v = value[keys[i]]
|
|
209
|
+
if (typeof v === "string" && v.trim()) return v.trim()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const nested = Object.values(value)
|
|
213
|
+
for (let i = 0; i < nested.length; i += 1) {
|
|
214
|
+
const found = readFirstStringDeep(nested[i], keys)
|
|
215
|
+
if (found) return found
|
|
216
|
+
}
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function mapTierToPlan(tier, idTokenPayload) {
|
|
221
|
+
if (!tier) return null
|
|
222
|
+
const normalized = String(tier).trim().toLowerCase()
|
|
223
|
+
if (normalized === "standard-tier") return "Paid"
|
|
224
|
+
if (normalized === "legacy-tier") return "Legacy"
|
|
225
|
+
if (normalized === "free-tier") return idTokenPayload && idTokenPayload.hd ? "Workspace" : "Free"
|
|
226
|
+
return null
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function discoverProjectId(ctx, accessToken, loadCodeAssistData) {
|
|
230
|
+
const fromLoadCodeAssist = readFirstStringDeep(loadCodeAssistData, ["cloudaicompanionProject"])
|
|
231
|
+
if (fromLoadCodeAssist) return fromLoadCodeAssist
|
|
232
|
+
|
|
233
|
+
let projectsResp
|
|
234
|
+
try {
|
|
235
|
+
projectsResp = ctx.util.request({
|
|
236
|
+
method: "GET",
|
|
237
|
+
url: PROJECTS_URL,
|
|
238
|
+
headers: { Authorization: "Bearer " + accessToken, Accept: "application/json" },
|
|
239
|
+
timeoutMs: 10000,
|
|
240
|
+
})
|
|
241
|
+
} catch (e) {
|
|
242
|
+
ctx.host.log.warn("project discovery failed: " + String(e))
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (projectsResp.status < 200 || projectsResp.status >= 300) return null
|
|
247
|
+
const projectsData = ctx.util.tryParseJson(projectsResp.bodyText)
|
|
248
|
+
const projects = projectsData && Array.isArray(projectsData.projects) ? projectsData.projects : []
|
|
249
|
+
for (let i = 0; i < projects.length; i += 1) {
|
|
250
|
+
const project = projects[i]
|
|
251
|
+
const projectId = project && typeof project.projectId === "string" ? project.projectId : null
|
|
252
|
+
if (!projectId) continue
|
|
253
|
+
if (projectId.indexOf("gen-lang-client") === 0) return projectId
|
|
254
|
+
const labels = project && project.labels && typeof project.labels === "object" ? project.labels : null
|
|
255
|
+
if (labels && labels["generative-language"] !== undefined) return projectId
|
|
256
|
+
}
|
|
257
|
+
return null
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function collectQuotaBuckets(value, out) {
|
|
261
|
+
if (Array.isArray(value)) {
|
|
262
|
+
for (let i = 0; i < value.length; i += 1) collectQuotaBuckets(value[i], out)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
if (!value || typeof value !== "object") return
|
|
266
|
+
|
|
267
|
+
if (typeof value.remainingFraction === "number") {
|
|
268
|
+
const modelId =
|
|
269
|
+
typeof value.modelId === "string"
|
|
270
|
+
? value.modelId
|
|
271
|
+
: typeof value.model_id === "string"
|
|
272
|
+
? value.model_id
|
|
273
|
+
: "unknown"
|
|
274
|
+
out.push({
|
|
275
|
+
modelId,
|
|
276
|
+
remainingFraction: value.remainingFraction,
|
|
277
|
+
resetTime: value.resetTime || value.reset_time || null,
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const nested = Object.values(value)
|
|
282
|
+
for (let i = 0; i < nested.length; i += 1) collectQuotaBuckets(nested[i], out)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function pickLowestRemainingBucket(buckets) {
|
|
286
|
+
let best = null
|
|
287
|
+
for (let i = 0; i < buckets.length; i += 1) {
|
|
288
|
+
const bucket = buckets[i]
|
|
289
|
+
if (!Number.isFinite(bucket.remainingFraction)) continue
|
|
290
|
+
if (!best || bucket.remainingFraction < best.remainingFraction) best = bucket
|
|
291
|
+
}
|
|
292
|
+
return best
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function toUsageLine(ctx, label, bucket) {
|
|
296
|
+
const clampedRemaining = Math.max(0, Math.min(1, Number(bucket.remainingFraction)))
|
|
297
|
+
const used = Math.round((1 - clampedRemaining) * 100)
|
|
298
|
+
const resetsAt = ctx.util.toIso(bucket.resetTime)
|
|
299
|
+
const opts = {
|
|
300
|
+
label,
|
|
301
|
+
used,
|
|
302
|
+
limit: 100,
|
|
303
|
+
format: { kind: "percent" },
|
|
304
|
+
}
|
|
305
|
+
if (resetsAt) opts.resetsAt = resetsAt
|
|
306
|
+
return ctx.line.progress(opts)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function parseQuotaLines(ctx, quotaData) {
|
|
310
|
+
const buckets = []
|
|
311
|
+
collectQuotaBuckets(quotaData, buckets)
|
|
312
|
+
if (!buckets.length) return []
|
|
313
|
+
|
|
314
|
+
const proBuckets = []
|
|
315
|
+
const flashBuckets = []
|
|
316
|
+
for (let i = 0; i < buckets.length; i += 1) {
|
|
317
|
+
const bucket = buckets[i]
|
|
318
|
+
const lower = String(bucket.modelId || "").toLowerCase()
|
|
319
|
+
if (lower.indexOf("gemini") !== -1 && lower.indexOf("pro") !== -1) {
|
|
320
|
+
proBuckets.push(bucket)
|
|
321
|
+
} else if (lower.indexOf("gemini") !== -1 && lower.indexOf("flash") !== -1) {
|
|
322
|
+
flashBuckets.push(bucket)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const lines = []
|
|
327
|
+
const pro = pickLowestRemainingBucket(proBuckets)
|
|
328
|
+
if (pro) lines.push(toUsageLine(ctx, "Pro", pro))
|
|
329
|
+
const flash = pickLowestRemainingBucket(flashBuckets)
|
|
330
|
+
if (flash) lines.push(toUsageLine(ctx, "Flash", flash))
|
|
331
|
+
return lines
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function fetchLoadCodeAssist(ctx, accessToken, creds) {
|
|
335
|
+
let currentToken = accessToken
|
|
336
|
+
const resp = ctx.util.retryOnceOnAuth({
|
|
337
|
+
request: function (token) {
|
|
338
|
+
return postJson(ctx, LOAD_CODE_ASSIST_URL, token || currentToken, { metadata: IDE_METADATA })
|
|
339
|
+
},
|
|
340
|
+
refresh: function () {
|
|
341
|
+
const refreshed = refreshToken(ctx, creds)
|
|
342
|
+
if (refreshed) currentToken = refreshed
|
|
343
|
+
return refreshed
|
|
344
|
+
},
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
if (ctx.util.isAuthStatus(resp.status)) {
|
|
348
|
+
throw "Gemini session expired. Run `gemini auth login` to authenticate."
|
|
349
|
+
}
|
|
350
|
+
if (resp.status < 200 || resp.status >= 300) return { data: null, accessToken: currentToken }
|
|
351
|
+
return { data: ctx.util.tryParseJson(resp.bodyText), accessToken: currentToken }
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function fetchQuotaWithRetry(ctx, accessToken, creds, projectId) {
|
|
355
|
+
let currentToken = accessToken
|
|
356
|
+
const resp = ctx.util.retryOnceOnAuth({
|
|
357
|
+
request: function (token) {
|
|
358
|
+
const body = projectId ? { project: projectId } : {}
|
|
359
|
+
return postJson(ctx, QUOTA_URL, token || currentToken, body)
|
|
360
|
+
},
|
|
361
|
+
refresh: function () {
|
|
362
|
+
const refreshed = refreshToken(ctx, creds)
|
|
363
|
+
if (refreshed) currentToken = refreshed
|
|
364
|
+
return refreshed
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
if (ctx.util.isAuthStatus(resp.status)) {
|
|
369
|
+
throw "Gemini session expired. Run `gemini auth login` to authenticate."
|
|
370
|
+
}
|
|
371
|
+
if (resp.status < 200 || resp.status >= 300) {
|
|
372
|
+
throw "Gemini quota request failed (HTTP " + String(resp.status) + "). Try again later."
|
|
373
|
+
}
|
|
374
|
+
return resp
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function probe(ctx) {
|
|
378
|
+
assertSupportedAuthType(ctx)
|
|
379
|
+
|
|
380
|
+
const creds = loadOauthCreds(ctx)
|
|
381
|
+
if (!creds) throw "Not logged in. Run `gemini auth login` to authenticate."
|
|
382
|
+
|
|
383
|
+
let accessToken = creds.access_token
|
|
384
|
+
if (needsRefresh(creds)) {
|
|
385
|
+
const refreshed = refreshToken(ctx, creds)
|
|
386
|
+
if (refreshed) accessToken = refreshed
|
|
387
|
+
else if (!accessToken) throw "Not logged in. Run `gemini auth login` to authenticate."
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const idTokenPayload = decodeIdToken(ctx, creds.id_token)
|
|
391
|
+
const loadCodeAssistResult = fetchLoadCodeAssist(ctx, accessToken, creds)
|
|
392
|
+
accessToken = loadCodeAssistResult.accessToken
|
|
393
|
+
|
|
394
|
+
const tier = readFirstStringDeep(loadCodeAssistResult.data, ["tier", "userTier", "subscriptionTier"])
|
|
395
|
+
const plan = mapTierToPlan(tier, idTokenPayload)
|
|
396
|
+
|
|
397
|
+
const projectId = discoverProjectId(ctx, accessToken, loadCodeAssistResult.data)
|
|
398
|
+
const quotaResp = fetchQuotaWithRetry(ctx, accessToken, creds, projectId)
|
|
399
|
+
const quotaData = ctx.util.tryParseJson(quotaResp.bodyText)
|
|
400
|
+
if (!quotaData || typeof quotaData !== "object") {
|
|
401
|
+
throw "Gemini quota response invalid. Try again later."
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const lines = parseQuotaLines(ctx, quotaData)
|
|
405
|
+
const email = idTokenPayload && typeof idTokenPayload.email === "string" ? idTokenPayload.email : null
|
|
406
|
+
if (email) lines.push(ctx.line.text({ label: "Account", value: email }))
|
|
407
|
+
if (!lines.length) lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" }))
|
|
408
|
+
|
|
409
|
+
return { plan: plan || undefined, lines }
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
globalThis.__openusage_plugin = { id: "gemini", probe }
|
|
413
|
+
})()
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
"cli": {
|
|
10
|
+
"category": "cli",
|
|
11
|
+
"binaryName": "gemini",
|
|
12
|
+
"installCmd": "npm install -g @google/gemini-cli",
|
|
13
|
+
"loginCmd": "gemini auth login"
|
|
14
|
+
},
|
|
15
|
+
"lines": [
|
|
16
|
+
{ "type": "progress", "label": "Pro", "scope": "overview", "primaryOrder": 1 },
|
|
17
|
+
{ "type": "progress", "label": "Flash", "scope": "overview", "primaryOrder": 2 },
|
|
18
|
+
{ "type": "text", "label": "Account", "scope": "detail" }
|
|
19
|
+
]
|
|
20
|
+
}
|