@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,92 +1,293 @@
1
1
  (function () {
2
- var SETTINGS_URL = "https://ollama.com/settings"
3
- var SESSION_COOKIE = "__Secure-session"
2
+ var SETTINGS_URL = "https://ollama.com/settings";
3
+ var TAGS_URL = "https://ollama.com/api/tags";
4
+ var VALIDATION_URL = "https://ollama.com/api/web_search";
5
+ var SESSION_COOKIE = "__Secure-session";
6
+ var SESSION_MS = 5 * 60 * 60 * 1000;
7
+ var WEEK_MS = 7 * 24 * 60 * 60 * 1000;
4
8
 
5
- function getCookieHeader(ctx) {
6
- var raw = ctx.host.env.get("OLLAMA_COOKIE")
7
- if (!raw || !raw.trim()) {
8
- throw "Set OLLAMA_COOKIE to your " + SESSION_COOKIE + " cookie value from ollama.com/settings."
9
+ function clean(value) {
10
+ if (typeof value !== "string") return null;
11
+ var v = value.trim();
12
+ if (!v) return null;
13
+ if (
14
+ v.length >= 2 &&
15
+ ((v.charAt(0) === '"' && v.charAt(v.length - 1) === '"') ||
16
+ (v.charAt(0) === "'" && v.charAt(v.length - 1) === "'"))
17
+ ) {
18
+ v = v.slice(1, -1).trim();
19
+ }
20
+ return v || null;
21
+ }
22
+
23
+ function env(ctx, name) {
24
+ try {
25
+ return clean(ctx.host.env.get(name));
26
+ } catch (_) {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ function setting(ctx, names) {
32
+ var settings = ctx.provider && ctx.provider.settings ? ctx.provider.settings : {};
33
+ for (var i = 0; i < names.length; i++) {
34
+ var value = clean(settings[names[i]]);
35
+ if (value) return value;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function getApiKey(ctx) {
41
+ return clean(ctx.provider && ctx.provider.apiKey) ||
42
+ setting(ctx, ["apiKey", "ollamaApiKey"]) ||
43
+ env(ctx, "OLLAMA_API_KEY") ||
44
+ env(ctx, "OLLAMA_KEY");
45
+ }
46
+
47
+ function getCookieHeader(ctx, optional) {
48
+ var raw = clean(ctx.provider && ctx.provider.cookieHeader) ||
49
+ setting(ctx, ["cookieHeader", "cookie"]) ||
50
+ env(ctx, "OLLAMA_COOKIE");
51
+ if (!raw) {
52
+ if (optional) return null;
53
+ throw "Set OLLAMA_COOKIE to your " + SESSION_COOKIE + " cookie value from ollama.com/settings.";
54
+ }
55
+ var val = raw;
56
+ if (val.toLowerCase().slice(0, 5) === "curl ") {
57
+ var cookieMatch = val.match(/(?:^|\s)-H\s+['"]Cookie:\s*([^'"]+)['"]/i) ||
58
+ val.match(/(?:^|\s)--header\s+['"]Cookie:\s*([^'"]+)['"]/i);
59
+ if (cookieMatch) val = cookieMatch[1].trim();
9
60
  }
10
- var val = raw.trim()
11
- // Strip optional "cookie:" prefix
12
61
  if (val.toLowerCase().slice(0, 7) === "cookie:") {
13
- val = val.slice(7).trim()
62
+ val = val.slice(7).trim();
14
63
  }
15
- // If just a bare token value with no key=value format, wrap it
16
64
  if (val.indexOf("=") < 0) {
17
- return SESSION_COOKIE + "=" + val
65
+ return SESSION_COOKIE + "=" + val;
66
+ }
67
+ return val;
68
+ }
69
+
70
+ function request(ctx, req) {
71
+ try {
72
+ return ctx.host.http.request(req);
73
+ } catch (e) {
74
+ ctx.host.log.warn("Ollama request failed: " + String(e));
75
+ throw "Ollama request failed. Check your connection.";
76
+ }
77
+ }
78
+
79
+ function parseJson(ctx, text) {
80
+ var parsed = ctx.util && typeof ctx.util.tryParseJson === "function"
81
+ ? ctx.util.tryParseJson(text)
82
+ : null;
83
+ if (!parsed && text) {
84
+ try {
85
+ parsed = JSON.parse(text);
86
+ } catch (_) {}
87
+ }
88
+ return parsed;
89
+ }
90
+
91
+ function probeApi(ctx, apiKey) {
92
+ var validation = request(ctx, {
93
+ method: "POST",
94
+ url: VALIDATION_URL,
95
+ headers: {
96
+ Authorization: "Bearer " + apiKey,
97
+ Accept: "application/json",
98
+ "Content-Type": "application/json",
99
+ "User-Agent": "usagestat/" + String(ctx.app && ctx.app.version ? ctx.app.version : "0.0.0"),
100
+ },
101
+ bodyText: '{"query":""}',
102
+ timeoutMs: 15000,
103
+ });
104
+ if (validation.status === 401 || validation.status === 403) {
105
+ throw "Ollama API key is invalid or revoked.";
106
+ }
107
+ if (validation.status !== 200 && validation.status !== 400) {
108
+ throw "Ollama API validation returned HTTP " + validation.status + ".";
109
+ }
110
+
111
+ var tags = request(ctx, {
112
+ method: "GET",
113
+ url: TAGS_URL,
114
+ headers: {
115
+ Authorization: "Bearer " + apiKey,
116
+ Accept: "application/json",
117
+ "User-Agent": "usagestat/" + String(ctx.app && ctx.app.version ? ctx.app.version : "0.0.0"),
118
+ },
119
+ timeoutMs: 15000,
120
+ });
121
+ if (tags.status === 401 || tags.status === 403) {
122
+ throw "Ollama API key is invalid or revoked.";
123
+ }
124
+ if (tags.status < 200 || tags.status >= 300) {
125
+ throw "Ollama API returned HTTP " + tags.status + ".";
18
126
  }
19
- return val
127
+ var parsed = parseJson(ctx, tags.bodyText);
128
+ if (!parsed || !Array.isArray(parsed.models)) {
129
+ throw "Ollama API tags response was not valid JSON.";
130
+ }
131
+
132
+ return {
133
+ displayName: "Ollama",
134
+ source: "api",
135
+ plan: "API key",
136
+ lines: [
137
+ ctx.line.text({
138
+ label: "Cloud Models",
139
+ value: String(parsed.models.length) + " available",
140
+ }),
141
+ ctx.line.badge({
142
+ label: "Status",
143
+ text: "API key valid; quota unavailable",
144
+ color: "#22c55e",
145
+ }),
146
+ ],
147
+ };
20
148
  }
21
149
 
22
- function parsePercent(html, labels) {
150
+ function blockEnd(html, start, currentLabel) {
151
+ var labels = ["Monthly usage", "Session usage", "Hourly usage", "Weekly usage"];
152
+ var end = html.length;
23
153
  for (var i = 0; i < labels.length; i++) {
24
- var idx = html.indexOf(labels[i])
25
- if (idx < 0) continue
26
- var window = html.slice(idx, idx + 800)
154
+ if (labels[i] === currentLabel) continue;
155
+ var idx = html.indexOf(labels[i], start + currentLabel.length);
156
+ if (idx >= 0 && idx < end) end = idx;
157
+ }
158
+ return Math.min(end, start + 4000);
159
+ }
160
+
161
+ function stripHtmlEntities(value) {
162
+ return String(value || "")
163
+ .replace(/&nbsp;/g, " ")
164
+ .replace(/&amp;/g, "&")
165
+ .replace(/&#x2F;/g, "/")
166
+ .replace(/\s+/g, " ")
167
+ .trim();
168
+ }
27
169
 
28
- var usedMatch = window.match(/(\d+(?:\.\d+)?)\s*%\s*used/)
29
- if (usedMatch) return parseFloat(usedMatch[1])
170
+ function parseIso(segment) {
171
+ var match = String(segment || "").match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/);
172
+ if (!match) return null;
173
+ var ms = Date.parse(match[0]);
174
+ return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
175
+ }
176
+
177
+ function parseResetDescription(segment) {
178
+ var match = String(segment || "").match(/(resets?\s+in\s+[^<\n\r]+|reset\s+[^<\n\r]+)/i);
179
+ return match ? stripHtmlEntities(match[1]) : null;
180
+ }
30
181
 
31
- var widthMatch = window.match(/width:\s*(\d+(?:\.\d+)?)%/)
32
- if (widthMatch) return parseFloat(widthMatch[1])
182
+ function parseUsageBlock(html, labels, periodMs) {
183
+ for (var i = 0; i < labels.length; i++) {
184
+ var label = labels[i];
185
+ var idx = html.indexOf(label);
186
+ if (idx < 0) continue;
187
+ var segment = html.slice(idx, blockEnd(html, idx, label));
188
+ var usedMatch = segment.match(/(\d+(?:\.\d+)?)\s*%\s*used/i);
189
+ var dollars = segment.match(/\$((?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?)\s+of\s+\$((?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?)\s+used/i);
190
+ var widthMatch = segment.match(/width:\s*(\d+(?:\.\d+)?)%/i);
191
+ var value = usedMatch ? parseFloat(usedMatch[1]) : null;
192
+ if (value === null && dollars) {
193
+ var spent = Number(dollars[1].replace(/,/g, ""));
194
+ var cap = Number(dollars[2].replace(/,/g, ""));
195
+ if (Number.isFinite(spent) && Number.isFinite(cap) && cap > 0) value = spent / cap * 100;
196
+ }
197
+ if (!Number.isFinite(value) && widthMatch) value = parseFloat(widthMatch[1]);
198
+ if (Number.isFinite(value)) {
199
+ return {
200
+ used: Math.max(0, Math.min(100, value)),
201
+ resetsAt: parseIso(segment),
202
+ detail: parseResetDescription(segment),
203
+ periodMs: label === "Hourly usage" ? null : periodMs,
204
+ };
205
+ }
33
206
  }
34
- return null
207
+ return null;
35
208
  }
36
209
 
37
- function probe(ctx) {
38
- var cookieHeader = getCookieHeader(ctx)
210
+ function progress(ctx, label, block) {
211
+ var line = {
212
+ label: label,
213
+ used: block.used,
214
+ limit: 100,
215
+ format: { kind: "percent" },
216
+ };
217
+ if (block.periodMs) line.periodDurationMs = block.periodMs;
218
+ if (block.resetsAt) line.resetsAt = block.resetsAt;
219
+ if (block.detail) line.detail = block.detail;
220
+ return ctx.line.progress(line);
221
+ }
39
222
 
40
- var resp = ctx.host.http.request({
223
+ function probeWeb(ctx) {
224
+ var cookieHeader = getCookieHeader(ctx);
225
+ var resp = request(ctx, {
41
226
  method: "GET",
42
227
  url: SETTINGS_URL,
43
228
  headers: {
44
- "Cookie": cookieHeader,
45
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
46
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
229
+ Cookie: cookieHeader,
230
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
231
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/143.0.0.0 Safari/537.36",
47
232
  },
48
233
  timeoutMs: 20000,
49
- })
234
+ });
50
235
 
51
236
  if (resp.status === 401 || resp.status === 403) {
52
- throw "Session expired. Update OLLAMA_COOKIE with a fresh session token."
237
+ throw "Session expired. Update OLLAMA_COOKIE with a fresh session token.";
53
238
  }
54
239
 
55
- var html = resp.bodyText || ""
56
-
57
- if (!html.includes("Session usage") && !html.includes("Weekly usage") && !html.includes("Cloud Usage")) {
240
+ var html = resp.bodyText || "";
241
+ if (!html.includes("Monthly usage") && !html.includes("Session usage") && !html.includes("Weekly usage") && !html.includes("Cloud Usage")) {
58
242
  if (html.includes("Sign in") || resp.status !== 200) {
59
- throw "Session expired. Update OLLAMA_COOKIE with a fresh session token."
243
+ throw "Session expired. Update OLLAMA_COOKIE with a fresh session token.";
60
244
  }
61
245
  }
62
246
 
63
- var sessionPct = parsePercent(html, ["Session usage", "Hourly usage"])
64
- var weeklyPct = parsePercent(html, ["Weekly usage"])
247
+ var monthly = parseUsageBlock(html, ["Monthly usage"], null);
248
+ if (monthly && monthly.resetsAt) monthly.periodMs = ctx.util.calendarMonthDuration(monthly.resetsAt);
249
+ var session = parseUsageBlock(html, ["Session usage", "Hourly usage"], SESSION_MS);
250
+ var weekly = parseUsageBlock(html, ["Weekly usage"], WEEK_MS);
65
251
 
66
- if (sessionPct === null && weeklyPct === null) {
67
- throw "Could not find usage data on Ollama settings page."
252
+ if (!monthly && !session && !weekly) {
253
+ throw "Could not find usage data on Ollama settings page.";
68
254
  }
69
255
 
70
- var lines = []
71
- if (sessionPct !== null) {
72
- lines.push(ctx.line.progress({
73
- label: "Session",
74
- used: sessionPct,
75
- limit: 100,
76
- format: { kind: "percent" },
77
- }))
256
+ var lines = [];
257
+ if (monthly) lines.push(progress(ctx, "Monthly credits", monthly));
258
+ if (session) lines.push(progress(ctx, "Session", session));
259
+ if (weekly) lines.push(progress(ctx, "Weekly", weekly));
260
+
261
+ var planMatch = html.match(/(?:Included usage|Cloud Usage)\s*<\/span>\s*<span[^>]*>([^<]+)<\/span/i);
262
+ return {
263
+ displayName: "Ollama",
264
+ source: "web",
265
+ plan: planMatch ? stripHtmlEntities(planMatch[1]) : undefined,
266
+ lines: lines,
267
+ };
268
+ }
269
+
270
+ function probe(ctx) {
271
+ var source = String(ctx.sourceMode || "auto").toLowerCase();
272
+ var apiKey = getApiKey(ctx);
273
+ if (source === "api") {
274
+ if (!apiKey) throw "Missing Ollama API key. Set OLLAMA_API_KEY.";
275
+ return probeApi(ctx, apiKey);
78
276
  }
79
- if (weeklyPct !== null) {
80
- lines.push(ctx.line.progress({
81
- label: "Weekly",
82
- used: weeklyPct,
83
- limit: 100,
84
- format: { kind: "percent" },
85
- }))
277
+ if (source === "web") return probeWeb(ctx);
278
+ if (getCookieHeader(ctx, true)) {
279
+ try {
280
+ return probeWeb(ctx);
281
+ } catch (e) {
282
+ if (!apiKey) throw e;
283
+ ctx.host.log.warn("Ollama settings unavailable; checking API access.");
284
+ }
86
285
  }
87
-
88
- return { lines: lines }
286
+ if (apiKey) {
287
+ return probeApi(ctx, apiKey);
288
+ }
289
+ return probeWeb(ctx);
89
290
  }
90
291
 
91
- globalThis.__openusage_plugin = { id: "ollama", probe: probe }
92
- })()
292
+ globalThis.__openusage_plugin = { id: "ollama", probe: probe };
293
+ })();
@@ -8,13 +8,16 @@
8
8
  "links": [
9
9
  { "label": "Settings", "url": "https://ollama.com/settings" }
10
10
  ],
11
- "envVars": ["OLLAMA_COOKIE"],
12
- "supportedModes": ["web"],
11
+ "envVars": ["OLLAMA_COOKIE", "OLLAMA_API_KEY", "OLLAMA_KEY"],
12
+ "supportedModes": ["api", "web"],
13
13
  "autoMode": "web",
14
14
  "webUrl": "https://ollama.com",
15
15
  "usageDashboardUrl": "https://ollama.com/settings",
16
16
  "lines": [
17
17
  { "type": "progress", "label": "Session", "scope": "overview" },
18
- { "type": "progress", "label": "Weekly", "scope": "detail" }
18
+ { "type": "progress", "label": "Weekly", "scope": "detail" },
19
+ { "type": "progress", "label": "Monthly credits", "scope": "overview" },
20
+ { "type": "text", "label": "Cloud Models", "scope": "detail" },
21
+ { "type": "badge", "label": "Status", "scope": "detail" }
19
22
  ]
20
23
  }
@@ -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>OpenAI</title><path d="M9.205 8.658v-2.26c0-.19.072-.333.238-.428l4.543-2.616c.619-.357 1.356-.523 2.117-.523 2.854 0 4.662 2.212 4.662 4.566 0 .167 0 .357-.024.547l-4.71-2.759a.797.797 0 00-.856 0l-5.97 3.473zm10.609 8.8V12.06c0-.333-.143-.57-.429-.737l-5.97-3.473 1.95-1.118a.433.433 0 01.476 0l4.543 2.617c1.309.76 2.189 2.378 2.189 3.948 0 1.808-1.07 3.473-2.76 4.163zM7.802 12.703l-1.95-1.142c-.167-.095-.239-.238-.239-.428V5.899c0-2.545 1.95-4.472 4.591-4.472 1 0 1.927.333 2.712.928L8.23 5.067c-.285.166-.428.404-.428.737v6.898zM12 15.128l-2.795-1.57v-3.33L12 8.658l2.795 1.57v3.33L12 15.128zm1.796 7.23c-1 0-1.927-.332-2.712-.927l4.686-2.712c.285-.166.428-.404.428-.737v-6.898l1.974 1.142c.167.095.238.238.238.428v5.233c0 2.545-1.974 4.472-4.614 4.472zm-5.637-5.303l-4.544-2.617c-1.308-.761-2.188-2.378-2.188-3.948A4.482 4.482 0 014.21 6.327v5.423c0 .333.143.571.428.738l5.947 3.449-1.95 1.118a.432.432 0 01-.476 0zm-.262 3.9c-2.688 0-4.662-2.021-4.662-4.519 0-.19.024-.38.047-.57l4.686 2.71c.286.167.571.167.856 0l5.97-3.448v2.26c0 .19-.07.333-.237.428l-4.543 2.616c-.619.357-1.356.523-2.117.523zm5.899 2.83a5.947 5.947 0 005.827-4.756C22.287 18.339 24 15.84 24 13.296c0-1.665-.713-3.282-1.998-4.448.119-.5.19-.999.19-1.498 0-3.401-2.759-5.947-5.946-5.947-.642 0-1.26.095-1.88.31A5.962 5.962 0 0010.205 0a5.947 5.947 0 00-5.827 4.757C1.713 5.447 0 7.945 0 10.49c0 1.666.713 3.283 1.998 4.448-.119.5-.19 1-.19 1.499 0 3.401 2.759 5.946 5.946 5.946.642 0 1.26-.095 1.88-.309a5.96 5.96 0 004.162 1.713z"></path></svg>
File without changes
File without changes
File without changes
@@ -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>opencode</title><path d="M16 6H8v12h8V6zm4 16H4V2h16v20z"></path></svg>
File without changes
File without changes
File without changes
@@ -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>opencode</title><path d="M16 6H8v12h8V6zm4 16H4V2h16v20z"></path></svg>
File without changes
File without changes
@@ -18,22 +18,27 @@
18
18
  WHERE json_valid(data)
19
19
  AND json_extract(data, '$.providerID') = 'opencode-go'
20
20
  AND json_extract(data, '$.role') = 'assistant'
21
- AND json_type(data, '$.cost') IN ('integer', 'real')
22
21
  LIMIT 1
23
22
  `;
24
23
 
25
24
  const HISTORY_ROWS_SQL = `
26
25
  SELECT
27
26
  CAST(COALESCE(json_extract(data, '$.time.created'), time_created) AS INTEGER) AS createdMs,
28
- CAST(json_extract(data, '$.cost') AS REAL) AS cost
27
+ json_extract(data, '$.cost') AS cost,
28
+ json_extract(data, '$.tokens.input') AS inputTokens,
29
+ json_extract(data, '$.tokens.output') AS outputTokens,
30
+ json_extract(data, '$.tokens.cache.read') AS cacheReadTokens,
31
+ json_extract(data, '$.tokens.cache.write') AS cacheCreationTokens,
32
+ json_extract(data, '$.tokens.reasoning') AS reasoningOutputTokens
29
33
  FROM message
30
34
  WHERE json_valid(data)
31
35
  AND json_extract(data, '$.providerID') = 'opencode-go'
32
36
  AND json_extract(data, '$.role') = 'assistant'
33
- AND json_type(data, '$.cost') IN ('integer', 'real')
34
37
  `;
35
38
 
36
39
  function readNumber(value) {
40
+ if (typeof value !== "number" && typeof value !== "string") return null;
41
+ if (typeof value === "string" && !value.trim()) return null;
37
42
  const n = Number(value);
38
43
  return Number.isFinite(n) ? n : null;
39
44
  }
@@ -318,6 +323,8 @@
318
323
  }
319
324
 
320
325
  function loadAuthKey(ctx) {
326
+ const configured = trim(ctx.provider && ctx.provider.apiKey) || env(ctx, "OPENCODE_GO_API_KEY");
327
+ if (configured) return configured;
321
328
  const inline = ctx.host.env.get("OPENCODE_AUTH_CONTENT");
322
329
  const path = inline ? null : dataPath(ctx, "auth.json");
323
330
  if (!inline && !ctx.host.fs.exists(path)) return null;
@@ -357,9 +364,15 @@
357
364
  if (!row || typeof row !== "object") continue;
358
365
  const createdMs = readNumber(row.createdMs);
359
366
  const cost = readNumber(row.cost);
360
- if (createdMs === null || createdMs <= 0) continue;
361
- if (cost === null || cost < 0) continue;
362
- rows.push({ createdMs, cost });
367
+ if (createdMs === null || createdMs <= 0 || createdMs > Date.parse(ctx.nowIso)) continue;
368
+ const entry = { createdMs, cost: cost !== null && cost >= 0 ? cost : 0, costKnown: cost !== null && cost >= 0, tokensKnown: false };
369
+ for (const key of ['inputTokens','outputTokens','cacheReadTokens','cacheCreationTokens','reasoningOutputTokens']) {
370
+ const value = readNumber(row[key]);
371
+ entry[key] = value !== null && value >= 0 ? value : 0;
372
+ if (value !== null && value >= 0 && key !== 'reasoningOutputTokens') entry.tokensKnown = true;
373
+ }
374
+ entry.totalTokens = entry.inputTokens + entry.outputTokens + entry.cacheReadTokens + entry.cacheCreationTokens;
375
+ if (entry.costKnown || entry.tokensKnown) rows.push(entry);
363
376
  }
364
377
 
365
378
  return { ok: true, rows };
@@ -438,10 +451,9 @@
438
451
  text,
439
452
  new RegExp(name + "[^}]*?(?:resetInSec|resetInSeconds|resetSeconds|resetSec)\\s*[:=]\\s*([0-9]+)")
440
453
  ) || 0;
441
- const normalized = percent <= 1 ? percent * 100 : percent;
442
454
  return {
443
- percent: Math.max(0, Math.min(100, normalized)),
444
- resetsAt: toIso(Date.now() + Math.max(0, reset) * 1000),
455
+ percent: Math.max(0, Math.min(100, percent)),
456
+ resetsAt: reset > 0 ? toIso(Date.now() + reset * 1000) : null,
445
457
  };
446
458
  }
447
459
  return null;
@@ -560,11 +572,52 @@
560
572
  if (!parsed.lines.length) {
561
573
  throw "OpenCode Go parse error: missing usage and balance fields.";
562
574
  }
563
- return { plan: "Go", lines: parsed.lines };
575
+ return { plan: "Go", source: "web", lines: parsed.lines };
576
+ }
577
+
578
+ function fetchApiResult(ctx, key) {
579
+ const result = ctx.util.requestJson({
580
+ method: "GET", url: BASE_URL + "/zen/go/v1/usage",
581
+ headers: { Authorization: "Bearer " + key, Accept: "application/json" }, timeoutMs: 15000,
582
+ });
583
+ if (result.resp.status === 403 && result.json && result.json.error && result.json.error.type === "EntitlementError")
584
+ throw "No OpenCode Go subscription on this key.";
585
+ if (ctx.util.isAuthStatus(result.resp.status)) throw "OpenCode Go key was rejected. Sign into OpenCode Go again.";
586
+ if (result.resp.status < 200 || result.resp.status >= 300) throw "OpenCode Go usage API returned HTTP " + result.resp.status + ".";
587
+ const usage = result.json && result.json.usage;
588
+ const specs = [["rolling", "Session", FIVE_HOURS_MS], ["weekly", "Weekly", WEEK_MS], ["monthly", "Monthly", null]];
589
+ const lines = [];
590
+ for (const spec of specs) {
591
+ const window = usage && usage[spec[0]];
592
+ if (window == null && spec[0] !== "rolling") continue;
593
+ const percent = window && readNumber(window.percent);
594
+ if (!Number.isFinite(percent)) throw "OpenCode Go usage API returned an invalid quota.";
595
+ const resetInSec = readNumber(window.resetInSec);
596
+ const resetsAt = ctx.util.toIso(window.resetsAt) || (resetInSec !== null && resetInSec >= 0
597
+ ? ctx.util.toIso(Date.parse(ctx.nowIso) + resetInSec * 1000) : null);
598
+ lines.push(ctx.line.progress({ label: spec[1], used: Math.max(0, Math.min(100, percent)), limit: 100,
599
+ format: { kind: "percent" }, resetsAt, periodDurationMs: spec[2] }));
600
+ }
601
+ return { plan: "Go", source: "api", lines };
564
602
  }
565
603
 
566
604
  function probe(ctx) {
605
+ const source = ctx.sourceMode || "auto";
606
+ if (source === "web") {
607
+ const cookie = cookieHeader(ctx);
608
+ if (!cookie) throw "OpenCode Go session cookie is missing.";
609
+ return fetchWebResult(ctx, cookie);
610
+ }
567
611
  const authKey = loadAuthKey(ctx);
612
+ if (source === "api" && !authKey) throw "OpenCode Go API key missing. Set OPENCODE_GO_API_KEY.";
613
+ if (authKey && source !== "local") {
614
+ const result = fetchApiResult(ctx, authKey);
615
+ try {
616
+ const history = loadHistory(ctx);
617
+ if (history.ok) attachCostHistory(ctx, result.lines, history.rows);
618
+ } catch (_) { ctx.host.log.warn("OpenCode Go local cost history unavailable"); }
619
+ return result;
620
+ }
568
621
  const history = hasHistory(ctx);
569
622
  const cookie = cookieHeader(ctx);
570
623
  const detected = !!authKey || !!cookie || (history.ok && history.present);
@@ -574,13 +627,16 @@
574
627
  }
575
628
 
576
629
  if (!history.ok && !cookie) {
577
- return { plan: "Go", lines: buildSoftEmptyLines(ctx) };
630
+ return { plan: "Go", source: "local", lines: buildSoftEmptyLines(ctx) };
578
631
  }
579
632
 
580
633
  const rowsResult = loadHistory(ctx);
581
634
  if (rowsResult.ok && rowsResult.rows.length > 0) {
582
- const lines = buildProgressLines(ctx, rowsResult.rows, readNowMs());
583
- if (cookie) {
635
+ const lines = rowsResult.rows.every(row => row.costKnown)
636
+ ? buildProgressLines(ctx, rowsResult.rows, readNowMs()) : [];
637
+ for (const line of lines) line.detail = "Estimated from local API-rate costs";
638
+ attachCostHistory(ctx, lines, rowsResult.rows);
639
+ if (cookie && source !== "local") {
584
640
  try {
585
641
  const webResult = fetchWebResult(ctx, cookie);
586
642
  for (let i = 0; i < webResult.lines.length; i += 1) {
@@ -592,10 +648,10 @@
592
648
  ctx.host.log.warn("OpenCode Go web enrichment failed: " + String(e));
593
649
  }
594
650
  }
595
- return { plan: "Go", lines };
651
+ return { plan: "Go", source: "local-estimate", lines };
596
652
  }
597
653
 
598
- if (cookie) {
654
+ if (cookie && source !== "local") {
599
655
  try {
600
656
  return fetchWebResult(ctx, cookie);
601
657
  } catch (e) {
@@ -604,14 +660,44 @@
604
660
  }
605
661
 
606
662
  if (!rowsResult.ok) {
607
- return { plan: "Go", lines: buildSoftEmptyLines(ctx) };
663
+ return { plan: "Go", source: "local", lines: buildSoftEmptyLines(ctx) };
608
664
  }
609
665
 
610
666
  return {
611
667
  plan: "Go",
612
- lines: buildProgressLines(ctx, rowsResult.rows, readNowMs()),
668
+ source: "local",
669
+ lines: buildSoftEmptyLines(ctx),
613
670
  };
614
671
  }
615
672
 
673
+ function attachCostHistory(ctx, lines, rows) {
674
+ const byDay = {};
675
+ const now = Date.parse(ctx.nowIso);
676
+ const since = Date.parse(ctx.nowIso.slice(0, 10)) - 29 * 86400000;
677
+ for (const row of rows) {
678
+ if (row.createdMs < since || row.createdMs > now) continue;
679
+ const date = toIso(row.createdMs);
680
+ if (!date) continue;
681
+ const day = date.slice(0, 10);
682
+ const daily = byDay[day] || (byDay[day] = { date: day, costUsd: 0, tokensKnown: true, costKnown: true,
683
+ inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, reasoningOutputTokens: 0, totalTokens: 0, requests: 0 });
684
+ daily.costUsd += row.cost;
685
+ daily.costKnown = daily.costKnown && row.costKnown;
686
+ daily.tokensKnown = daily.tokensKnown && row.tokensKnown;
687
+ daily.requests++;
688
+ for (const key of ['inputTokens','outputTokens','cacheReadTokens','cacheCreationTokens','reasoningOutputTokens','totalTokens']) daily[key] += row[key];
689
+ }
690
+ const daily = Object.keys(byDay).sort().map((day) => byDay[day]);
691
+ if (!daily.length) return;
692
+ const total = daily.reduce((n, day) => n + day.costUsd, 0), known = daily.every(day => day.costKnown);
693
+ lines.push(ctx.line.text({ label: "Local Cost", value: known ? "$" + total.toFixed(2) : total > 0 ? "$" + total.toFixed(2) + "+" : "Unpriced", subtitle: "Last 30 days; estimated API-rate cost" }));
694
+ lines.push(ctx.line.text({ label: "Local Tokens", value: String(daily.reduce((n, day) => n + day.totalTokens, 0)) + (daily.every(day => day.tokensKnown) ? "" : "+"), subtitle: "Last 30 days" }));
695
+ lines.push(ctx.line.barChart({ label: "Cost History", points: daily.map((day) => ({ label: day.date, value: day.costUsd, valueLabel: day.costKnown ? "$" + day.costUsd.toFixed(2) : day.costUsd > 0 ? "$" + day.costUsd.toFixed(2) + "+" : "Unpriced" })),
696
+ note: "Estimated from local OpenCode Go messages, not a subscription bill.", color: "#4d9f75" }));
697
+ try {
698
+ ctx.host.usageDaily.ingest({ displayName: "OpenCode Go", source: "opencode_go_local_estimated", daily });
699
+ } catch (_) { ctx.host.log.warn("Could not persist OpenCode Go daily costs"); }
700
+ }
701
+
616
702
  globalThis.__openusage_plugin = { id: PROVIDER_ID, probe };
617
703
  })();
@@ -17,9 +17,12 @@
17
17
  }
18
18
  ],
19
19
  "usageDashboardUrl": "https://opencode.ai/auth",
20
- "supportedModes": ["local", "web"],
21
- "autoMode": "local",
20
+ "envVars": ["OPENCODE_GO_API_KEY", "OPENCODE_GO_COOKIE", "OPENCODE_GO_WORKSPACE_ID"],
21
+ "supportedModes": ["api", "local", "web"],
22
+ "autoMode": "api",
22
23
  "lines": [
24
+ { "type": "text", "label": "Local Cost", "scope": "detail" },
25
+ { "type": "barChart", "label": "Cost History", "scope": "detail" },
23
26
  {
24
27
  "type": "progress",
25
28
  "label": "Session",
@@ -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>OpenRouter</title><path d="M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z"></path></svg>
File without changes