@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.
- package/LICENSE +57744 -0
- package/native-manifest.json +1414 -0
- package/package.json +254 -0
- package/plugins/abacus-ai/icon.svg +18 -0
- package/plugins/abacus-ai/plugin.js +121 -0
- package/plugins/abacus-ai/plugin.json +17 -0
- package/plugins/alibaba/icon-color.svg +1 -0
- package/plugins/alibaba/icon.svg +1 -0
- package/plugins/alibaba/plugin.js +322 -0
- package/plugins/alibaba/plugin.json +20 -0
- package/plugins/alibaba-token-plan/icon-color.svg +1 -0
- package/plugins/alibaba-token-plan/icon.svg +1 -0
- package/plugins/alibaba-token-plan/plugin.js +544 -0
- package/plugins/alibaba-token-plan/plugin.json +19 -0
- package/plugins/amp/icon-color.svg +1 -0
- package/plugins/amp/icon-conflict.svg +6 -0
- package/plugins/amp/icon.svg +1 -0
- package/plugins/amp/plugin.js +175 -0
- package/plugins/amp/plugin.json +16 -0
- package/plugins/antigravity/icon-color.svg +1 -0
- package/plugins/antigravity/icon-conflict.svg +3 -0
- package/plugins/antigravity/icon.svg +1 -0
- package/plugins/antigravity/plugin.js +739 -0
- package/plugins/antigravity/plugin.json +31 -0
- package/plugins/antigravity-cli/icon.svg +3 -0
- package/plugins/antigravity-cli/plugin.js +570 -0
- package/plugins/antigravity-cli/plugin.json +16 -0
- package/plugins/antigravity-ide/icon.svg +3 -0
- package/plugins/antigravity-ide/plugin.js +257 -0
- package/plugins/antigravity-ide/plugin.json +29 -0
- package/plugins/augment/icon.svg +14 -0
- package/plugins/augment/plugin.js +59 -0
- package/plugins/augment/plugin.json +18 -0
- package/plugins/aws-bedrock/icon-color.svg +1 -0
- package/plugins/aws-bedrock/icon.svg +1 -0
- package/plugins/aws-bedrock/plugin.js +206 -0
- package/plugins/aws-bedrock/plugin.json +18 -0
- package/plugins/azure-openai/icon-color.svg +1 -0
- package/plugins/azure-openai/icon.svg +1 -0
- package/plugins/azure-openai/plugin.js +145 -0
- package/plugins/azure-openai/plugin.json +17 -0
- package/plugins/chutes/icon.svg +7 -0
- package/plugins/chutes/plugin.js +236 -0
- package/plugins/chutes/plugin.json +19 -0
- package/plugins/claude/icon-color.svg +1 -0
- package/plugins/claude/icon.svg +1 -0
- package/plugins/claude/plugin.js +1723 -0
- package/plugins/claude/plugin.json +30 -0
- package/plugins/codebuff/icon.svg +92 -0
- package/plugins/codebuff/plugin.js +113 -0
- package/plugins/codebuff/plugin.json +17 -0
- package/plugins/codex/icon-color.svg +5 -0
- package/plugins/codex/icon.svg +1 -0
- package/plugins/codex/plugin.js +1009 -0
- package/plugins/codex/plugin.json +82 -0
- package/plugins/command-code/icon.svg +3 -0
- package/plugins/command-code/plugin.js +184 -0
- package/plugins/command-code/plugin.json +20 -0
- package/plugins/copilot/icon-color.svg +1 -0
- package/plugins/copilot/icon.svg +1 -0
- package/plugins/copilot/plugin.js +684 -0
- package/plugins/copilot/plugin.json +21 -0
- package/plugins/crof/icon.svg +3 -0
- package/plugins/crof/plugin.js +53 -0
- package/plugins/crof/plugin.json +17 -0
- package/plugins/crofai/icon.svg +16 -0
- package/plugins/crofai/plugin.js +81 -0
- package/plugins/crofai/plugin.json +18 -0
- package/plugins/cursor/icon-conflict.svg +3 -0
- package/plugins/cursor/icon.svg +1 -0
- package/plugins/cursor/plugin.js +1379 -0
- package/plugins/cursor/plugin.json +23 -0
- package/plugins/cursor-nightly/icon.png +0 -0
- package/plugins/cursor-nightly/plugin.js +1379 -0
- package/plugins/cursor-nightly/plugin.json +23 -0
- package/plugins/deepgram/icon.svg +1 -0
- package/plugins/deepgram/plugin.js +20 -0
- package/plugins/deepgram/plugin.json +17 -0
- package/plugins/deepseek/icon-color.svg +1 -0
- package/plugins/deepseek/icon.svg +1 -0
- package/plugins/deepseek/plugin.js +71 -0
- package/plugins/deepseek/plugin.json +17 -0
- package/plugins/devin/icon.svg +5 -0
- package/plugins/devin/plugin.js +341 -0
- package/plugins/devin/plugin.json +16 -0
- package/plugins/doubao/icon-color.svg +1 -0
- package/plugins/doubao/icon.svg +1 -0
- package/plugins/doubao/plugin.js +92 -0
- package/plugins/doubao/plugin.json +17 -0
- package/plugins/droid/icon.svg +1 -0
- package/plugins/droid/plugin.json +20 -0
- package/plugins/elevenlabs/icon.svg +4 -0
- package/plugins/elevenlabs/plugin.js +48 -0
- package/plugins/elevenlabs/plugin.json +18 -0
- package/plugins/factory/icon-conflict.svg +1 -0
- package/plugins/factory/icon.svg +1 -0
- package/plugins/factory/plugin.js +487 -0
- package/plugins/factory/plugin.json +15 -0
- package/plugins/fireworks-ai/icon.svg +6 -0
- package/plugins/fireworks-ai/plugin.js +370 -0
- package/plugins/fireworks-ai/plugin.json +19 -0
- package/plugins/gemini/icon-color.svg +1 -0
- package/plugins/gemini/icon.svg +1 -0
- package/plugins/gemini/plugin.js +460 -0
- package/plugins/gemini/plugin.json +16 -0
- package/plugins/grok/icon.svg +1 -0
- package/plugins/grok/plugin.js +297 -0
- package/plugins/grok/plugin.json +19 -0
- package/plugins/groqcloud/icon.svg +1 -0
- package/plugins/groqcloud/plugin.js +117 -0
- package/plugins/groqcloud/plugin.json +19 -0
- package/plugins/jetbrains-ai-assistant/icon-color.svg +14 -0
- package/plugins/jetbrains-ai-assistant/icon-conflict.svg +3 -0
- package/plugins/jetbrains-ai-assistant/icon.svg +14 -0
- package/plugins/jetbrains-ai-assistant/plugin.js +313 -0
- package/plugins/jetbrains-ai-assistant/plugin.json +16 -0
- package/plugins/kilo/icon.svg +1 -0
- package/plugins/kilo/plugin.js +111 -0
- package/plugins/kilo/plugin.json +19 -0
- package/plugins/kimi/icon-color.svg +1 -0
- package/plugins/kimi/icon-conflict.svg +3 -0
- package/plugins/kimi/icon.svg +1 -0
- package/plugins/kimi/plugin.js +358 -0
- package/plugins/kimi/plugin.json +15 -0
- package/plugins/kimi-k2/icon-color.svg +1 -0
- package/plugins/kimi-k2/icon.svg +1 -0
- package/plugins/kimi-k2/plugin.js +58 -0
- package/plugins/kimi-k2/plugin.json +18 -0
- package/plugins/kiro/icon-color.svg +5 -0
- package/plugins/kiro/icon-conflict.svg +1 -0
- package/plugins/kiro/icon.svg +4 -0
- package/plugins/kiro/plugin.js +390 -0
- package/plugins/kiro/plugin.json +16 -0
- package/plugins/litellm/icon.svg +16 -0
- package/plugins/litellm/plugin.js +199 -0
- package/plugins/litellm/plugin.json +16 -0
- package/plugins/llm-proxy/icon.svg +1 -0
- package/plugins/llm-proxy/plugin.js +40 -0
- package/plugins/llm-proxy/plugin.json +17 -0
- package/plugins/manus/icon.svg +6 -0
- package/plugins/manus/plugin.js +50 -0
- package/plugins/manus/plugin.json +17 -0
- package/plugins/mimo/icon.svg +1 -0
- package/plugins/mimo/plugin.js +177 -0
- package/plugins/mimo/plugin.json +19 -0
- package/plugins/minimax/icon-color.svg +1 -0
- package/plugins/minimax/icon-conflict.svg +4 -0
- package/plugins/minimax/icon.svg +1 -0
- package/plugins/minimax/plugin.js +453 -0
- package/plugins/minimax/plugin.json +14 -0
- package/plugins/mistral/icon-color.svg +1 -0
- package/plugins/mistral/icon.svg +1 -0
- package/plugins/mistral/plugin.js +152 -0
- package/plugins/mistral/plugin.json +18 -0
- package/plugins/moonshot/icon.svg +1 -0
- package/plugins/moonshot/plugin.js +43 -0
- package/plugins/moonshot/plugin.json +16 -0
- package/plugins/nanogpt/icon-color.svg +17 -0
- package/plugins/nanogpt/icon.svg +10 -0
- package/plugins/nanogpt/plugin.js +80 -0
- package/plugins/nanogpt/plugin.json +17 -0
- package/plugins/neuralwatt/icon.svg +3 -0
- package/plugins/neuralwatt/plugin.js +152 -0
- package/plugins/neuralwatt/plugin.json +17 -0
- package/plugins/ollama/icon.svg +1 -0
- package/plugins/ollama/plugin.js +92 -0
- package/plugins/ollama/plugin.json +20 -0
- package/plugins/openai-api/icon.svg +1 -0
- package/plugins/openai-api/plugin.js +533 -0
- package/plugins/openai-api/plugin.json +26 -0
- package/plugins/opencode/icon.svg +1 -0
- package/plugins/opencode/plugin.js +342 -0
- package/plugins/opencode/plugin.json +19 -0
- package/plugins/opencode-go/icon-conflict.svg +1 -0
- package/plugins/opencode-go/icon.svg +1 -0
- package/plugins/opencode-go/plugin.js +617 -0
- package/plugins/opencode-go/plugin.json +50 -0
- package/plugins/openrouter/icon.svg +1 -0
- package/plugins/openrouter/plugin.js +156 -0
- package/plugins/openrouter/plugin.json +8 -0
- package/plugins/perplexity/icon-color.svg +1 -0
- package/plugins/perplexity/icon-conflict.svg +1 -0
- package/plugins/perplexity/icon.svg +1 -0
- package/plugins/perplexity/plugin.js +425 -0
- package/plugins/perplexity/plugin.json +19 -0
- package/plugins/poe/icon.svg +1 -0
- package/plugins/poe/plugin.js +190 -0
- package/plugins/poe/plugin.json +19 -0
- package/plugins/stepfun/icon-color.svg +1 -0
- package/plugins/stepfun/icon.svg +1 -0
- package/plugins/stepfun/plugin.js +155 -0
- package/plugins/stepfun/plugin.json +18 -0
- package/plugins/synthetic/icon-conflict.svg +12 -0
- package/plugins/synthetic/icon.svg +14 -0
- package/plugins/synthetic/plugin.js +331 -0
- package/plugins/synthetic/plugin.json +23 -0
- package/plugins/t3chat/icon-color.svg +28 -0
- package/plugins/t3chat/icon.svg +4 -0
- package/plugins/t3chat/plugin.js +243 -0
- package/plugins/t3chat/plugin.json +20 -0
- package/plugins/venice/icon-color.svg +1 -0
- package/plugins/venice/icon.svg +1 -0
- package/plugins/venice/plugin.js +69 -0
- package/plugins/venice/plugin.json +16 -0
- package/plugins/vertex-ai/icon-color.svg +1 -0
- package/plugins/vertex-ai/icon.svg +1 -0
- package/plugins/vertex-ai/plugin.js +172 -0
- package/plugins/vertex-ai/plugin.json +18 -0
- package/plugins/warp/icon-color.svg +10 -0
- package/plugins/warp/icon.svg +6 -0
- package/plugins/warp/plugin.js +108 -0
- package/plugins/warp/plugin.json +18 -0
- package/plugins/windsurf/icon-conflict.svg +3 -0
- package/plugins/windsurf/icon.svg +1 -0
- package/plugins/windsurf/plugin.js +208 -0
- package/plugins/windsurf/plugin.json +16 -0
- package/plugins/zai/icon-conflict.svg +5 -0
- package/plugins/zai/icon.svg +1 -0
- package/plugins/zai/plugin.js +188 -0
- package/plugins/zai/plugin.json +16 -0
- package/plugins/zed/icon.svg +4 -0
- package/plugins/zed/plugin.js +270 -0
- package/plugins/zed/plugin.json +19 -0
- package/usagestat +0 -0
- 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>
|