@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,453 @@
1
+ (function () {
2
+ const GLOBAL_PRIMARY_USAGE_URL = "https://www.minimax.io/v1/token_plan/remains"
3
+ const GLOBAL_FALLBACK_USAGE_URLS = [
4
+ "https://www.minimax.io/v1/token_plan/remains",
5
+ ]
6
+ const CN_PRIMARY_USAGE_URL = "https://api.minimaxi.com/v1/token_plan/remains"
7
+ const CN_FALLBACK_USAGE_URLS = ["https://api.minimaxi.com/v1/token_plan/remains"]
8
+ const GLOBAL_API_KEY_ENV_VARS = ["MINIMAX_API_KEY", "MINIMAX_API_TOKEN"]
9
+ const CN_API_KEY_ENV_VARS = ["MINIMAX_CN_API_KEY", "MINIMAX_API_KEY", "MINIMAX_API_TOKEN"]
10
+ const CODING_PLAN_WINDOW_MS = 5 * 60 * 60 * 1000
11
+ const CODING_PLAN_WINDOW_TOLERANCE_MS = 10 * 60 * 1000
12
+ // GLOBAL plan tiers (based on prompt limits)
13
+ const GLOBAL_PROMPT_LIMIT_TO_PLAN = {
14
+ 100: "Starter",
15
+ 300: "Plus",
16
+ 1000: "Max",
17
+ 2000: "Ultra",
18
+ }
19
+ // CN plan tiers (based on model call counts = prompts × 15)
20
+ // Starter: 40 prompts = 600, Plus: 100 prompts = 1500, Max: 300 prompts = 4500
21
+ const CN_PROMPT_LIMIT_TO_PLAN = {
22
+ 600: "Starter",
23
+ 1500: "Plus",
24
+ 4500: "Max",
25
+ }
26
+ const MODEL_CALLS_PER_PROMPT = 15
27
+
28
+ function readString(value) {
29
+ if (typeof value !== "string") return null
30
+ const trimmed = value.trim()
31
+ return trimmed ? trimmed : null
32
+ }
33
+
34
+ function readNumber(value) {
35
+ if (typeof value === "number") return Number.isFinite(value) ? value : null
36
+ if (typeof value !== "string") return null
37
+ const trimmed = value.trim()
38
+ if (!trimmed) return null
39
+ const n = Number(trimmed)
40
+ return Number.isFinite(n) ? n : null
41
+ }
42
+
43
+ function pickFirstString(values) {
44
+ for (let i = 0; i < values.length; i += 1) {
45
+ const value = readString(values[i])
46
+ if (value) return value
47
+ }
48
+ return null
49
+ }
50
+
51
+ function normalizePlanName(value) {
52
+ const raw = readString(value)
53
+ if (!raw) return null
54
+ const compact = raw.replace(/\s+/g, " ").trim()
55
+ const withoutPrefix = compact.replace(/^minimax\s+coding\s+plan\b[:\-]?\s*/i, "").trim()
56
+ if (withoutPrefix) return withoutPrefix
57
+ if (/coding\s+plan/i.test(compact)) return "Coding Plan"
58
+ return compact
59
+ }
60
+
61
+ function inferPlanNameFromLimit(totalCount, endpointSelection) {
62
+ const n = readNumber(totalCount)
63
+ if (n === null || n <= 0) return null
64
+
65
+ const normalized = Math.round(n)
66
+ if (endpointSelection === "CN") {
67
+ // CN totals are model-call counts; only exact known CN tiers should infer.
68
+ return CN_PROMPT_LIMIT_TO_PLAN[normalized] || null
69
+ }
70
+
71
+ if (GLOBAL_PROMPT_LIMIT_TO_PLAN[normalized]) return GLOBAL_PROMPT_LIMIT_TO_PLAN[normalized]
72
+
73
+ if (normalized % MODEL_CALLS_PER_PROMPT !== 0) return null
74
+ const inferredPromptLimit = normalized / MODEL_CALLS_PER_PROMPT
75
+ return GLOBAL_PROMPT_LIMIT_TO_PLAN[inferredPromptLimit] || null
76
+ }
77
+
78
+ function epochToMs(epoch) {
79
+ const n = readNumber(epoch)
80
+ if (n === null) return null
81
+ return Math.abs(n) < 1e10 ? n * 1000 : n
82
+ }
83
+
84
+ function inferRemainsMs(remainsRaw, endMs, nowMs) {
85
+ if (remainsRaw === null || remainsRaw <= 0) return null
86
+
87
+ const asSecondsMs = remainsRaw * 1000
88
+ const asMillisecondsMs = remainsRaw
89
+
90
+ // If end_time exists, infer remains_time unit by whichever aligns best.
91
+ if (endMs !== null) {
92
+ const toEndMs = endMs - nowMs
93
+ if (toEndMs > 0) {
94
+ const secDelta = Math.abs(asSecondsMs - toEndMs)
95
+ const msDelta = Math.abs(asMillisecondsMs - toEndMs)
96
+ return secDelta <= msDelta ? asSecondsMs : asMillisecondsMs
97
+ }
98
+ }
99
+
100
+ // Coding Plan resets every 5h. Use that constraint before defaulting.
101
+ const maxExpectedMs = CODING_PLAN_WINDOW_MS + CODING_PLAN_WINDOW_TOLERANCE_MS
102
+ const secondsLooksValid = asSecondsMs <= maxExpectedMs
103
+ const millisecondsLooksValid = asMillisecondsMs <= maxExpectedMs
104
+
105
+ if (secondsLooksValid && !millisecondsLooksValid) return asSecondsMs
106
+ if (millisecondsLooksValid && !secondsLooksValid) return asMillisecondsMs
107
+ if (secondsLooksValid && millisecondsLooksValid) return asSecondsMs
108
+
109
+ const secOverflow = Math.abs(asSecondsMs - maxExpectedMs)
110
+ const msOverflow = Math.abs(asMillisecondsMs - maxExpectedMs)
111
+ return secOverflow <= msOverflow ? asSecondsMs : asMillisecondsMs
112
+ }
113
+
114
+ function loadApiKey(ctx, endpointSelection) {
115
+ const envVars = endpointSelection === "CN" ? CN_API_KEY_ENV_VARS : GLOBAL_API_KEY_ENV_VARS
116
+ for (let i = 0; i < envVars.length; i += 1) {
117
+ const name = envVars[i]
118
+ let value = null
119
+ try {
120
+ value = ctx.host.env.get(name)
121
+ } catch (e) {
122
+ ctx.host.log.warn("env read failed for " + name + ": " + String(e))
123
+ }
124
+ const key = readString(value)
125
+ if (key) {
126
+ ctx.host.log.info("api key loaded from " + name)
127
+ return { value: key, source: name }
128
+ }
129
+ }
130
+ return null
131
+ }
132
+
133
+ function getUsageUrls(endpointSelection) {
134
+ if (endpointSelection === "CN") {
135
+ return [CN_PRIMARY_USAGE_URL].concat(CN_FALLBACK_USAGE_URLS)
136
+ }
137
+ return [GLOBAL_PRIMARY_USAGE_URL].concat(GLOBAL_FALLBACK_USAGE_URLS)
138
+ }
139
+
140
+ function endpointAttempts(ctx) {
141
+ // AUTO: if CN key exists, try CN first; otherwise try GLOBAL first.
142
+ let cnApiKeyValue = null
143
+ try {
144
+ cnApiKeyValue = ctx.host.env.get("MINIMAX_CN_API_KEY")
145
+ } catch (e) {
146
+ ctx.host.log.warn("env read failed for MINIMAX_CN_API_KEY: " + String(e))
147
+ }
148
+ if (readString(cnApiKeyValue)) return ["CN", "GLOBAL"]
149
+ return ["GLOBAL", "CN"]
150
+ }
151
+
152
+ function formatAuthError() {
153
+ return "Session expired. Check your MiniMax API key."
154
+ }
155
+
156
+ /**
157
+ * Tries multiple URL candidates and returns the first successful response.
158
+ * @returns {object} parsed JSON response
159
+ * @throws {string} error message
160
+ */
161
+ function tryUrls(ctx, urls, apiKey) {
162
+ let lastStatus = null
163
+ let hadNetworkError = false
164
+ let authStatusCount = 0
165
+
166
+ for (let i = 0; i < urls.length; i += 1) {
167
+ const url = urls[i]
168
+ let resp
169
+ try {
170
+ resp = ctx.util.request({
171
+ method: "GET",
172
+ url: url,
173
+ headers: {
174
+ Authorization: "Bearer " + apiKey,
175
+ "Content-Type": "application/json",
176
+ Accept: "application/json",
177
+ },
178
+ timeoutMs: 15000,
179
+ })
180
+ } catch (e) {
181
+ hadNetworkError = true
182
+ ctx.host.log.warn("request failed (" + url + "): " + String(e))
183
+ continue
184
+ }
185
+
186
+ if (ctx.util.isAuthStatus(resp.status)) {
187
+ authStatusCount += 1
188
+ ctx.host.log.warn("request returned auth status " + resp.status + " (" + url + ")")
189
+ continue
190
+ }
191
+ if (resp.status < 200 || resp.status >= 300) {
192
+ lastStatus = resp.status
193
+ ctx.host.log.warn("request returned status " + resp.status + " (" + url + ")")
194
+ continue
195
+ }
196
+
197
+ const parsed = ctx.util.tryParseJson(resp.bodyText)
198
+ if (!parsed || typeof parsed !== "object") {
199
+ ctx.host.log.warn("request returned invalid JSON (" + url + ")")
200
+ continue
201
+ }
202
+
203
+ return parsed
204
+ }
205
+
206
+ if (authStatusCount > 0 && lastStatus === null && !hadNetworkError) {
207
+ throw formatAuthError()
208
+ }
209
+ if (lastStatus !== null) throw "Request failed (HTTP " + lastStatus + "). Try again later."
210
+ if (hadNetworkError) throw "Request failed. Check your connection."
211
+ throw "Could not parse usage data."
212
+ }
213
+
214
+ function parsePayloadShape(ctx, payload, endpointSelection) {
215
+ if (!payload || typeof payload !== "object") return null
216
+
217
+ const data = payload.data && typeof payload.data === "object" ? payload.data : payload
218
+ const baseResp = (data && data.base_resp) || payload.base_resp || null
219
+ const statusCode = readNumber(baseResp && baseResp.status_code)
220
+ const statusMessage = readString(baseResp && baseResp.status_msg)
221
+
222
+ if (statusCode !== null && statusCode !== 0) {
223
+ const normalized = (statusMessage || "").toLowerCase()
224
+ if (
225
+ statusCode === 1004 ||
226
+ normalized.includes("cookie") ||
227
+ normalized.includes("log in") ||
228
+ normalized.includes("login")
229
+ ) {
230
+ throw formatAuthError()
231
+ }
232
+ throw statusMessage
233
+ ? "MiniMax API error: " + statusMessage
234
+ : "MiniMax API error (status " + statusCode + ")."
235
+ }
236
+
237
+ const modelRemains =
238
+ (Array.isArray(data.model_remains) && data.model_remains) ||
239
+ (Array.isArray(payload.model_remains) && payload.model_remains) ||
240
+ (Array.isArray(data.modelRemains) && data.modelRemains) ||
241
+ (Array.isArray(payload.modelRemains) && payload.modelRemains) ||
242
+ null
243
+
244
+ if (!modelRemains || modelRemains.length === 0) return null
245
+
246
+ const displayMultiplierForSelection = endpointSelection === "CN" ? 1 / MODEL_CALLS_PER_PROMPT : 1
247
+ let chosen = null
248
+ let percentFallbackCandidate = null
249
+ let generalPercentFallbackCandidate = null
250
+ for (let i = 0; i < modelRemains.length; i += 1) {
251
+ const item = modelRemains[i]
252
+ if (!item || typeof item !== "object") continue
253
+ const total = readNumber(item.current_interval_total_count ?? item.currentIntervalTotalCount)
254
+ if (total !== null && total > 0 && Math.round(total * displayMultiplierForSelection) > 0) {
255
+ chosen = item
256
+ break
257
+ }
258
+ const remainingPercent = readNumber(
259
+ item.current_interval_remaining_percent ??
260
+ item.currentIntervalRemainingPercent
261
+ )
262
+ if (remainingPercent !== null && remainingPercent >= 0 && remainingPercent <= 100) {
263
+ const modelName = readString(item.model_name ?? item.modelName)
264
+ if (!percentFallbackCandidate) percentFallbackCandidate = item
265
+ if (!generalPercentFallbackCandidate && modelName === "general") {
266
+ generalPercentFallbackCandidate = item
267
+ }
268
+ }
269
+ }
270
+ if (!chosen) chosen = generalPercentFallbackCandidate || percentFallbackCandidate
271
+
272
+ if (!chosen || typeof chosen !== "object") return null
273
+
274
+ const total = readNumber(chosen.current_interval_total_count ?? chosen.currentIntervalTotalCount)
275
+ const remainingPercent = readNumber(
276
+ chosen.current_interval_remaining_percent ??
277
+ chosen.currentIntervalRemainingPercent
278
+ )
279
+
280
+ const hasDisplayableCount =
281
+ total !== null && total > 0 && Math.round(total * displayMultiplierForSelection) > 0
282
+
283
+ if (!hasDisplayableCount && remainingPercent !== null) {
284
+ const percentUsed = 100 - remainingPercent
285
+ const startMs = epochToMs(chosen.start_time ?? chosen.startTime)
286
+ const endMs = epochToMs(chosen.end_time ?? chosen.endTime)
287
+ const remainsRaw = readNumber(chosen.remains_time ?? chosen.remainsTime)
288
+ const nowMs = Date.now()
289
+ const remainsMs = inferRemainsMs(remainsRaw, endMs, nowMs)
290
+
291
+ let resetsAt = endMs !== null ? ctx.util.toIso(endMs) : null
292
+ if (!resetsAt && remainsMs !== null) {
293
+ resetsAt = ctx.util.toIso(nowMs + remainsMs)
294
+ }
295
+
296
+ let periodDurationMs = null
297
+ if (startMs !== null && endMs !== null && endMs > startMs) {
298
+ periodDurationMs = endMs - startMs
299
+ }
300
+
301
+ const explicitPlanName = normalizePlanName(pickFirstString([
302
+ data.current_subscribe_title,
303
+ data.plan_name,
304
+ data.plan,
305
+ data.current_plan_title,
306
+ data.combo_title,
307
+ payload.current_subscribe_title,
308
+ payload.plan_name,
309
+ payload.plan,
310
+ ]))
311
+
312
+ return {
313
+ planName: explicitPlanName || null,
314
+ used: percentUsed,
315
+ total: 100,
316
+ resetsAt,
317
+ periodDurationMs,
318
+ isPercent: true,
319
+ }
320
+ }
321
+
322
+ if (!hasDisplayableCount) return null
323
+
324
+ const usageFieldCount = readNumber(chosen.current_interval_usage_count ?? chosen.currentIntervalUsageCount)
325
+ const remainingCount = readNumber(
326
+ chosen.current_interval_remaining_count ??
327
+ chosen.currentIntervalRemainingCount ??
328
+ chosen.current_interval_remains_count ??
329
+ chosen.currentIntervalRemainsCount ??
330
+ chosen.current_interval_remain_count ??
331
+ chosen.currentIntervalRemainCount ??
332
+ chosen.remaining_count ??
333
+ chosen.remainingCount ??
334
+ chosen.remains_count ??
335
+ chosen.remainsCount ??
336
+ chosen.remaining ??
337
+ chosen.remains ??
338
+ chosen.left_count ??
339
+ chosen.leftCount
340
+ )
341
+ // MiniMax "coding_plan/remains" commonly returns remaining prompts in current_interval_usage_count.
342
+ const inferredRemainingCount = remainingCount !== null ? remainingCount : usageFieldCount
343
+ const explicitUsed = readNumber(
344
+ chosen.current_interval_used_count ??
345
+ chosen.currentIntervalUsedCount ??
346
+ chosen.used_count ??
347
+ chosen.used
348
+ )
349
+ let used = explicitUsed
350
+
351
+ if (used === null && inferredRemainingCount !== null) used = total - inferredRemainingCount
352
+ if (used === null) return null
353
+ if (used < 0) used = 0
354
+ if (used > total) used = total
355
+
356
+ const startMs = epochToMs(chosen.start_time ?? chosen.startTime)
357
+ const endMs = epochToMs(chosen.end_time ?? chosen.endTime)
358
+ const remainsRaw = readNumber(chosen.remains_time ?? chosen.remainsTime)
359
+ const nowMs = Date.now()
360
+ const remainsMs = inferRemainsMs(remainsRaw, endMs, nowMs)
361
+
362
+ let resetsAt = endMs !== null ? ctx.util.toIso(endMs) : null
363
+ if (!resetsAt && remainsMs !== null) {
364
+ resetsAt = ctx.util.toIso(nowMs + remainsMs)
365
+ }
366
+
367
+ let periodDurationMs = null
368
+ if (startMs !== null && endMs !== null && endMs > startMs) {
369
+ periodDurationMs = endMs - startMs
370
+ }
371
+
372
+ const explicitPlanName = normalizePlanName(pickFirstString([
373
+ data.current_subscribe_title,
374
+ data.plan_name,
375
+ data.plan,
376
+ data.current_plan_title,
377
+ data.combo_title,
378
+ payload.current_subscribe_title,
379
+ payload.plan_name,
380
+ payload.plan,
381
+ ]))
382
+ const inferredPlanName = inferPlanNameFromLimit(total, endpointSelection)
383
+ const planName = explicitPlanName || inferredPlanName
384
+
385
+ return {
386
+ planName,
387
+ used,
388
+ total,
389
+ resetsAt,
390
+ periodDurationMs,
391
+ isPercent: false,
392
+ }
393
+ }
394
+
395
+ function fetchUsagePayload(ctx, apiKey, endpointSelection) {
396
+ return tryUrls(ctx, getUsageUrls(endpointSelection), apiKey)
397
+ }
398
+
399
+ function probe(ctx) {
400
+ const attempts = endpointAttempts(ctx)
401
+ let lastError = null
402
+ let parsed = null
403
+ let successfulEndpoint = null
404
+
405
+ for (let i = 0; i < attempts.length; i += 1) {
406
+ const endpoint = attempts[i]
407
+ const apiKeyInfo = loadApiKey(ctx, endpoint)
408
+ if (!apiKeyInfo) continue
409
+ try {
410
+ const payload = fetchUsagePayload(ctx, apiKeyInfo.value, endpoint)
411
+ parsed = parsePayloadShape(ctx, payload, endpoint)
412
+ if (parsed) {
413
+ successfulEndpoint = endpoint
414
+ break
415
+ }
416
+ if (!lastError) lastError = "Could not parse usage data."
417
+ } catch (e) {
418
+ if (!lastError) lastError = String(e)
419
+ }
420
+ }
421
+
422
+ if (!parsed) {
423
+ if (lastError) throw lastError
424
+ throw "MiniMax API key missing. Set MINIMAX_API_KEY or MINIMAX_CN_API_KEY."
425
+ }
426
+
427
+ // CN API returns model call counts (needs division by 15 for prompts)
428
+ // GLOBAL API returns prompt counts directly
429
+ const isCnEndpoint = successfulEndpoint === "CN"
430
+ const displayMultiplier = isCnEndpoint ? 1 / MODEL_CALLS_PER_PROMPT : 1
431
+ const valueMultiplier = parsed.isPercent ? 1 : displayMultiplier
432
+
433
+ const line = {
434
+ label: "Session",
435
+ used: Math.round(parsed.used * valueMultiplier),
436
+ limit: Math.round(parsed.total * valueMultiplier),
437
+ format: parsed.isPercent
438
+ ? { kind: "percent" }
439
+ : { kind: "count", suffix: "prompts" },
440
+ }
441
+ if (parsed.resetsAt) line.resetsAt = parsed.resetsAt
442
+ if (parsed.periodDurationMs !== null) line.periodDurationMs = parsed.periodDurationMs
443
+
444
+ const result = { lines: [ctx.line.progress(line)] }
445
+ if (parsed.planName) {
446
+ const regionLabel = successfulEndpoint === "CN" ? " (CN)" : " (GLOBAL)"
447
+ result.plan = parsed.planName + regionLabel
448
+ }
449
+ return result
450
+ }
451
+
452
+ globalThis.__openusage_plugin = { id: "minimax", probe }
453
+ })()
@@ -0,0 +1,14 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "minimax",
4
+ "name": "MiniMax",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "icon": "icon.svg",
8
+ "brandColor": "#F5433C",
9
+ "supportedModes": ["api"],
10
+ "autoMode": "api",
11
+ "lines": [
12
+ { "type": "progress", "label": "Session", "scope": "overview", "primaryOrder": 1 }
13
+ ]
14
+ }
@@ -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>Mistral</title><path d="M3.428 3.4h3.429v3.428H3.428V3.4zm13.714 0h3.43v3.428h-3.43V3.4z" fill="gold"></path><path d="M3.428 6.828h6.857v3.429H3.429V6.828zm10.286 0h6.857v3.429h-6.857V6.828z" fill="#FFAF00"></path><path d="M3.428 10.258h17.144v3.428H3.428v-3.428z" fill="#FF8205"></path><path d="M3.428 13.686h3.429v3.428H3.428v-3.428zm6.858 0h3.429v3.428h-3.429v-3.428zm6.856 0h3.43v3.428h-3.43v-3.428z" fill="#FA500F"></path><path d="M0 17.114h10.286v3.429H0v-3.429zm13.714 0H24v3.429H13.714v-3.429z" fill="#E10500"></path></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>Mistral</title><path clip-rule="evenodd" d="M3.428 3.4h3.429v3.428h3.429v3.429h-.002 3.431V6.828h3.427V3.4h3.43v13.714H24v3.429H13.714v-3.428h-3.428v-3.429h-3.43v3.428h3.43v3.429H0v-3.429h3.428V3.4zm10.286 13.715h3.428v-3.429h-3.427v3.429z"></path></svg>
@@ -0,0 +1,152 @@
1
+ (function () {
2
+ var BASE_URL = "https://admin.mistral.ai"
3
+ var USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36"
4
+
5
+ function getCookieHeader(ctx) {
6
+ var raw = ctx.host.env.get("MISTRAL_COOKIE")
7
+ if (!raw || !raw.trim()) {
8
+ throw "Set MISTRAL_COOKIE to your cookie header from admin.mistral.ai. Include csrftoken if present."
9
+ }
10
+ return raw.trim()
11
+ }
12
+
13
+ function extractCsrf(cookieHeader) {
14
+ var parts = cookieHeader.split(";")
15
+ for (var i = 0; i < parts.length; i++) {
16
+ var pair = parts[i].trim()
17
+ var eq = pair.indexOf("=")
18
+ if (eq < 0) continue
19
+ var name = pair.slice(0, eq).trim()
20
+ if (name === "csrftoken") return pair.slice(eq + 1).trim()
21
+ }
22
+ return null
23
+ }
24
+
25
+ function currentMonthYear() {
26
+ var now = new Date()
27
+ return { month: now.getUTCMonth() + 1, year: now.getUTCFullYear() }
28
+ }
29
+
30
+ function aggregateModels(models, prices) {
31
+ var total = 0
32
+ var inputTokens = 0
33
+ var outputTokens = 0
34
+ if (!models) return { cost: 0, inputTokens: 0, outputTokens: 0 }
35
+ var keys = Object.keys(models)
36
+ for (var i = 0; i < keys.length; i++) {
37
+ var data = models[keys[i]]
38
+ total += sumEntries(data.input, prices)
39
+ total += sumEntries(data.output, prices)
40
+ total += sumEntries(data.cached, prices)
41
+ inputTokens += countEntries(data.input)
42
+ outputTokens += countEntries(data.output)
43
+ }
44
+ return { cost: total, inputTokens: inputTokens, outputTokens: outputTokens }
45
+ }
46
+
47
+ function sumEntries(entries, prices) {
48
+ if (!entries) return 0
49
+ var total = 0
50
+ for (var i = 0; i < entries.length; i++) {
51
+ var e = entries[i]
52
+ var paid = e.value_paid != null ? e.value_paid : (e.value || 0)
53
+ var key = e.billing_metric + "::" + e.billing_group
54
+ var price = prices[key] || 0
55
+ total += paid * price
56
+ }
57
+ return total
58
+ }
59
+
60
+ function countEntries(entries) {
61
+ if (!entries) return 0
62
+ var total = 0
63
+ for (var i = 0; i < entries.length; i++) {
64
+ var e = entries[i]
65
+ total += e.value_paid != null ? e.value_paid : (e.value || 0)
66
+ }
67
+ return total
68
+ }
69
+
70
+ function buildPriceIndex(prices) {
71
+ var index = {}
72
+ if (!prices) return index
73
+ for (var i = 0; i < prices.length; i++) {
74
+ var p = prices[i]
75
+ if (p.billing_metric && p.billing_group && p.price) {
76
+ var key = p.billing_metric + "::" + p.billing_group
77
+ index[key] = parseFloat(p.price) || 0
78
+ }
79
+ }
80
+ return index
81
+ }
82
+
83
+ function probe(ctx) {
84
+ var cookieHeader = getCookieHeader(ctx)
85
+ var csrf = extractCsrf(cookieHeader)
86
+ var my = currentMonthYear()
87
+
88
+ var headers = {
89
+ "Cookie": cookieHeader,
90
+ "Accept": "*/*",
91
+ "Origin": BASE_URL,
92
+ "Referer": BASE_URL + "/organization/usage",
93
+ "User-Agent": USER_AGENT,
94
+ }
95
+ if (csrf) headers["X-CSRFTOKEN"] = csrf
96
+
97
+ var result = ctx.util.requestJson({
98
+ method: "GET",
99
+ url: BASE_URL + "/api/billing/v2/usage?month=" + my.month + "&year=" + my.year,
100
+ headers: headers,
101
+ timeoutMs: 20000,
102
+ })
103
+
104
+ if (result.resp.status === 401 || result.resp.status === 403) {
105
+ throw "Session expired. Update MISTRAL_COOKIE with fresh cookies from admin.mistral.ai."
106
+ }
107
+ if (result.resp.status < 200 || result.resp.status >= 300) {
108
+ throw "Mistral API returned HTTP " + result.resp.status + "."
109
+ }
110
+
111
+ var billing = result.json
112
+ if (!billing) throw "Could not parse Mistral billing response."
113
+
114
+ var prices = buildPriceIndex(billing.prices)
115
+ var currency = billing.currency || "EUR"
116
+ var symbol = billing.currency_symbol || "€"
117
+
118
+ var totalCost = 0
119
+ var totalInput = 0
120
+ var totalOutput = 0
121
+
122
+ if (billing.completion && billing.completion.models) {
123
+ var r = aggregateModels(billing.completion.models, prices)
124
+ totalCost += r.cost; totalInput += r.inputTokens; totalOutput += r.outputTokens
125
+ }
126
+
127
+ var extras = [billing.ocr, billing.connectors, billing.audio]
128
+ for (var i = 0; i < extras.length; i++) {
129
+ if (extras[i] && extras[i].models) {
130
+ totalCost += aggregateModels(extras[i].models, prices).cost
131
+ }
132
+ }
133
+
134
+ if (billing.libraries_api) {
135
+ var lib = billing.libraries_api
136
+ if (lib.pages && lib.pages.models) totalCost += aggregateModels(lib.pages.models, prices).cost
137
+ if (lib.tokens && lib.tokens.models) totalCost += aggregateModels(lib.tokens.models, prices).cost
138
+ }
139
+
140
+ var costStr = symbol + totalCost.toFixed(4) + " this month (" + currency + ")"
141
+ var tokenDetail = totalInput + " in / " + totalOutput + " out tokens"
142
+
143
+ var lines = [
144
+ ctx.line.text({ label: "Monthly spend", value: costStr }),
145
+ ctx.line.text({ label: "Tokens", value: tokenDetail }),
146
+ ]
147
+
148
+ return { lines: lines }
149
+ }
150
+
151
+ globalThis.__openusage_plugin = { id: "mistral", probe: probe }
152
+ })()
@@ -0,0 +1,18 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "mistral",
4
+ "name": "Mistral",
5
+ "version": "0.0.1",
6
+ "entry": "plugin.js",
7
+ "brandColor": "#FF7000",
8
+ "links": [
9
+ { "label": "Usage", "url": "https://admin.mistral.ai/organization/usage" }
10
+ ],
11
+ "envVars": ["MISTRAL_COOKIE"],
12
+ "supportedModes": ["web"],
13
+ "autoMode": "web",
14
+ "webUrl": "https://console.mistral.ai",
15
+ "lines": [
16
+ { "type": "text", "label": "Monthly spend", "scope": "overview" }
17
+ ]
18
+ }
@@ -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>MoonshotAI</title><path d="M1.052 16.916l9.539 2.552a21.007 21.007 0 00.06 2.033l5.956 1.593a11.997 11.997 0 01-5.586.865l-.18-.016-.044-.004-.084-.009-.094-.01a11.605 11.605 0 01-.157-.02l-.107-.014-.11-.016a11.962 11.962 0 01-.32-.051l-.042-.008-.075-.013-.107-.02-.07-.015-.093-.019-.075-.016-.095-.02-.097-.023-.094-.022-.068-.017-.088-.022-.09-.024-.095-.025-.082-.023-.109-.03-.062-.02-.084-.025-.093-.028-.105-.034-.058-.019-.08-.026-.09-.031-.066-.024a6.293 6.293 0 01-.044-.015l-.068-.025-.101-.037-.057-.022-.08-.03-.087-.035-.088-.035-.079-.032-.095-.04-.063-.028-.063-.027a5.655 5.655 0 01-.041-.018l-.066-.03-.103-.047-.052-.024-.096-.046-.062-.03-.084-.04-.086-.044-.093-.047-.052-.027-.103-.055-.057-.03-.058-.032a6.49 6.49 0 01-.046-.026l-.094-.053-.06-.034-.051-.03-.072-.041-.082-.05-.093-.056-.052-.032-.084-.053-.061-.039-.079-.05-.07-.047-.053-.035a7.785 7.785 0 01-.054-.036l-.044-.03-.044-.03a6.066 6.066 0 01-.04-.028l-.057-.04-.076-.054-.069-.05-.074-.054-.056-.042-.076-.057-.076-.059-.086-.067-.045-.035-.064-.052-.074-.06-.089-.073-.046-.039-.046-.039a7.516 7.516 0 01-.043-.037l-.045-.04-.061-.053-.07-.062-.068-.06-.062-.058-.067-.062-.053-.05-.088-.084a13.28 13.28 0 01-.099-.097l-.029-.028-.041-.042-.069-.07-.05-.051-.05-.053a6.457 6.457 0 01-.168-.179l-.08-.088-.062-.07-.071-.08-.042-.049-.053-.062-.058-.068-.046-.056a7.175 7.175 0 01-.027-.033l-.045-.055-.066-.082-.041-.052-.05-.064-.02-.025a11.99 11.99 0 01-1.44-2.402zm-1.02-5.794l11.353 3.037a20.468 20.468 0 00-.469 2.011l10.817 2.894a12.076 12.076 0 01-1.845 2.005L.657 15.923l-.016-.046-.035-.104a11.965 11.965 0 01-.05-.153l-.007-.023a11.896 11.896 0 01-.207-.741l-.03-.126-.018-.08-.021-.097-.018-.081-.018-.09-.017-.084-.018-.094c-.026-.141-.05-.283-.071-.426l-.017-.118-.011-.083-.013-.102a12.01 12.01 0 01-.019-.161l-.005-.047a12.12 12.12 0 01-.034-2.145zm1.593-5.15l11.948 3.196c-.368.605-.705 1.231-1.01 1.875l11.295 3.022c-.142.82-.368 1.612-.668 2.365l-11.55-3.09L.124 10.26l.015-.1.008-.049.01-.067.015-.087.018-.098c.026-.148.056-.295.088-.442l.028-.124.02-.085.024-.097c.022-.09.045-.18.07-.268l.028-.102.023-.083.03-.1.025-.082.03-.096.026-.082.031-.095a11.896 11.896 0 011.01-2.232zm4.442-4.4L17.352 4.59a20.77 20.77 0 00-1.688 1.721l7.823 2.093c.267.852.442 1.744.513 2.665L2.106 5.213l.045-.065.027-.04.04-.055.046-.065.055-.076.054-.072.064-.086.05-.065.057-.073.055-.07.06-.074.055-.069.065-.077.054-.066.066-.077.053-.06.072-.082.053-.06.067-.074.054-.058.073-.078.058-.06.063-.067.168-.17.1-.098.059-.056.076-.071a12.084 12.084 0 012.272-1.677zM12.017 0h.097l.082.001.069.001.054.002.068.002.046.001.076.003.047.002.06.003.054.002.087.005.105.007.144.011.088.007.044.004.077.008.082.008.047.005.102.012.05.006.108.014.081.01.042.006.065.01.207.032.07.012.065.011.14.026.092.018.11.022.046.01.075.016.041.01L14.7.3l.042.01.065.015.049.012.071.017.096.024.112.03.113.03.113.032.05.015.07.02.078.024.073.023.05.016.05.016.076.025.099.033.102.036.048.017.064.023.093.034.11.041.116.045.1.04.047.02.06.024.041.018.063.026.04.018.057.025.11.048.1.046.074.035.075.036.06.028.092.046.091.045.102.052.053.028.049.026.046.024.06.033.041.022.052.029.088.05.106.06.087.051.057.034.053.032.096.059.088.055.098.062.036.024.064.041.084.056.04.027.062.042.062.043.023.017c.054.037.108.075.161.114l.083.06.065.048.056.043.086.065.082.064.04.03.05.041.086.069.079.065.085.071c.712.6 1.353 1.283 1.909 2.031L7.222.994l.062-.027.065-.028.081-.034.086-.035c.113-.045.227-.09.341-.131l.096-.035.093-.033.084-.03.096-.031c.087-.03.176-.058.264-.085l.091-.027.086-.025.102-.03.085-.023.1-.026L9.04.37l.09-.023.091-.022.095-.022.09-.02.098-.021.091-.02.095-.018.092-.018.1-.018.091-.016.098-.017.092-.014.097-.015.092-.013.102-.013.091-.012.105-.012.09-.01.105-.01c.093-.01.186-.018.28-.024l.106-.008.09-.005.11-.006.093-.004.1-.004.097-.002.099-.002.197-.002z"></path></svg>