@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.1 → 2.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/LICENSE +0 -0
  2. package/native-manifest.json +1088 -134
  3. package/package.json +160 -1
  4. package/plugins/UPSTREAM-LICENSES.md +113 -0
  5. package/plugins/_provider-icons/LICENSE +21 -0
  6. package/plugins/_provider-icons/NOTICE +12 -0
  7. package/plugins/_provider-icons/licenses/CodexBar-MIT.txt +21 -0
  8. package/plugins/_provider-icons/licenses/LobeHub-MIT.txt +21 -0
  9. package/plugins/_provider-icons/licenses/UsageStat-Bar-MIT.txt +21 -0
  10. package/plugins/_provider-icons/manifest.json +1219 -0
  11. package/plugins/_provider-icons/provenance.json +269 -0
  12. package/plugins/_provider-icons/source.json +4 -0
  13. package/plugins/abacus-ai/icon-color.svg +18 -0
  14. package/plugins/abacus-ai/icon.svg +0 -0
  15. package/plugins/abacus-ai/plugin.js +0 -0
  16. package/plugins/abacus-ai/plugin.json +0 -0
  17. package/plugins/aiand/icon.svg +6 -0
  18. package/plugins/aiand/plugin.js +108 -0
  19. package/plugins/aiand/plugin.json +22 -0
  20. package/plugins/aixy/LICENSE +21 -0
  21. package/plugins/aixy/plugin.js +261 -0
  22. package/plugins/aixy/plugin.json +24 -0
  23. package/plugins/alibaba/icon-color.svg +0 -0
  24. package/plugins/alibaba/icon.svg +0 -0
  25. package/plugins/alibaba/plugin.js +0 -0
  26. package/plugins/alibaba/plugin.json +0 -0
  27. package/plugins/alibaba-token-plan/icon-color.svg +0 -0
  28. package/plugins/alibaba-token-plan/icon.svg +0 -0
  29. package/plugins/alibaba-token-plan/plugin.js +0 -0
  30. package/plugins/alibaba-token-plan/plugin.json +0 -0
  31. package/plugins/amp/icon-color.svg +0 -0
  32. package/plugins/amp/icon-conflict.svg +0 -0
  33. package/plugins/amp/icon.svg +0 -0
  34. package/plugins/amp/plugin.js +0 -0
  35. package/plugins/amp/plugin.json +0 -0
  36. package/plugins/antigravity/icon-color.svg +0 -0
  37. package/plugins/antigravity/icon-conflict.svg +0 -0
  38. package/plugins/antigravity/icon.svg +0 -0
  39. package/plugins/antigravity/plugin.js +136 -46
  40. package/plugins/antigravity/plugin.json +0 -0
  41. package/plugins/antigravity-cli/icon-color.svg +1 -0
  42. package/plugins/antigravity-cli/icon.svg +1 -3
  43. package/plugins/antigravity-cli/plugin.js +11 -15
  44. package/plugins/antigravity-cli/plugin.json +0 -0
  45. package/plugins/antigravity-ide/icon-color.svg +1 -0
  46. package/plugins/antigravity-ide/icon.svg +1 -3
  47. package/plugins/antigravity-ide/plugin.js +0 -0
  48. package/plugins/antigravity-ide/plugin.json +0 -0
  49. package/plugins/atlascloud/LICENSE +21 -0
  50. package/plugins/atlascloud/icon-color.svg +1 -0
  51. package/plugins/atlascloud/icon.svg +1 -0
  52. package/plugins/atlascloud/plugin.js +49 -0
  53. package/plugins/atlascloud/plugin.json +23 -0
  54. package/plugins/augment/icon-color.svg +14 -0
  55. package/plugins/augment/icon.svg +0 -0
  56. package/plugins/augment/plugin.js +0 -0
  57. package/plugins/augment/plugin.json +0 -0
  58. package/plugins/aws-bedrock/icon-color.svg +0 -0
  59. package/plugins/aws-bedrock/icon.svg +0 -0
  60. package/plugins/aws-bedrock/plugin.js +0 -0
  61. package/plugins/aws-bedrock/plugin.json +0 -0
  62. package/plugins/azure-openai/icon-color.svg +0 -0
  63. package/plugins/azure-openai/icon.svg +0 -0
  64. package/plugins/azure-openai/plugin.js +0 -0
  65. package/plugins/azure-openai/plugin.json +0 -0
  66. package/plugins/bifrost/LICENSE +21 -0
  67. package/plugins/bifrost/plugin.js +361 -0
  68. package/plugins/bifrost/plugin.json +23 -0
  69. package/plugins/chutes/icon.svg +0 -0
  70. package/plugins/chutes/plugin.js +0 -0
  71. package/plugins/chutes/plugin.json +0 -0
  72. package/plugins/claude/icon-color.svg +0 -0
  73. package/plugins/claude/icon.svg +0 -0
  74. package/plugins/claude/plugin.js +358 -161
  75. package/plugins/claude/plugin.json +1 -1
  76. package/plugins/clawrouter/icon.svg +7 -0
  77. package/plugins/clawrouter/plugin.js +45 -0
  78. package/plugins/clawrouter/plugin.json +22 -0
  79. package/plugins/clinepass/icon.svg +4 -0
  80. package/plugins/clinepass/plugin.js +87 -0
  81. package/plugins/clinepass/plugin.json +23 -0
  82. package/plugins/codebuddy/plugin.js +331 -0
  83. package/plugins/codebuddy/plugin.json +30 -0
  84. package/plugins/codebuff/icon-color.svg +92 -0
  85. package/plugins/codebuff/icon.svg +0 -0
  86. package/plugins/codebuff/plugin.js +0 -0
  87. package/plugins/codebuff/plugin.json +0 -0
  88. package/plugins/coderabbit/plugin.js +25 -0
  89. package/plugins/coderabbit/plugin.json +12 -0
  90. package/plugins/codex/icon-color.svg +0 -0
  91. package/plugins/codex/icon.svg +0 -0
  92. package/plugins/codex/plugin.js +8 -2
  93. package/plugins/codex/plugin.json +0 -0
  94. package/plugins/command-code/icon-color.svg +3 -0
  95. package/plugins/command-code/icon.svg +0 -0
  96. package/plugins/command-code/plugin.js +14 -3
  97. package/plugins/command-code/plugin.json +0 -0
  98. package/plugins/copilot/icon-color.svg +0 -0
  99. package/plugins/copilot/icon.svg +1 -1
  100. package/plugins/copilot/plugin.js +61 -9
  101. package/plugins/copilot/plugin.json +4 -1
  102. package/plugins/crof/icon-color.svg +3 -0
  103. package/plugins/crof/icon.svg +0 -0
  104. package/plugins/crof/plugin.js +0 -0
  105. package/plugins/crof/plugin.json +0 -0
  106. package/plugins/crofai/icon-color.svg +3 -0
  107. package/plugins/crofai/icon.svg +2 -15
  108. package/plugins/crofai/plugin.js +0 -0
  109. package/plugins/crofai/plugin.json +0 -0
  110. package/plugins/cursor/icon-color.svg +1 -0
  111. package/plugins/cursor/icon-conflict.svg +0 -0
  112. package/plugins/cursor/icon.svg +0 -0
  113. package/plugins/cursor/plugin.js +46 -9
  114. package/plugins/cursor/plugin.json +3 -2
  115. package/plugins/cursor-nightly/icon.png +0 -0
  116. package/plugins/cursor-nightly/plugin.js +45 -8
  117. package/plugins/cursor-nightly/plugin.json +3 -2
  118. package/plugins/deepgram/icon.svg +0 -0
  119. package/plugins/deepgram/plugin.js +0 -0
  120. package/plugins/deepgram/plugin.json +0 -0
  121. package/plugins/deepinfra/icon-color.svg +1 -0
  122. package/plugins/deepinfra/icon.svg +1 -0
  123. package/plugins/deepinfra/plugin.js +97 -0
  124. package/plugins/deepinfra/plugin.json +28 -0
  125. package/plugins/deepseek/icon-color.svg +0 -0
  126. package/plugins/deepseek/icon.svg +0 -0
  127. package/plugins/deepseek/plugin.js +150 -59
  128. package/plugins/deepseek/plugin.json +3 -1
  129. package/plugins/devin/icon.svg +0 -0
  130. package/plugins/devin/plugin.js +114 -0
  131. package/plugins/devin/plugin.json +2 -1
  132. package/plugins/devpass/LICENSE +21 -0
  133. package/plugins/devpass/plugin.js +115 -0
  134. package/plugins/devpass/plugin.json +23 -0
  135. package/plugins/doubao/icon-color.svg +0 -0
  136. package/plugins/doubao/icon.svg +0 -0
  137. package/plugins/doubao/plugin.js +0 -0
  138. package/plugins/doubao/plugin.json +0 -0
  139. package/plugins/droid/icon.svg +0 -0
  140. package/plugins/droid/plugin.json +2 -1
  141. package/plugins/elevenlabs/icon-color.svg +4 -0
  142. package/plugins/elevenlabs/icon.svg +0 -0
  143. package/plugins/elevenlabs/plugin.js +17 -2
  144. package/plugins/elevenlabs/plugin.json +0 -0
  145. package/plugins/factory/icon-color.svg +1 -0
  146. package/plugins/factory/icon-conflict.svg +0 -0
  147. package/plugins/factory/icon.svg +0 -0
  148. package/plugins/factory/plugin.js +38 -14
  149. package/plugins/factory/plugin.json +2 -1
  150. package/plugins/fireworks-ai/icon-color.svg +1 -0
  151. package/plugins/fireworks-ai/icon.svg +1 -6
  152. package/plugins/fireworks-ai/plugin.js +0 -0
  153. package/plugins/fireworks-ai/plugin.json +0 -0
  154. package/plugins/gemini/icon-color.svg +0 -0
  155. package/plugins/gemini/icon.svg +0 -0
  156. package/plugins/gemini/plugin.js +0 -0
  157. package/plugins/gemini/plugin.json +0 -0
  158. package/plugins/gemini-apps/LICENSE +22 -0
  159. package/plugins/gemini-apps/icon-color.svg +1 -0
  160. package/plugins/gemini-apps/icon.svg +1 -0
  161. package/plugins/gemini-apps/plugin.js +470 -0
  162. package/plugins/gemini-apps/plugin.json +19 -0
  163. package/plugins/gitkraken/LICENSE +21 -0
  164. package/plugins/gitkraken/plugin.js +153 -0
  165. package/plugins/gitkraken/plugin.json +24 -0
  166. package/plugins/grok/icon-color.svg +1 -0
  167. package/plugins/grok/icon.svg +0 -0
  168. package/plugins/grok/plugin.js +30 -5
  169. package/plugins/grok/plugin.json +3 -1
  170. package/plugins/groqcloud/icon-color.svg +1 -0
  171. package/plugins/groqcloud/icon.svg +0 -0
  172. package/plugins/groqcloud/plugin.js +0 -0
  173. package/plugins/groqcloud/plugin.json +0 -0
  174. package/plugins/helmcode/LICENSE +21 -0
  175. package/plugins/helmcode/plugin.js +188 -0
  176. package/plugins/helmcode/plugin.json +24 -0
  177. package/plugins/huggingface/LICENSE +21 -0
  178. package/plugins/huggingface/icon-color.svg +1 -0
  179. package/plugins/huggingface/icon.svg +1 -0
  180. package/plugins/huggingface/plugin.js +293 -0
  181. package/plugins/huggingface/plugin.json +25 -0
  182. package/plugins/hyper/LICENSE +21 -0
  183. package/plugins/hyper/plugin.js +131 -0
  184. package/plugins/hyper/plugin.json +26 -0
  185. package/plugins/ibmbob/icon.svg +23 -0
  186. package/plugins/ibmbob/plugin.js +61 -0
  187. package/plugins/ibmbob/plugin.json +22 -0
  188. package/plugins/jetbrains-ai-assistant/icon-color.svg +0 -0
  189. package/plugins/jetbrains-ai-assistant/icon-conflict.svg +0 -0
  190. package/plugins/jetbrains-ai-assistant/icon.svg +0 -0
  191. package/plugins/jetbrains-ai-assistant/plugin.js +0 -0
  192. package/plugins/jetbrains-ai-assistant/plugin.json +0 -0
  193. package/plugins/kilo/icon-color.svg +1 -0
  194. package/plugins/kilo/icon.svg +0 -0
  195. package/plugins/kilo/plugin.js +0 -0
  196. package/plugins/kilo/plugin.json +0 -0
  197. package/plugins/kimi/icon-color.svg +0 -0
  198. package/plugins/kimi/icon-conflict.svg +0 -0
  199. package/plugins/kimi/icon.svg +0 -0
  200. package/plugins/kimi/plugin.js +122 -141
  201. package/plugins/kimi/plugin.json +5 -2
  202. package/plugins/kimi-k2/icon-color.svg +0 -0
  203. package/plugins/kimi-k2/icon.svg +0 -0
  204. package/plugins/kimi-k2/plugin.js +0 -0
  205. package/plugins/kimi-k2/plugin.json +0 -0
  206. package/plugins/kiro/icon-color.svg +0 -0
  207. package/plugins/kiro/icon-conflict.svg +0 -0
  208. package/plugins/kiro/icon.svg +0 -0
  209. package/plugins/kiro/plugin.js +49 -10
  210. package/plugins/kiro/plugin.json +3 -1
  211. package/plugins/litellm/icon.svg +0 -0
  212. package/plugins/litellm/plugin.js +54 -3
  213. package/plugins/litellm/plugin.json +0 -0
  214. package/plugins/llm-proxy/icon.svg +0 -0
  215. package/plugins/llm-proxy/plugin.js +0 -0
  216. package/plugins/llm-proxy/plugin.json +0 -0
  217. package/plugins/llmman/LICENSE +21 -0
  218. package/plugins/llmman/plugin.js +133 -0
  219. package/plugins/llmman/plugin.json +23 -0
  220. package/plugins/longcat/icon-color.svg +1 -0
  221. package/plugins/longcat/icon.svg +1 -0
  222. package/plugins/longcat/plugin.js +182 -0
  223. package/plugins/longcat/plugin.json +23 -0
  224. package/plugins/manus/icon-color.svg +6 -0
  225. package/plugins/manus/icon.svg +0 -0
  226. package/plugins/manus/plugin.js +0 -0
  227. package/plugins/manus/plugin.json +0 -0
  228. package/plugins/mimo/icon-color.svg +4 -0
  229. package/plugins/mimo/icon.svg +4 -1
  230. package/plugins/mimo/plugin.js +0 -0
  231. package/plugins/mimo/plugin.json +0 -0
  232. package/plugins/minimax/icon-color.svg +0 -0
  233. package/plugins/minimax/icon-conflict.svg +0 -0
  234. package/plugins/minimax/icon.svg +0 -0
  235. package/plugins/minimax/plugin.js +25 -31
  236. package/plugins/minimax/plugin.json +0 -0
  237. package/plugins/mistral/icon-color.svg +0 -0
  238. package/plugins/mistral/icon.svg +0 -0
  239. package/plugins/mistral/plugin.js +110 -5
  240. package/plugins/mistral/plugin.json +0 -0
  241. package/plugins/moonshot/icon-color.svg +1 -0
  242. package/plugins/moonshot/icon.svg +0 -0
  243. package/plugins/moonshot/plugin.js +8 -6
  244. package/plugins/moonshot/plugin.json +0 -0
  245. package/plugins/muse/LICENSE +21 -0
  246. package/plugins/muse/plugin.js +319 -0
  247. package/plugins/muse/plugin.json +26 -0
  248. package/plugins/nanogpt/icon-color.svg +0 -0
  249. package/plugins/nanogpt/icon.svg +0 -0
  250. package/plugins/nanogpt/plugin.js +0 -0
  251. package/plugins/nanogpt/plugin.json +0 -0
  252. package/plugins/neuralwatt/icon.svg +0 -0
  253. package/plugins/neuralwatt/plugin.js +0 -0
  254. package/plugins/neuralwatt/plugin.json +0 -0
  255. package/plugins/notion/icon.svg +1 -0
  256. package/plugins/notion/plugin.js +282 -0
  257. package/plugins/notion/plugin.json +29 -0
  258. package/plugins/nous/LICENSE +21 -0
  259. package/plugins/nous/icon-color.svg +1 -0
  260. package/plugins/nous/icon.svg +1 -0
  261. package/plugins/nous/plugin.js +124 -0
  262. package/plugins/nous/plugin.json +24 -0
  263. package/plugins/ollama/icon-color.svg +1 -0
  264. package/plugins/ollama/icon.svg +0 -0
  265. package/plugins/ollama/plugin.js +257 -56
  266. package/plugins/ollama/plugin.json +6 -3
  267. package/plugins/openai-api/icon-color.svg +1 -0
  268. package/plugins/openai-api/icon.svg +0 -0
  269. package/plugins/openai-api/plugin.js +0 -0
  270. package/plugins/openai-api/plugin.json +0 -0
  271. package/plugins/opencode/icon-color.svg +1 -0
  272. package/plugins/opencode/icon.svg +0 -0
  273. package/plugins/opencode/plugin.js +0 -0
  274. package/plugins/opencode/plugin.json +0 -0
  275. package/plugins/opencode-go/icon-color.svg +1 -0
  276. package/plugins/opencode-go/icon-conflict.svg +0 -0
  277. package/plugins/opencode-go/icon.svg +0 -0
  278. package/plugins/opencode-go/plugin.js +103 -17
  279. package/plugins/opencode-go/plugin.json +5 -2
  280. package/plugins/openrouter/icon-color.svg +1 -0
  281. package/plugins/openrouter/icon.svg +0 -0
  282. package/plugins/openrouter/plugin.js +371 -114
  283. package/plugins/openrouter/plugin.json +6 -0
  284. package/plugins/perplexity/icon-color.svg +0 -0
  285. package/plugins/perplexity/icon-conflict.svg +0 -0
  286. package/plugins/perplexity/icon.svg +0 -0
  287. package/plugins/perplexity/plugin.js +0 -0
  288. package/plugins/perplexity/plugin.json +0 -0
  289. package/plugins/pi/plugin.js +113 -0
  290. package/plugins/pi/plugin.json +11 -0
  291. package/plugins/poe/icon.svg +0 -0
  292. package/plugins/poe/plugin.js +24 -18
  293. package/plugins/poe/plugin.json +2 -0
  294. package/plugins/qoder/icon.svg +3 -0
  295. package/plugins/qoder/plugin.js +227 -0
  296. package/plugins/qoder/plugin.json +23 -0
  297. package/plugins/qwencloud/icon-color.svg +1 -0
  298. package/plugins/qwencloud/icon.svg +1 -0
  299. package/plugins/qwencloud/plugin.js +677 -0
  300. package/plugins/qwencloud/plugin.json +29 -0
  301. package/plugins/raycast/LICENSE +21 -0
  302. package/plugins/raycast/plugin.js +174 -0
  303. package/plugins/raycast/plugin.json +24 -0
  304. package/plugins/replicate/LICENSE +21 -0
  305. package/plugins/replicate/icon-color.svg +1 -0
  306. package/plugins/replicate/icon.svg +1 -0
  307. package/plugins/replicate/plugin.js +165 -0
  308. package/plugins/replicate/plugin.json +24 -0
  309. package/plugins/sakana/icon.svg +5 -0
  310. package/plugins/sakana/plugin.js +156 -0
  311. package/plugins/sakana/plugin.json +23 -0
  312. package/plugins/stepfun/icon-color.svg +0 -0
  313. package/plugins/stepfun/icon.svg +0 -0
  314. package/plugins/stepfun/plugin.js +0 -0
  315. package/plugins/stepfun/plugin.json +0 -0
  316. package/plugins/sub2api/icon.svg +4 -0
  317. package/plugins/sub2api/plugin.js +191 -0
  318. package/plugins/sub2api/plugin.json +16 -0
  319. package/plugins/synthetic/icon-color.svg +14 -0
  320. package/plugins/synthetic/icon-conflict.svg +0 -0
  321. package/plugins/synthetic/icon.svg +0 -0
  322. package/plugins/synthetic/plugin.js +0 -0
  323. package/plugins/synthetic/plugin.json +0 -0
  324. package/plugins/t3chat/icon-color.svg +0 -0
  325. package/plugins/t3chat/icon.svg +0 -0
  326. package/plugins/t3chat/plugin.js +0 -0
  327. package/plugins/t3chat/plugin.json +0 -0
  328. package/plugins/typesafe/LICENSE +21 -0
  329. package/plugins/typesafe/plugin.js +225 -0
  330. package/plugins/typesafe/plugin.json +24 -0
  331. package/plugins/v0/LICENSE +21 -0
  332. package/plugins/v0/icon-color.svg +1 -0
  333. package/plugins/v0/icon.svg +1 -0
  334. package/plugins/v0/plugin.js +214 -0
  335. package/plugins/v0/plugin.json +24 -0
  336. package/plugins/venice/icon-color.svg +0 -0
  337. package/plugins/venice/icon.svg +0 -0
  338. package/plugins/venice/plugin.js +50 -1
  339. package/plugins/venice/plugin.json +2 -1
  340. package/plugins/vercel/LICENSE +21 -0
  341. package/plugins/vercel/icon-color.svg +1 -0
  342. package/plugins/vercel/icon.svg +1 -0
  343. package/plugins/vercel/plugin.js +55 -0
  344. package/plugins/vercel/plugin.json +23 -0
  345. package/plugins/vertex-ai/icon-color.svg +0 -0
  346. package/plugins/vertex-ai/icon.svg +0 -0
  347. package/plugins/vertex-ai/plugin.js +0 -0
  348. package/plugins/vertex-ai/plugin.json +0 -0
  349. package/plugins/warp/icon-color.svg +0 -0
  350. package/plugins/warp/icon.svg +0 -0
  351. package/plugins/warp/plugin.js +0 -0
  352. package/plugins/warp/plugin.json +0 -0
  353. package/plugins/wayfinder/icon.svg +5 -0
  354. package/plugins/wayfinder/plugin.js +47 -0
  355. package/plugins/wayfinder/plugin.json +16 -0
  356. package/plugins/windsurf/icon-color.svg +1 -0
  357. package/plugins/windsurf/icon-conflict.svg +0 -0
  358. package/plugins/windsurf/icon.svg +0 -0
  359. package/plugins/windsurf/plugin.js +0 -0
  360. package/plugins/windsurf/plugin.json +0 -0
  361. package/plugins/xai/icon-color.svg +1 -0
  362. package/plugins/xai/icon.svg +1 -0
  363. package/plugins/xai/plugin.js +162 -0
  364. package/plugins/xai/plugin.json +28 -0
  365. package/plugins/xkiro/LICENSE +21 -0
  366. package/plugins/xkiro/plugin.js +99 -0
  367. package/plugins/xkiro/plugin.json +23 -0
  368. package/plugins/zai/icon-color.svg +1 -0
  369. package/plugins/zai/icon-conflict.svg +0 -0
  370. package/plugins/zai/icon.svg +0 -0
  371. package/plugins/zai/plugin.js +111 -161
  372. package/plugins/zai/plugin.json +4 -1
  373. package/plugins/zed/icon.svg +0 -0
  374. package/plugins/zed/plugin.js +0 -0
  375. package/plugins/zed/plugin.json +0 -0
  376. package/plugins/zenmux/icon-color.svg +1 -0
  377. package/plugins/zenmux/icon.svg +1 -0
  378. package/plugins/zenmux/plugin.js +102 -0
  379. package/plugins/zenmux/plugin.json +23 -0
  380. package/plugins/zoommate/icon.svg +1 -0
  381. package/plugins/zoommate/plugin.js +244 -0
  382. package/plugins/zoommate/plugin.json +29 -0
  383. package/usagestat +0 -0
  384. package/usagestatd +0 -0
@@ -1,11 +1,11 @@
1
1
  (function () {
2
- const CRED_PATH = "~/.kimi/credentials/kimi-code.json"
3
- const USAGE_URL = "https://api.kimi.com/coding/v1/usages"
4
- const REFRESH_URL = "https://auth.kimi.com/api/oauth/token"
5
- const CLIENT_ID = "17e5f671-d194-4dfb-9706-5516cb48c098"
6
- const REFRESH_BUFFER_SEC = 5 * 60
2
+ const WEB_USAGE_URL = "https://www.kimi.com/apiv2/kimi.gateway.billing.v1.BillingService/GetUsages"
3
+ const MEMBERSHIP_URL = "https://www.kimi.com/apiv2/kimi.gateway.membership.v2.MembershipService/"
4
+ const WEEK_MS = 7 * 86400000
7
5
 
8
6
  function readNumber(value) {
7
+ if (typeof value !== "number" && typeof value !== "string") return null
8
+ if (typeof value === "string" && !value.trim()) return null
9
9
  const n = Number(value)
10
10
  return Number.isFinite(n) ? n : null
11
11
  }
@@ -29,119 +29,109 @@
29
29
  : null
30
30
  if (!level) return null
31
31
 
32
+ if (level === "LEVEL_UNSPECIFIED") return null
33
+ const names = { LEVEL_FREE: "Adagio", LEVEL_TRIAL: "Andante", LEVEL_BASIC: "Moderato", LEVEL_INTERMEDIATE: "Allegretto", LEVEL_ADVANCED: "Allegro" }
34
+ if (data.version != null && data.version !== "GOODS_VERSION_V1") return level
35
+ if (names[level]) return names[level]
32
36
  const cleaned = level.replace(/^LEVEL_/, "").replace(/_/g, " ")
33
37
  const label = titleCaseWords(cleaned)
34
38
  return label || null
35
39
  }
36
40
 
37
- function loadCredentials(ctx) {
38
- if (!ctx.host.fs.exists(CRED_PATH)) {
39
- ctx.host.log.warn("credentials file not found: " + CRED_PATH)
40
- return null
41
- }
42
-
43
- try {
44
- const text = ctx.host.fs.readText(CRED_PATH)
45
- const parsed = ctx.util.tryParseJson(text)
46
- if (!parsed || typeof parsed !== "object") {
47
- ctx.host.log.warn("credentials file is not valid json")
48
- return null
49
- }
50
- if (!parsed.access_token && !parsed.refresh_token) {
51
- ctx.host.log.warn("credentials missing access_token and refresh_token")
52
- return null
53
- }
54
- return parsed
55
- } catch (e) {
56
- ctx.host.log.warn("credentials read failed: " + String(e))
57
- return null
58
- }
41
+ function clean(value) {
42
+ return typeof value === "string" && value.trim() ? value.trim() : null
59
43
  }
60
44
 
61
- function saveCredentials(ctx, creds) {
62
- try {
63
- ctx.host.fs.writeText(CRED_PATH, JSON.stringify(creds))
64
- } catch (e) {
65
- ctx.host.log.warn("failed to persist credentials: " + String(e))
66
- }
45
+ function env(ctx, name) {
46
+ try { return clean(ctx.host.env.get(name)) } catch (_) { return null }
67
47
  }
68
48
 
69
- function needsRefresh(creds, nowSec) {
70
- if (!creds.access_token) return true
71
- const expiresAt = readNumber(creds.expires_at)
72
- if (expiresAt === null) return true
73
- return nowSec + REFRESH_BUFFER_SEC >= expiresAt
49
+ function readText(ctx, path) {
50
+ try { return clean(ctx.host.fs.readText(path)) } catch (_) { return null }
74
51
  }
75
52
 
76
- function refreshToken(ctx, creds) {
77
- if (!creds.refresh_token) {
78
- ctx.host.log.warn("refresh skipped: no refresh token")
79
- return null
80
- }
81
-
82
- ctx.host.log.info("attempting token refresh")
83
- let resp
84
- try {
85
- resp = ctx.util.request({
86
- method: "POST",
87
- url: REFRESH_URL,
88
- headers: {
89
- "Content-Type": "application/x-www-form-urlencoded",
90
- Accept: "application/json",
91
- },
92
- bodyText:
93
- "client_id=" +
94
- encodeURIComponent(CLIENT_ID) +
95
- "&grant_type=refresh_token" +
96
- "&refresh_token=" +
97
- encodeURIComponent(creds.refresh_token),
98
- timeoutMs: 15000,
99
- })
100
- } catch (e) {
101
- ctx.host.log.error("refresh exception: " + String(e))
102
- return null
103
- }
104
-
105
- if (ctx.util.isAuthStatus(resp.status)) {
106
- throw "Session expired. Run `kimi login` to authenticate."
53
+ function cliSession(ctx) {
54
+ const override = env(ctx, "KIMI_CODE_HOME")
55
+ const homes = override ? [override] : ["~/.kimi-code", "~/.kimi"]
56
+ for (const home of homes) {
57
+ const raw = readText(ctx, home + "/credentials/kimi-code.json")
58
+ if (!raw) continue
59
+ const credential = ctx.util.tryParseJson(raw)
60
+ const expires = credential && readNumber(credential.expires_at)
61
+ if (!credential || !clean(credential.access_token) || expires === null || expires <= Date.parse(ctx.nowIso) / 1000 + 60) continue
62
+ let deviceId = readText(ctx, home + "/device_id")
63
+ if (!deviceId) {
64
+ // This is a client identity, not an authentication secret.
65
+ deviceId = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
66
+ const n = Math.floor(Math.random() * 16)
67
+ return (c === "x" ? n : (n & 3) | 8).toString(16)
68
+ })
69
+ try { ctx.host.fs.writeText(home + "/device_id", deviceId) } catch (_) {}
70
+ }
71
+ return { token: credential.access_token, headers: {
72
+ "X-Msh-Platform": "kimi_code_cli", "X-Msh-Version": ctx.app.version,
73
+ "X-Msh-Device-Model": ctx.app.platform,
74
+ "X-Msh-Device-Id": deviceId.replace(/[^\x20-\x7e]/g, ""),
75
+ } }
107
76
  }
77
+ return null
78
+ }
108
79
 
109
- if (resp.status < 200 || resp.status >= 300) {
110
- ctx.host.log.warn("refresh returned unexpected status: " + resp.status)
111
- return null
112
- }
80
+ function webToken(ctx) {
81
+ const raw = clean(ctx.provider.cookieHeader) || env(ctx, "KIMI_AUTH_TOKEN")
82
+ if (!raw) return null
83
+ const cookie = raw.replace(/^Cookie:\s*/i, "")
84
+ const match = /(?:^|;)\s*kimi-auth=([^;]+)/.exec(cookie)
85
+ if (match) return clean(match[1])
86
+ return cookie.includes("=") ? null : clean(cookie.replace(/^Bearer\s+/i, ""))
87
+ }
113
88
 
114
- const body = ctx.util.tryParseJson(resp.bodyText)
115
- if (!body || !body.access_token) {
116
- ctx.host.log.warn("refresh response missing access_token")
117
- return null
89
+ function webRequest(ctx, token, url, body, timeoutMs) {
90
+ const payload = ctx.jwt.decodePayload(token) || {}
91
+ const headers = {
92
+ Authorization: "Bearer " + token, Cookie: "kimi-auth=" + token,
93
+ "Content-Type": "application/json", Accept: "application/json",
94
+ Origin: "https://www.kimi.com", Referer: "https://www.kimi.com/code/console",
95
+ "connect-protocol-version": "1", "x-msh-platform": "web", "x-language": "en-US", "r-timezone": "UTC",
118
96
  }
119
-
120
- creds.access_token = body.access_token
121
- if (body.refresh_token) creds.refresh_token = body.refresh_token
122
- if (typeof body.expires_in === "number") {
123
- creds.expires_at = Date.now() / 1000 + body.expires_in
97
+ for (const pair of [["device_id", "x-msh-device-id"], ["ssid", "x-msh-session-id"], ["sub", "x-traffic-id"]]) {
98
+ if (clean(payload[pair[0]])) headers[pair[1]] = payload[pair[0]].replace(/[^\x20-\x7e]/g, "")
124
99
  }
125
- if (typeof body.scope === "string") creds.scope = body.scope
126
- if (typeof body.token_type === "string") creds.token_type = body.token_type
127
-
128
- saveCredentials(ctx, creds)
129
- return creds.access_token
130
- }
131
-
132
- function fetchUsage(ctx, accessToken) {
133
100
  return ctx.util.request({
134
- method: "GET",
135
- url: USAGE_URL,
136
- headers: {
137
- Authorization: "Bearer " + accessToken,
138
- Accept: "application/json",
139
- "User-Agent": "OpenUsage",
140
- },
141
- timeoutMs: 10000,
101
+ method: "POST", url, headers, bodyText: JSON.stringify(body), timeoutMs,
142
102
  })
143
103
  }
144
104
 
105
+ function enrichMembership(ctx, result, token) {
106
+ if (!token || ctx.provider.settings && ctx.provider.settings.cookieSource === "off") return result
107
+ try {
108
+ const response = webRequest(ctx, token, MEMBERSHIP_URL + "GetSubscriptionStats", {}, 2000)
109
+ const data = response.status === 200 && ctx.util.tryParseJson(response.bodyText)
110
+ if (!data) return result
111
+ const pool = data.subscriptionBalance
112
+ const ratio = pool && readNumber(pool.amountUsedRatio)
113
+ if (Number.isFinite(ratio) && pool && (!pool.feature || pool.feature === "FEATURE_OMNI") && (!pool.type || pool.type === "SUBSCRIPTION")) {
114
+ const reset = ctx.util.toIso(pool.expireTime)
115
+ const monthly = ctx.line.progress({ label: "Monthly", used: Math.max(0, Math.min(100, ratio * 100)), limit: 100,
116
+ format: { kind: "percent" }, resetsAt: reset, periodDurationMs: ctx.util.calendarMonthDuration(reset) })
117
+ // Automatic summaries use the first quota. Reset Code windows cannot
118
+ // make an exhausted membership pool available again.
119
+ if (ratio >= 1) result.lines.unshift(monthly)
120
+ else result.lines.push(monthly)
121
+ }
122
+ const weekly = data.ratelimitCode7d
123
+ const weeklyRatio = weekly && readNumber(weekly.ratio)
124
+ if (weekly && weekly.enabled !== false && Number.isFinite(weeklyRatio)) {
125
+ const reset = ctx.util.toIso(weekly.resetTime)
126
+ const used = Math.max(0, Math.min(100, weeklyRatio * 100))
127
+ if (!result.lines.some((line) => line.label === "Weekly" && Math.abs(line.used - used) < 0.01 && (line.resetsAt || null) === reset)) {
128
+ result.lines.push(ctx.line.progress({ label: "Code 7-day", used, limit: 100, format: { kind: "percent" }, resetsAt: reset, periodDurationMs: WEEK_MS }))
129
+ }
130
+ }
131
+ } catch (_) { ctx.host.log.warn("Kimi membership enrichment unavailable") }
132
+ return result
133
+ }
134
+
145
135
  function parseWindowPeriodMs(window) {
146
136
  if (!window || typeof window !== "object") return null
147
137
  const duration = readNumber(window.duration)
@@ -164,11 +154,11 @@
164
154
  let used = readNumber(row.used)
165
155
  if (used === null) {
166
156
  const remaining = readNumber(row.remaining)
167
- if (remaining !== null) {
157
+ if (remaining !== null && remaining >= 0 && remaining <= limit) {
168
158
  used = limit - remaining
169
159
  }
170
160
  }
171
- if (used === null) return null
161
+ if (used === null || used < 0) return null
172
162
 
173
163
  return {
174
164
  used,
@@ -240,43 +230,29 @@
240
230
  }
241
231
 
242
232
  function probe(ctx) {
243
- const creds = loadCredentials(ctx)
244
- if (!creds) {
245
- throw "Not logged in. Run `kimi login` to authenticate."
246
- }
247
-
248
- const nowSec = Date.now() / 1000
249
- let accessToken = creds.access_token || ""
250
-
251
- if (needsRefresh(creds, nowSec)) {
252
- const refreshed = refreshToken(ctx, creds)
253
- if (refreshed) {
254
- accessToken = refreshed
255
- } else if (!accessToken) {
256
- throw "Not logged in. Run `kimi login` to authenticate."
257
- }
258
- }
259
-
260
- let didRefresh = false
233
+ const mode = ctx.sourceMode || "auto"
234
+ if (mode === "local") throw "Kimi quotas require a live source. Select api, oauth, cli, or web."
235
+ const key = clean(ctx.provider.apiKey) || env(ctx, "KIMI_CODE_API_KEY")
236
+ const baseOverride = clean(ctx.provider.settings && ctx.provider.settings.baseUrl) || env(ctx, "KIMI_CODE_BASE_URL")
237
+ const hasOverride = baseOverride || env(ctx, "KIMI_CODE_OAUTH_HOST") || env(ctx, "KIMI_OAUTH_HOST")
238
+ const token = webToken(ctx)
239
+ let source
261
240
  let resp
262
- try {
263
- resp = ctx.util.retryOnceOnAuth({
264
- request: function (token) {
265
- return fetchUsage(ctx, token || accessToken)
266
- },
267
- refresh: function () {
268
- didRefresh = true
269
- const refreshed = refreshToken(ctx, creds)
270
- if (refreshed) accessToken = refreshed
271
- return refreshed
272
- },
273
- })
274
- } catch (e) {
275
- if (typeof e === "string") throw e
276
- if (didRefresh) {
277
- throw "Usage request failed after refresh. Try again."
278
- }
279
- throw "Usage request failed. Check your connection."
241
+ if (mode === "api" && !key) throw "Set KIMI_CODE_API_KEY to a Kimi Code key, not an Open Platform key."
242
+ const useKey = key && (mode === "auto" || mode === "api")
243
+ const session = mode !== "web" && !useKey && !hasOverride ? cliSession(ctx) : null
244
+ if (useKey || session) {
245
+ const base = ctx.host.http.validateBaseUrl(baseOverride || "https://api.kimi.com", false)
246
+ const url = base + (/\/coding\/v1$/.test(base) ? "/usages" : /\/coding$/.test(base) ? "/v1/usages" : "/coding/v1/usages")
247
+ resp = ctx.util.request({ method: "GET", url, headers: Object.assign({}, session && session.headers,
248
+ { Authorization: "Bearer " + (useKey ? key : session.token), Accept: "application/json", "User-Agent": "usagestat/" + ctx.app.version }), timeoutMs: 10000 })
249
+ source = useKey ? "api" : "oauth"
250
+ } else if (token && (mode === "web" || mode === "auto")) {
251
+ resp = webRequest(ctx, token, WEB_USAGE_URL, { scope: ["FEATURE_CODING"] }, 10000)
252
+ source = "web"
253
+ } else {
254
+ if (hasOverride) throw "Kimi endpoint overrides require an explicit API key; CLI credentials are never forwarded."
255
+ throw "No fresh Kimi Code credentials. Run `kimi login`, set KIMI_CODE_API_KEY, or configure a web session."
280
256
  }
281
257
 
282
258
  if (ctx.util.isAuthStatus(resp.status)) {
@@ -286,7 +262,11 @@
286
262
  throw "Usage request failed (HTTP " + String(resp.status) + "). Try again later."
287
263
  }
288
264
 
289
- const data = ctx.util.tryParseJson(resp.bodyText)
265
+ let data = ctx.util.tryParseJson(resp.bodyText)
266
+ if (source === "web") {
267
+ const coding = data && Array.isArray(data.usages) && data.usages.find((item) => item.scope === "FEATURE_CODING")
268
+ data = coding ? { usage: coding.detail, limits: coding.limits } : null
269
+ }
290
270
  if (!data || typeof data !== "object") {
291
271
  throw "Usage response invalid. Try again later."
292
272
  }
@@ -298,7 +278,7 @@
298
278
  let weeklyCandidate = null
299
279
  const usageQuota = parseQuota(data.usage, ctx)
300
280
  if (usageQuota) {
301
- weeklyCandidate = { quota: usageQuota, periodMs: null }
281
+ weeklyCandidate = { quota: usageQuota, periodMs: WEEK_MS }
302
282
  } else {
303
283
  const withoutSession = candidates.filter(function (candidate) {
304
284
  return candidate !== sessionCandidate
@@ -345,13 +325,14 @@
345
325
  }
346
326
 
347
327
  if (lines.length === 0) {
348
- lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" }))
328
+ throw "Kimi usage response did not contain measurable quota counters."
349
329
  }
350
330
 
351
- return {
331
+ return enrichMembership(ctx, {
332
+ source,
352
333
  plan: parsePlanLabel(data),
353
334
  lines,
354
- }
335
+ }, token)
355
336
  }
356
337
 
357
338
  globalThis.__openusage_plugin = { id: "kimi", probe }
@@ -6,10 +6,13 @@
6
6
  "entry": "plugin.js",
7
7
  "icon": "icon.svg",
8
8
  "brandColor": "#000000",
9
- "supportedModes": ["oauth", "local"],
9
+ "supportedModes": ["api", "oauth", "cli", "web"],
10
10
  "autoMode": "oauth",
11
+ "envVars": ["KIMI_CODE_API_KEY", "KIMI_CODE_HOME", "KIMI_CODE_BASE_URL", "KIMI_CODE_OAUTH_HOST", "KIMI_OAUTH_HOST", "KIMI_AUTH_TOKEN"],
11
12
  "lines": [
12
13
  { "type": "progress", "label": "Session", "scope": "overview", "primaryOrder": 1 },
13
- { "type": "progress", "label": "Weekly", "scope": "overview", "primaryOrder": 2 }
14
+ { "type": "progress", "label": "Weekly", "scope": "overview", "primaryOrder": 2 },
15
+ { "type": "progress", "label": "Monthly", "scope": "overview" },
16
+ { "type": "progress", "label": "Code 7-day", "scope": "detail" }
14
17
  ]
15
18
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -88,7 +88,13 @@
88
88
  }
89
89
  function regionFromArn(profileArn) {
90
90
  const parts = String(profileArn || "").split(":")
91
- return parts.length > 3 && parts[3] ? parts[3] : DEFAULT_REGION
91
+ if (parts.length !== 6 || parts[0] !== "arn" || parts[1] !== "aws" ||
92
+ parts[2] !== "codewhisperer" || !/^profile\/.+/.test(parts[5]) ||
93
+ /[\s\x00-\x1f\x7f]/.test(profileArn) ||
94
+ ["us-east-1", "eu-central-1"].indexOf(parts[3]) === -1) {
95
+ throw "Kiro profile ARN is invalid or uses an unsupported region. Sign in again with Kiro."
96
+ }
97
+ return parts[3]
92
98
  }
93
99
  function readStateValue(ctx, key) {
94
100
  try {
@@ -118,9 +124,12 @@
118
124
  }
119
125
  function normalizeBreakdown(ctx, raw) {
120
126
  if (!raw || typeof raw !== "object") return null
121
- const currentUsage = first(raw.currentUsageWithPrecision, raw.currentUsage)
127
+ const totalUsage = first(raw.currentUsageWithPrecision, raw.currentUsage)
122
128
  const usageLimit = first(raw.usageLimitWithPrecision, raw.usageLimit)
123
- if (currentUsage === null || usageLimit === null || usageLimit <= 0) return null
129
+ const overageUsed = first(raw.currentOveragesWithPrecision, raw.currentOverages)
130
+ if (totalUsage === null || totalUsage < 0 || usageLimit === null || usageLimit < 0) return null
131
+ if (overageUsed !== null && (overageUsed < 0 || overageUsed > totalUsage)) return null
132
+ const currentUsage = totalUsage - (overageUsed || 0)
124
133
  const bonuses = Array.isArray(raw.bonuses)
125
134
  ? raw.bonuses
126
135
  .map((item) =>
@@ -143,6 +152,11 @@
143
152
  currentUsage,
144
153
  usageLimit,
145
154
  resetDate: iso(ctx, raw.nextDateReset || raw.resetDate),
155
+ overageUsed,
156
+ overageCap: first(raw.overageCapWithPrecision, raw.overageCap),
157
+ overageCharges: num(raw.overageCharges),
158
+ overageRate: num(raw.overageRate),
159
+ currency: typeof raw.currency === "string" ? raw.currency : "USD",
146
160
  freeTrialUsage: normalizePool(ctx, raw.freeTrialInfo || raw.freeTrialUsage, {
147
161
  current: "currentUsage",
148
162
  preciseCurrent: "currentUsageWithPrecision",
@@ -169,11 +183,14 @@
169
183
  if (!raw || typeof raw !== "object") return null
170
184
  return {
171
185
  usageBreakdowns: Array.isArray(raw.usageBreakdownList)
172
- ? raw.usageBreakdownList.map((item) => normalizeBreakdown(ctx, item)).filter(Boolean)
186
+ ? raw.usageBreakdownList.map((item) => normalizeBreakdown(ctx, {
187
+ ...item, nextDateReset: item.nextDateReset || raw.nextDateReset,
188
+ })).filter(Boolean)
173
189
  : [],
174
190
  timestampMs: timestampMs !== null ? timestampMs : null,
175
191
  plan: title(raw.subscriptionInfo && raw.subscriptionInfo.subscriptionTitle),
176
- overageEnabled: raw.overageConfiguration ? raw.overageConfiguration.overageStatus === "ENABLED" : null,
192
+ overageEnabled: raw.overageConfiguration && raw.overageConfiguration.overageStatus === "ENABLED"
193
+ ? true : raw.overageConfiguration && raw.overageConfiguration.overageStatus === "DISABLED" ? false : null,
177
194
  }
178
195
  }
179
196
  function parseUsageLogText(ctx, text) {
@@ -274,7 +291,7 @@
274
291
  encodeURIComponent("AGENTIC_REQUEST")
275
292
 
276
293
  let accessToken = authState.token && authState.token.accessToken
277
- if (!accessToken || needsRefresh(ctx, authState, nowMs)) {
294
+ if (!accessToken || authState.token.refreshToken && needsRefresh(ctx, authState, nowMs)) {
278
295
  const refreshed = refreshAccessToken(ctx, authState, nowMs)
279
296
  if (refreshed) accessToken = refreshed
280
297
  }
@@ -326,6 +343,7 @@
326
343
  : null,
327
344
  usageBreakdowns: usageSource.usageBreakdowns,
328
345
  timestampMs: usageSource.timestampMs !== null ? usageSource.timestampMs : nowMs,
346
+ source: usageSource === liveState ? "api" : "local",
329
347
  }
330
348
  }
331
349
  function pickPrimaryBreakdown(usageBreakdowns) {
@@ -347,7 +365,9 @@
347
365
  function buildOutput(ctx, snapshot, nowMs) {
348
366
  const primary = pickPrimaryBreakdown(snapshot.usageBreakdowns)
349
367
  if (!primary) throw DATA_HINT
350
- const lines = [ctx.line.progress({ label: "Credits", used: primary.currentUsage, limit: primary.usageLimit, format: COUNT_FORMAT, resetsAt: primary.resetDate || undefined })]
368
+ const lines = [primary.usageLimit > 0
369
+ ? ctx.line.progress({ label: "Credits", used: primary.currentUsage, limit: primary.usageLimit, format: COUNT_FORMAT, resetsAt: primary.resetDate || undefined })
370
+ : ctx.line.text({ label: "Credits", value: String(primary.currentUsage) + " used; no included credits" })]
351
371
  const bonusUsage = pickBonusUsage(primary)
352
372
  if (bonusUsage) {
353
373
  lines.push(
@@ -361,17 +381,36 @@
361
381
  )
362
382
  }
363
383
  if (snapshot.overageEnabled !== null) lines.push(ctx.line.badge({ label: "Overages", text: snapshot.overageEnabled ? "Enabled" : "Disabled" }))
364
- return { plan: snapshot.plan || undefined, lines }
384
+ if (primary.overageUsed !== null && primary.overageUsed !== undefined) {
385
+ if (snapshot.overageEnabled === true && primary.overageCap > 0) {
386
+ lines.push(ctx.line.progress({ label: "Overage Credits", used: primary.overageUsed,
387
+ limit: primary.overageCap, format: COUNT_FORMAT, resetsAt: primary.resetDate || undefined }))
388
+ } else if (primary.overageUsed > 0) {
389
+ lines.push(ctx.line.text({ label: "Overage Credits", value: String(primary.overageUsed) + " credits" }))
390
+ }
391
+ }
392
+ if (primary.overageCharges !== null && primary.overageCharges >= 0) {
393
+ const cap = snapshot.overageEnabled === true && primary.overageCap > 0 && primary.overageRate > 0
394
+ ? primary.overageCap * primary.overageRate : null
395
+ if (primary.currency === "USD" && cap > 0) {
396
+ lines.push(ctx.line.progress({ label: "Overage Charges", used: primary.overageCharges,
397
+ limit: cap, format: { kind: "dollars" }, resetsAt: primary.resetDate || undefined }))
398
+ } else {
399
+ lines.push(ctx.line.text({ label: "Overage Charges",
400
+ value: primary.currency + " " + primary.overageCharges.toFixed(2) }))
401
+ }
402
+ }
403
+ return { plan: snapshot.plan || undefined, lines, source: snapshot.source, fetchedAt: ctx.util.toIso(snapshot.timestampMs) }
365
404
  }
366
405
  function probe(ctx) {
367
406
  const nowMs = ctx.util.parseDateMs(ctx.nowIso) || Date.now()
368
407
  const authState = loadAuthState(ctx)
369
- if (!authState || !authState.token || !authState.token.refreshToken) throw LOGIN_HINT
408
+ if (ctx.sourceMode !== "local" && (!authState || !authState.token)) throw LOGIN_HINT
370
409
  const localState = normalizeCachedState(ctx)
371
410
  const loggedState = loadLoggedState(ctx)
372
411
  let liveState = null
373
412
  let liveError = null
374
- if (shouldTryLive(localState, loggedState, nowMs)) {
413
+ if (ctx.sourceMode !== "local" && shouldTryLive(localState, loggedState, nowMs)) {
375
414
  try {
376
415
  liveState = fetchLiveState(ctx, authState, nowMs)
377
416
  } catch (e) {
@@ -11,6 +11,8 @@
11
11
  "lines": [
12
12
  { "type": "progress", "label": "Credits", "scope": "overview", "primaryOrder": 1 },
13
13
  { "type": "progress", "label": "Bonus Credits", "scope": "overview" },
14
- { "type": "badge", "label": "Overages", "scope": "detail" }
14
+ { "type": "badge", "label": "Overages", "scope": "detail" },
15
+ { "type": "progress", "label": "Overage Credits", "scope": "detail" },
16
+ { "type": "progress", "label": "Overage Charges", "scope": "detail" }
15
17
  ]
16
18
  }
File without changes
@@ -9,6 +9,8 @@
9
9
  }
10
10
 
11
11
  function apiKey(ctx) {
12
+ if (Object.prototype.hasOwnProperty.call(ctx.provider || {}, 'apiKey')) return ctx.provider.apiKey;
13
+ if (ctx.provider && ctx.provider.instanceId && ctx.provider.instanceId !== 'litellm') return null;
12
14
  var configured = ctx.provider && typeof ctx.provider.apiKey === "string" ? ctx.provider.apiKey.trim() : "";
13
15
  return configured || env(ctx, "LITELLM_API_KEY");
14
16
  }
@@ -51,13 +53,14 @@
51
53
  return Number.isFinite(ms) ? new Date(ms) : null;
52
54
  }
53
55
 
54
- function requestJson(ctx, base, path, key) {
56
+ function requestJson(ctx, base, path, key, optional) {
55
57
  var resp = ctx.util.request({
56
58
  method: "GET",
57
59
  url: base + path,
58
60
  headers: { Authorization: "Bearer " + key, Accept: "application/json" },
59
61
  timeoutMs: 15000,
60
62
  });
63
+ if (optional && [401,403,404].includes(resp.status)) return null;
61
64
  if (ctx.util.isAuthStatus(resp.status)) throw "LiteLLM API key was rejected.";
62
65
  if (resp.status < 200 || resp.status >= 300) {
63
66
  throw "LiteLLM API request failed (HTTP " + resp.status + ").";
@@ -189,10 +192,58 @@
189
192
  var key = apiKey(ctx);
190
193
  if (!key) throw "Missing LiteLLM API key. Set LITELLM_API_KEY or provider apiKey.";
191
194
  var base = baseUrl(ctx);
192
- var info = keyInfo(requestJson(ctx, base, "/key/info", key));
195
+ var keyData = requestJson(ctx, base, "/key/info", key, true);
196
+ if (keyData === null) {
197
+ var end = ctx.nowIso.slice(0,10), start = end.slice(0,7)+'-01';
198
+ var query = '?start_date='+start+'&end_date='+end;
199
+ var rows = requestJson(ctx, base, '/key/spend/report'+query, key, true);
200
+ if (rows === null) rows = requestJson(ctx, base, '/user/spend/report'+query, key);
201
+ if (!Array.isArray(rows) || !rows.length) throw new Error('LiteLLM spend report is empty or invalid.');
202
+ var cost = 0;
203
+ for (var row of rows) {
204
+ if (typeof row.total_cost !== 'number' || !Number.isFinite(row.total_cost) || row.total_cost < 0) throw new Error('Invalid LiteLLM spend.');
205
+ cost += row.total_cost;
206
+ }
207
+ if (!Number.isFinite(cost)) throw new Error('Invalid LiteLLM spend total.');
208
+ return {source:'api',lines:[ctx.line.text({label:'Month to date',value:usd(cost),subtitle:start+' to '+end+' UTC'})]};
209
+ }
210
+ var info = keyInfo(keyData);
193
211
  var snapshot = info.userID ? userSnapshot(ctx, base, key, info) : teamSnapshot(ctx, base, key, info);
194
212
  var plan = snapshot.team && snapshot.team.alias ? snapshot.team.alias : null;
195
- return { displayName: "LiteLLM", source: "api", plan: plan, lines: linesFor(snapshot) };
213
+ var lines = linesFor(snapshot);
214
+ if (info.userID && (ctx.provider?.settings?.modelUsage === true || ctx.provider?.settings?.LITELLM_MODEL_USAGE_ENABLED === 'true')) {
215
+ try { lines.push.apply(lines, modelActivity(ctx,base,key,info.userID)); } catch (_) { ctx.host.log.warn('LiteLLM optional model activity unavailable.'); }
216
+ }
217
+ return { displayName: "LiteLLM", source: "api", plan: plan, lines: lines };
218
+ }
219
+
220
+ function modelActivity(ctx,base,key,user) {
221
+ var end=ctx.nowIso.slice(0,10),start=new Date(Date.parse(end)-29*86400000).toISOString().slice(0,10),totals={};
222
+ for(var page=1;page<=3;page++) {
223
+ var response=ctx.host.http.request({method:'GET',url:base+'/user/daily/activity?user_id='+encodeURIComponent(user)+'&start_date='+start+'&end_date='+end+'&page='+page+'&page_size=1000',
224
+ headers:{Authorization:'Bearer '+key,Accept:'application/json'},timeoutMs:2000});
225
+ if(response.status!==200) throw new Error('Unavailable');
226
+ var data=ctx.util.tryParseJson(response.bodyText);
227
+ if(!Array.isArray(data?.results)||data.results.length>31) throw new Error('Invalid activity');
228
+ for(var row of data.results) {
229
+ if(!/^\d{4}-\d{2}-\d{2}$/.test(row.date)||row.date<start||row.date>end||!row.breakdown?.models) throw new Error('Invalid day');
230
+ for(var [name, value] of Object.entries(row.breakdown.models)) {
231
+ if(!name.trim()||name.length>256||/[\x00-\x1f]/.test(name)||['__proto__','constructor','prototype'].includes(name)) throw new Error('Invalid model');
232
+ var metrics=value.metrics||value,prior=totals[name]||[0,0,0,0];
233
+ totals[name]=['prompt_tokens','completion_tokens','total_tokens','api_requests'].map(function(field,i){
234
+ var n=metrics[field]; if(!Number.isSafeInteger(n)||n<0||!Number.isSafeInteger(n+prior[i])) throw new Error('Invalid count'); return n+prior[i];
235
+ });
236
+ if(Object.keys(totals).length>1000) throw new Error('Too many models');
237
+ }
238
+ }
239
+ var metadata=data.metadata||{};
240
+ if(metadata.page!=null&&metadata.page!==page) throw new Error('Repeated page');
241
+ if(metadata.has_more!=null&&typeof metadata.has_more!=='boolean') throw new Error('Invalid page');
242
+ if(metadata.total_pages!=null&&(!Number.isSafeInteger(metadata.total_pages)||metadata.total_pages<1)) throw new Error('Invalid page');
243
+ if(metadata.has_more!==true&&page>=(metadata.total_pages||1)) return Object.entries(totals).sort((a,b)=>b[1][2]-a[1][2]).slice(0,20)
244
+ .map(([name,n])=>ctx.line.text({label:name,value:n[2]+' tokens / '+n[3]+' requests',subtitle:'30d UTC: '+n[0]+' input / '+n[1]+' output'}));
245
+ }
246
+ throw new Error('Incomplete activity');
196
247
  }
197
248
 
198
249
  globalThis.__openusage_plugin = { id: "litellm", probe: probe };
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peter Steinberger
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.