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

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 (273) hide show
  1. package/LICENSE +0 -0
  2. package/native-manifest.json +365 -77
  3. package/package.json +49 -1
  4. package/plugins/UPSTREAM-LICENSES.md +109 -0
  5. package/plugins/abacus-ai/icon.svg +0 -0
  6. package/plugins/abacus-ai/plugin.js +0 -0
  7. package/plugins/abacus-ai/plugin.json +0 -0
  8. package/plugins/aiand/icon.svg +6 -0
  9. package/plugins/aiand/plugin.js +108 -0
  10. package/plugins/aiand/plugin.json +22 -0
  11. package/plugins/alibaba/icon-color.svg +0 -0
  12. package/plugins/alibaba/icon.svg +0 -0
  13. package/plugins/alibaba/plugin.js +0 -0
  14. package/plugins/alibaba/plugin.json +0 -0
  15. package/plugins/alibaba-token-plan/icon-color.svg +0 -0
  16. package/plugins/alibaba-token-plan/icon.svg +0 -0
  17. package/plugins/alibaba-token-plan/plugin.js +0 -0
  18. package/plugins/alibaba-token-plan/plugin.json +0 -0
  19. package/plugins/amp/icon-color.svg +0 -0
  20. package/plugins/amp/icon-conflict.svg +0 -0
  21. package/plugins/amp/icon.svg +0 -0
  22. package/plugins/amp/plugin.js +0 -0
  23. package/plugins/amp/plugin.json +0 -0
  24. package/plugins/antigravity/icon-color.svg +0 -0
  25. package/plugins/antigravity/icon-conflict.svg +0 -0
  26. package/plugins/antigravity/icon.svg +0 -0
  27. package/plugins/antigravity/plugin.js +0 -0
  28. package/plugins/antigravity/plugin.json +0 -0
  29. package/plugins/antigravity-cli/icon.svg +0 -0
  30. package/plugins/antigravity-cli/plugin.js +0 -0
  31. package/plugins/antigravity-cli/plugin.json +0 -0
  32. package/plugins/antigravity-ide/icon.svg +0 -0
  33. package/plugins/antigravity-ide/plugin.js +0 -0
  34. package/plugins/antigravity-ide/plugin.json +0 -0
  35. package/plugins/augment/icon.svg +0 -0
  36. package/plugins/augment/plugin.js +0 -0
  37. package/plugins/augment/plugin.json +0 -0
  38. package/plugins/aws-bedrock/icon-color.svg +0 -0
  39. package/plugins/aws-bedrock/icon.svg +0 -0
  40. package/plugins/aws-bedrock/plugin.js +0 -0
  41. package/plugins/aws-bedrock/plugin.json +0 -0
  42. package/plugins/azure-openai/icon-color.svg +0 -0
  43. package/plugins/azure-openai/icon.svg +0 -0
  44. package/plugins/azure-openai/plugin.js +0 -0
  45. package/plugins/azure-openai/plugin.json +0 -0
  46. package/plugins/chutes/icon.svg +0 -0
  47. package/plugins/chutes/plugin.js +0 -0
  48. package/plugins/chutes/plugin.json +0 -0
  49. package/plugins/claude/icon-color.svg +0 -0
  50. package/plugins/claude/icon.svg +0 -0
  51. package/plugins/claude/plugin.js +70 -23
  52. package/plugins/claude/plugin.json +0 -0
  53. package/plugins/clawrouter/icon.svg +7 -0
  54. package/plugins/clawrouter/plugin.js +45 -0
  55. package/plugins/clawrouter/plugin.json +22 -0
  56. package/plugins/clinepass/icon.svg +4 -0
  57. package/plugins/clinepass/plugin.js +71 -0
  58. package/plugins/clinepass/plugin.json +23 -0
  59. package/plugins/codebuddy/plugin.js +331 -0
  60. package/plugins/codebuddy/plugin.json +30 -0
  61. package/plugins/codebuff/icon.svg +0 -0
  62. package/plugins/codebuff/plugin.js +0 -0
  63. package/plugins/codebuff/plugin.json +0 -0
  64. package/plugins/codex/icon-color.svg +0 -0
  65. package/plugins/codex/icon.svg +0 -0
  66. package/plugins/codex/plugin.js +7 -2
  67. package/plugins/codex/plugin.json +0 -0
  68. package/plugins/command-code/icon.svg +0 -0
  69. package/plugins/command-code/plugin.js +7 -0
  70. package/plugins/command-code/plugin.json +0 -0
  71. package/plugins/copilot/icon-color.svg +0 -0
  72. package/plugins/copilot/icon.svg +0 -0
  73. package/plugins/copilot/plugin.js +61 -9
  74. package/plugins/copilot/plugin.json +4 -1
  75. package/plugins/crof/icon.svg +0 -0
  76. package/plugins/crof/plugin.js +0 -0
  77. package/plugins/crof/plugin.json +0 -0
  78. package/plugins/crofai/icon.svg +0 -0
  79. package/plugins/crofai/plugin.js +0 -0
  80. package/plugins/crofai/plugin.json +0 -0
  81. package/plugins/cursor/icon-conflict.svg +0 -0
  82. package/plugins/cursor/icon.svg +0 -0
  83. package/plugins/cursor/plugin.js +46 -9
  84. package/plugins/cursor/plugin.json +3 -2
  85. package/plugins/cursor-nightly/icon.png +0 -0
  86. package/plugins/cursor-nightly/plugin.js +45 -8
  87. package/plugins/cursor-nightly/plugin.json +3 -2
  88. package/plugins/deepgram/icon.svg +0 -0
  89. package/plugins/deepgram/plugin.js +0 -0
  90. package/plugins/deepgram/plugin.json +0 -0
  91. package/plugins/deepinfra/icon.svg +4 -0
  92. package/plugins/deepinfra/plugin.js +97 -0
  93. package/plugins/deepinfra/plugin.json +28 -0
  94. package/plugins/deepseek/icon-color.svg +0 -0
  95. package/plugins/deepseek/icon.svg +0 -0
  96. package/plugins/deepseek/plugin.js +0 -0
  97. package/plugins/deepseek/plugin.json +0 -0
  98. package/plugins/devin/icon.svg +0 -0
  99. package/plugins/devin/plugin.js +111 -0
  100. package/plugins/devin/plugin.json +2 -1
  101. package/plugins/doubao/icon-color.svg +0 -0
  102. package/plugins/doubao/icon.svg +0 -0
  103. package/plugins/doubao/plugin.js +0 -0
  104. package/plugins/doubao/plugin.json +0 -0
  105. package/plugins/droid/icon.svg +0 -0
  106. package/plugins/droid/plugin.json +2 -1
  107. package/plugins/elevenlabs/icon.svg +0 -0
  108. package/plugins/elevenlabs/plugin.js +17 -2
  109. package/plugins/elevenlabs/plugin.json +0 -0
  110. package/plugins/factory/icon-conflict.svg +0 -0
  111. package/plugins/factory/icon.svg +0 -0
  112. package/plugins/factory/plugin.js +38 -14
  113. package/plugins/factory/plugin.json +2 -1
  114. package/plugins/fireworks-ai/icon.svg +0 -0
  115. package/plugins/fireworks-ai/plugin.js +0 -0
  116. package/plugins/fireworks-ai/plugin.json +0 -0
  117. package/plugins/gemini/icon-color.svg +0 -0
  118. package/plugins/gemini/icon.svg +0 -0
  119. package/plugins/gemini/plugin.js +0 -0
  120. package/plugins/gemini/plugin.json +0 -0
  121. package/plugins/grok/icon.svg +0 -0
  122. package/plugins/grok/plugin.js +30 -5
  123. package/plugins/grok/plugin.json +3 -1
  124. package/plugins/groqcloud/icon.svg +0 -0
  125. package/plugins/groqcloud/plugin.js +0 -0
  126. package/plugins/groqcloud/plugin.json +0 -0
  127. package/plugins/ibmbob/icon.svg +23 -0
  128. package/plugins/ibmbob/plugin.js +61 -0
  129. package/plugins/ibmbob/plugin.json +22 -0
  130. package/plugins/jetbrains-ai-assistant/icon-color.svg +0 -0
  131. package/plugins/jetbrains-ai-assistant/icon-conflict.svg +0 -0
  132. package/plugins/jetbrains-ai-assistant/icon.svg +0 -0
  133. package/plugins/jetbrains-ai-assistant/plugin.js +0 -0
  134. package/plugins/jetbrains-ai-assistant/plugin.json +0 -0
  135. package/plugins/kilo/icon.svg +0 -0
  136. package/plugins/kilo/plugin.js +0 -0
  137. package/plugins/kilo/plugin.json +0 -0
  138. package/plugins/kimi/icon-color.svg +0 -0
  139. package/plugins/kimi/icon-conflict.svg +0 -0
  140. package/plugins/kimi/icon.svg +0 -0
  141. package/plugins/kimi/plugin.js +122 -141
  142. package/plugins/kimi/plugin.json +5 -2
  143. package/plugins/kimi-k2/icon-color.svg +0 -0
  144. package/plugins/kimi-k2/icon.svg +0 -0
  145. package/plugins/kimi-k2/plugin.js +0 -0
  146. package/plugins/kimi-k2/plugin.json +0 -0
  147. package/plugins/kiro/icon-color.svg +0 -0
  148. package/plugins/kiro/icon-conflict.svg +0 -0
  149. package/plugins/kiro/icon.svg +0 -0
  150. package/plugins/kiro/plugin.js +49 -10
  151. package/plugins/kiro/plugin.json +3 -1
  152. package/plugins/litellm/icon.svg +0 -0
  153. package/plugins/litellm/plugin.js +0 -0
  154. package/plugins/litellm/plugin.json +0 -0
  155. package/plugins/llm-proxy/icon.svg +0 -0
  156. package/plugins/llm-proxy/plugin.js +0 -0
  157. package/plugins/llm-proxy/plugin.json +0 -0
  158. package/plugins/longcat/icon.svg +4 -0
  159. package/plugins/longcat/plugin.js +182 -0
  160. package/plugins/longcat/plugin.json +23 -0
  161. package/plugins/manus/icon.svg +0 -0
  162. package/plugins/manus/plugin.js +0 -0
  163. package/plugins/manus/plugin.json +0 -0
  164. package/plugins/mimo/icon.svg +0 -0
  165. package/plugins/mimo/plugin.js +0 -0
  166. package/plugins/mimo/plugin.json +0 -0
  167. package/plugins/minimax/icon-color.svg +0 -0
  168. package/plugins/minimax/icon-conflict.svg +0 -0
  169. package/plugins/minimax/icon.svg +0 -0
  170. package/plugins/minimax/plugin.js +25 -31
  171. package/plugins/minimax/plugin.json +0 -0
  172. package/plugins/mistral/icon-color.svg +0 -0
  173. package/plugins/mistral/icon.svg +0 -0
  174. package/plugins/mistral/plugin.js +0 -0
  175. package/plugins/mistral/plugin.json +0 -0
  176. package/plugins/moonshot/icon.svg +0 -0
  177. package/plugins/moonshot/plugin.js +8 -6
  178. package/plugins/moonshot/plugin.json +0 -0
  179. package/plugins/nanogpt/icon-color.svg +0 -0
  180. package/plugins/nanogpt/icon.svg +0 -0
  181. package/plugins/nanogpt/plugin.js +0 -0
  182. package/plugins/nanogpt/plugin.json +0 -0
  183. package/plugins/neuralwatt/icon.svg +0 -0
  184. package/plugins/neuralwatt/plugin.js +0 -0
  185. package/plugins/neuralwatt/plugin.json +0 -0
  186. package/plugins/notion/icon.svg +1 -0
  187. package/plugins/notion/plugin.js +282 -0
  188. package/plugins/notion/plugin.json +29 -0
  189. package/plugins/ollama/icon.svg +0 -0
  190. package/plugins/ollama/plugin.js +257 -56
  191. package/plugins/ollama/plugin.json +6 -3
  192. package/plugins/openai-api/icon.svg +0 -0
  193. package/plugins/openai-api/plugin.js +0 -0
  194. package/plugins/openai-api/plugin.json +0 -0
  195. package/plugins/opencode/icon.svg +0 -0
  196. package/plugins/opencode/plugin.js +0 -0
  197. package/plugins/opencode/plugin.json +0 -0
  198. package/plugins/opencode-go/icon-conflict.svg +0 -0
  199. package/plugins/opencode-go/icon.svg +0 -0
  200. package/plugins/opencode-go/plugin.js +78 -10
  201. package/plugins/opencode-go/plugin.json +5 -2
  202. package/plugins/openrouter/icon.svg +0 -0
  203. package/plugins/openrouter/plugin.js +371 -114
  204. package/plugins/openrouter/plugin.json +6 -0
  205. package/plugins/perplexity/icon-color.svg +0 -0
  206. package/plugins/perplexity/icon-conflict.svg +0 -0
  207. package/plugins/perplexity/icon.svg +0 -0
  208. package/plugins/perplexity/plugin.js +0 -0
  209. package/plugins/perplexity/plugin.json +0 -0
  210. package/plugins/poe/icon.svg +0 -0
  211. package/plugins/poe/plugin.js +24 -18
  212. package/plugins/poe/plugin.json +2 -0
  213. package/plugins/qoder/icon.svg +3 -0
  214. package/plugins/qoder/plugin.js +227 -0
  215. package/plugins/qoder/plugin.json +23 -0
  216. package/plugins/qwencloud/icon.svg +4 -0
  217. package/plugins/qwencloud/plugin.js +677 -0
  218. package/plugins/qwencloud/plugin.json +29 -0
  219. package/plugins/sakana/icon.svg +5 -0
  220. package/plugins/sakana/plugin.js +156 -0
  221. package/plugins/sakana/plugin.json +23 -0
  222. package/plugins/stepfun/icon-color.svg +0 -0
  223. package/plugins/stepfun/icon.svg +0 -0
  224. package/plugins/stepfun/plugin.js +0 -0
  225. package/plugins/stepfun/plugin.json +0 -0
  226. package/plugins/sub2api/icon.svg +4 -0
  227. package/plugins/sub2api/plugin.js +191 -0
  228. package/plugins/sub2api/plugin.json +16 -0
  229. package/plugins/synthetic/icon-conflict.svg +0 -0
  230. package/plugins/synthetic/icon.svg +0 -0
  231. package/plugins/synthetic/plugin.js +0 -0
  232. package/plugins/synthetic/plugin.json +0 -0
  233. package/plugins/t3chat/icon-color.svg +0 -0
  234. package/plugins/t3chat/icon.svg +0 -0
  235. package/plugins/t3chat/plugin.js +0 -0
  236. package/plugins/t3chat/plugin.json +0 -0
  237. package/plugins/venice/icon-color.svg +0 -0
  238. package/plugins/venice/icon.svg +0 -0
  239. package/plugins/venice/plugin.js +0 -0
  240. package/plugins/venice/plugin.json +0 -0
  241. package/plugins/vertex-ai/icon-color.svg +0 -0
  242. package/plugins/vertex-ai/icon.svg +0 -0
  243. package/plugins/vertex-ai/plugin.js +0 -0
  244. package/plugins/vertex-ai/plugin.json +0 -0
  245. package/plugins/warp/icon-color.svg +0 -0
  246. package/plugins/warp/icon.svg +0 -0
  247. package/plugins/warp/plugin.js +0 -0
  248. package/plugins/warp/plugin.json +0 -0
  249. package/plugins/wayfinder/icon.svg +5 -0
  250. package/plugins/wayfinder/plugin.js +47 -0
  251. package/plugins/wayfinder/plugin.json +16 -0
  252. package/plugins/windsurf/icon-conflict.svg +0 -0
  253. package/plugins/windsurf/icon.svg +0 -0
  254. package/plugins/windsurf/plugin.js +0 -0
  255. package/plugins/windsurf/plugin.json +0 -0
  256. package/plugins/xai/icon.svg +7 -0
  257. package/plugins/xai/plugin.js +162 -0
  258. package/plugins/xai/plugin.json +28 -0
  259. package/plugins/zai/icon-conflict.svg +0 -0
  260. package/plugins/zai/icon.svg +0 -0
  261. package/plugins/zai/plugin.js +111 -161
  262. package/plugins/zai/plugin.json +4 -1
  263. package/plugins/zed/icon.svg +0 -0
  264. package/plugins/zed/plugin.js +0 -0
  265. package/plugins/zed/plugin.json +0 -0
  266. package/plugins/zenmux/icon.svg +3 -0
  267. package/plugins/zenmux/plugin.js +102 -0
  268. package/plugins/zenmux/plugin.json +23 -0
  269. package/plugins/zoommate/icon.svg +1 -0
  270. package/plugins/zoommate/plugin.js +244 -0
  271. package/plugins/zoommate/plugin.json +29 -0
  272. package/usagestat +0 -0
  273. package/usagestatd +0 -0
@@ -1,156 +1,413 @@
1
- globalThis.__usagestat_plugin = {
2
- probe(ctx) {
3
- const apiKey = readApiKey(ctx);
4
- const apiBase = readApiBase(ctx);
1
+ (function () {
2
+ var DEFAULT_BASE = "https://openrouter.ai/api/v1";
3
+ var ACTIVITY_URL = DEFAULT_BASE + "/activity";
4
+ var ACTIVITY_DAYS = 30;
5
+ var MAX_ACTIVITY_ROWS = 20000;
5
6
 
6
- const credits = requestJson(ctx, {
7
+ function clean(value) {
8
+ if (typeof value !== "string") return null;
9
+ var v = value.trim();
10
+ if (!v) return null;
11
+ if (
12
+ v.length >= 2 &&
13
+ ((v.charAt(0) === '"' && v.charAt(v.length - 1) === '"') ||
14
+ (v.charAt(0) === "'" && v.charAt(v.length - 1) === "'"))
15
+ ) {
16
+ v = v.slice(1, -1).trim();
17
+ }
18
+ return v || null;
19
+ }
20
+
21
+ function env(ctx, name) {
22
+ try {
23
+ return clean(ctx.host.env.get(name));
24
+ } catch (_) {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ function setting(ctx, names) {
30
+ var settings = ctx.provider && ctx.provider.settings ? ctx.provider.settings : {};
31
+ for (var i = 0; i < names.length; i++) {
32
+ var value = clean(settings[names[i]]);
33
+ if (value) return value;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ function readApiKey(ctx) {
39
+ return clean(ctx.provider && ctx.provider.apiKey) || env(ctx, "OPENROUTER_API_KEY");
40
+ }
41
+
42
+ function readManagementKey(ctx) {
43
+ return setting(ctx, ["managementApiKey", "managementKey", "activityApiKey"]) ||
44
+ env(ctx, "OPENROUTER_MANAGEMENT_API_KEY");
45
+ }
46
+
47
+ function readApiBase(ctx) {
48
+ var value = env(ctx, "OPENROUTER_API_BASE") || setting(ctx, ["apiBase", "baseUrl", "apiUrl"]);
49
+ var base = (value || DEFAULT_BASE).replace(/\/+$/, "");
50
+ if (/\/auth$/i.test(base)) base = base.slice(0, -5);
51
+ return ctx.host.http.validateBaseUrl(base || DEFAULT_BASE, true);
52
+ }
53
+
54
+ function requestJson(ctx, request) {
55
+ var resp;
56
+ try {
57
+ resp = ctx.host.http.request(request);
58
+ } catch (error) {
59
+ ctx.host.log.error("HTTP request failed: " + String(error));
60
+ throw "OpenRouter request failed. Check your connection.";
61
+ }
62
+
63
+ var json = null;
64
+ try {
65
+ json = resp.bodyText ? JSON.parse(resp.bodyText) : null;
66
+ } catch (_) {
67
+ throw "OpenRouter response was not valid JSON.";
68
+ }
69
+
70
+ return { resp: resp, json: json };
71
+ }
72
+
73
+ function fetchOpenRouterJson(ctx, url, key, timeoutMs) {
74
+ var result = requestJson(ctx, {
7
75
  method: "GET",
8
- url: apiBase + "/credits",
76
+ url: url,
9
77
  headers: {
10
- "Authorization": "Bearer " + apiKey,
11
- "Accept": "application/json"
78
+ Authorization: "Bearer " + key,
79
+ Accept: "application/json",
12
80
  },
13
- timeoutMs: 15000
81
+ timeoutMs: timeoutMs || 15000,
14
82
  });
15
-
16
- if (credits.resp.status === 401 || credits.resp.status === 403) {
83
+ if (result.resp.status === 401 || result.resp.status === 403) {
17
84
  throw "OpenRouter API key is invalid.";
18
85
  }
19
- if (credits.resp.status < 200 || credits.resp.status >= 300) {
20
- throw "OpenRouter credits request failed (HTTP " + credits.resp.status + ").";
86
+ if (result.resp.status < 200 || result.resp.status >= 300) {
87
+ throw "OpenRouter request failed (HTTP " + result.resp.status + ").";
88
+ }
89
+ return result.json;
90
+ }
91
+
92
+ function readNumber(value) {
93
+ if (typeof value === "number" && Number.isFinite(value)) return value;
94
+ if (typeof value === "string" && value.trim()) {
95
+ var parsed = Number(value.trim().replace(/,/g, ""));
96
+ if (Number.isFinite(parsed)) return parsed;
21
97
  }
98
+ return null;
99
+ }
100
+
101
+ function clamp(value, min, max) {
102
+ return Math.max(min, Math.min(max, value));
103
+ }
104
+
105
+ function round1(value) {
106
+ return Math.round(value * 10) / 10;
107
+ }
22
108
 
23
- const data = credits.json && credits.json.data ? credits.json.data : {};
24
- const totalCredits = readNumber(data.total_credits);
25
- const totalUsage = readNumber(data.total_usage);
109
+ function round2(value) {
110
+ return Math.round(value * 100) / 100;
111
+ }
112
+
113
+ function money(value) {
114
+ return "$" + round2(value || 0).toFixed(2);
115
+ }
116
+
117
+ function progressLine(label, used, limit, format) {
118
+ return {
119
+ type: "progress",
120
+ label: label,
121
+ used: used,
122
+ limit: limit,
123
+ format: format || { kind: "percent" },
124
+ };
125
+ }
126
+
127
+ function appendCreditsMetrics(metrics, data) {
128
+ var totalCredits = readNumber(data.total_credits);
129
+ var totalUsage = readNumber(data.total_usage);
26
130
 
27
131
  if (totalCredits === null || totalUsage === null) {
28
132
  throw "OpenRouter credits response did not include usage totals.";
29
133
  }
30
134
 
31
- const remaining = Math.max(0, totalCredits - totalUsage);
32
- const usedPercent = totalCredits > 0 ? clamp((totalUsage / totalCredits) * 100, 0, 100) : 0;
33
- const metrics = [
34
- {
35
- type: "progress",
36
- label: "Credits",
37
- used: round2(totalUsage),
38
- limit: round2(totalCredits),
39
- format: { kind: "dollars" }
40
- },
41
- {
42
- type: "text",
43
- label: "Balance",
44
- value: "$" + round2(remaining).toFixed(2)
45
- },
46
- {
47
- type: "text",
48
- label: "Used",
49
- value: "$" + round2(totalUsage).toFixed(2) + " (" + round1(usedPercent) + "%)"
135
+ var remaining = Math.max(0, totalCredits - totalUsage);
136
+ var usedPercent = totalCredits > 0 ? clamp((totalUsage / totalCredits) * 100, 0, 100) : 0;
137
+ metrics.push(progressLine("Credits", round2(totalUsage), round2(totalCredits), { kind: "dollars" }));
138
+ metrics.push({ type: "text", label: "Balance", value: money(remaining) });
139
+ metrics.push({
140
+ type: "text",
141
+ label: "Used",
142
+ value: money(totalUsage) + " (" + round1(usedPercent) + "%)",
143
+ });
144
+ }
145
+
146
+ function quotaFallbackUsage(data) {
147
+ var reset = typeof data.limit_reset === "string" ? data.limit_reset.toLowerCase() : "";
148
+ if (reset === "daily") return readNumber(data.usage_daily);
149
+ if (reset === "weekly") return readNumber(data.usage_weekly);
150
+ if (reset === "monthly") return readNumber(data.usage_monthly);
151
+ return readNumber(data.usage);
152
+ }
153
+
154
+ function appendKeyMetrics(metrics, data) {
155
+ var limit = readNumber(data.limit);
156
+ if (limit !== null && limit > 0) {
157
+ var remaining = readNumber(data.limit_remaining);
158
+ if (remaining === null) remaining = readNumber(data.limitRemaining);
159
+ var used = null;
160
+ if (remaining !== null) {
161
+ used = limit - clamp(remaining, 0, limit);
162
+ } else {
163
+ used = quotaFallbackUsage(data);
50
164
  }
51
- ];
165
+ if (used !== null && used >= 0 && Number.isFinite(used)) {
166
+ metrics.push(progressLine("Key Cap", round2(used), round2(limit), { kind: "dollars" }));
167
+ }
168
+ }
52
169
 
53
- try {
54
- const key = requestJson(ctx, {
55
- method: "GET",
56
- url: apiBase + "/key",
57
- headers: {
58
- "Authorization": "Bearer " + apiKey,
59
- "Accept": "application/json"
60
- },
61
- timeoutMs: 15000
170
+ appendSpendText(metrics, "Daily Spend", data.usage_daily, "today");
171
+ appendSpendText(metrics, "Weekly Spend", data.usage_weekly, "this week");
172
+ appendSpendText(metrics, "Monthly Spend", data.usage_monthly, "this month");
173
+
174
+ var rateLimit = data.rate_limit || data.rateLimit;
175
+ if (rateLimit && rateLimit.requests !== undefined) {
176
+ var interval = typeof rateLimit.interval === "string" ? " / " + rateLimit.interval : "";
177
+ metrics.push({
178
+ type: "text",
179
+ label: "Rate Limit",
180
+ value: String(rateLimit.requests) + " requests" + interval,
62
181
  });
63
- if (key.resp.status >= 200 && key.resp.status < 300 && key.json && key.json.data) {
64
- appendKeyMetrics(metrics, key.json.data);
182
+ }
183
+ }
184
+
185
+ function appendSpendText(metrics, label, value, subtitle) {
186
+ var n = readNumber(value);
187
+ if (n === null || n < 0) return;
188
+ metrics.push({ type: "text", label: label, value: money(n), subtitle: subtitle });
189
+ }
190
+
191
+ function parseDay(value) {
192
+ if (typeof value === "string") {
193
+ var trimmed = value.trim();
194
+ var match = trimmed.match(/^(\d{4}-\d{2}-\d{2})/);
195
+ if (match) {
196
+ var calendarMs = Date.parse(match[1] + "T00:00:00Z");
197
+ return Number.isFinite(calendarMs) && new Date(calendarMs).toISOString().slice(0, 10) === match[1] ? match[1] : null;
65
198
  }
66
- } catch (error) {
67
- ctx.host.log.warn("OpenRouter key details request failed: " + String(error));
199
+ var ms = Date.parse(trimmed);
200
+ if (Number.isFinite(ms)) return new Date(ms).toISOString().slice(0, 10);
68
201
  }
202
+ if (typeof value === "number" && Number.isFinite(value)) {
203
+ var date = new Date(value > 1000000000000 ? value : value * 1000);
204
+ return Number.isFinite(date.getTime()) ? date.toISOString().slice(0, 10) : null;
205
+ }
206
+ return null;
207
+ }
69
208
 
70
- return {
71
- displayName: "OpenRouter",
72
- source: "api",
73
- metrics
74
- };
209
+ function dayToMs(day) {
210
+ return Date.parse(day + "T00:00:00Z");
75
211
  }
76
- };
77
212
 
78
- function readApiKey(ctx) {
79
- const value = ctx.host.env.get("OPENROUTER_API_KEY");
80
- if (typeof value === "string" && value.trim()) {
81
- return value.trim();
213
+ function shortDay(day) {
214
+ return Number(day.slice(5, 7)) + "/" + Number(day.slice(8, 10));
82
215
  }
83
- throw "No OPENROUTER_API_KEY found.";
84
- }
85
216
 
86
- function readApiBase(ctx) {
87
- const value = ctx.host.env.get("OPENROUTER_API_BASE");
88
- if (typeof value === "string" && value.trim()) {
89
- return value.trim().replace(/\/+$/, "");
217
+ function latestCompletedUtcDay(ctx) {
218
+ var nowMs = Date.parse(ctx.nowIso);
219
+ if (!Number.isFinite(nowMs)) nowMs = Date.now();
220
+ var now = new Date(nowMs);
221
+ var today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());
222
+ return new Date(today - 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
90
223
  }
91
- return "https://openrouter.ai/api/v1/auth";
92
- }
93
224
 
94
- function requestJson(ctx, request) {
95
- let resp;
96
- try {
97
- resp = ctx.host.http.request(request);
98
- } catch (error) {
99
- ctx.host.log.error("HTTP request failed: " + String(error));
100
- throw "OpenRouter request failed. Check your connection.";
225
+ function activityRowCost(row, index) {
226
+ var metered = readNumber(row.usage);
227
+ if (metered === null || metered < 0) throw "OpenRouter Activity usage is invalid at row " + index + ".";
228
+ var estimated = readNumber(row.byok_usage_inference);
229
+ if (estimated === null && row.byok_usage_inference != null) throw "OpenRouter Activity BYOK usage is invalid at row " + index + ".";
230
+ if (estimated === null) estimated = 0;
231
+ if (estimated < 0) throw "OpenRouter Activity BYOK usage is invalid at row " + index + ".";
232
+ var total = metered + estimated;
233
+ if (!Number.isFinite(total)) throw "OpenRouter Activity cost total is invalid.";
234
+ return { metered: metered, estimated: estimated, total: total };
101
235
  }
102
236
 
103
- let json = null;
104
- try {
105
- json = resp.bodyText ? JSON.parse(resp.bodyText) : null;
106
- } catch {
107
- throw "OpenRouter response was not valid JSON.";
237
+ function tokenCount(value) {
238
+ if (value == null) return 0;
239
+ var n = readNumber(value);
240
+ if (!Number.isSafeInteger(n) || n < 0) throw "OpenRouter Activity token count is invalid.";
241
+ return n;
108
242
  }
109
243
 
110
- return { resp, json };
111
- }
244
+ function rowId(value) {
245
+ if (typeof value === "number" && Number.isFinite(value)) return value;
246
+ return clean(value) || "";
247
+ }
112
248
 
113
- function appendKeyMetrics(metrics, data) {
114
- const limit = readNumber(data.limit);
115
- const usage = readNumber(data.usage);
116
- if (limit !== null && usage !== null && limit > 0) {
117
- metrics.push({
118
- type: "progress",
119
- label: "Key Limit",
120
- used: round2(usage),
121
- limit: round2(limit),
122
- format: { kind: "dollars" }
249
+ function summarizeActivityPayloads(payloads, latestCompleted) {
250
+ var latestMs = dayToMs(latestCompleted);
251
+ var cutoffMs = latestMs - (ACTIVITY_DAYS - 1) * 24 * 60 * 60 * 1000;
252
+ var seen = {};
253
+ var byDay = {};
254
+ var total = 0;
255
+ var estimated = 0;
256
+ var rowsSeen = 0;
257
+ var aggregateTokens = 0;
258
+ var aggregateRequests = 0;
259
+
260
+ for (var p = 0; p < payloads.length; p++) {
261
+ var rows = payloads[p] && Array.isArray(payloads[p].data) ? payloads[p].data : null;
262
+ if (!rows) throw "OpenRouter Activity response missing data array.";
263
+ rowsSeen += rows.length;
264
+ if (rowsSeen > MAX_ACTIVITY_ROWS) throw "OpenRouter Activity returned too many rows.";
265
+ for (var i = 0; i < rows.length; i++) {
266
+ var row = rows[i];
267
+ if (!row || typeof row !== "object") throw "OpenRouter Activity row is invalid.";
268
+ var day = parseDay(row.date);
269
+ if (!day) throw "OpenRouter Activity row is missing a usable date.";
270
+ var dayMs = dayToMs(day);
271
+ if (dayMs < cutoffMs || dayMs > latestMs) continue;
272
+ var model = clean(row.model_permaslug) || clean(row.model) || "";
273
+ if (model.length > 512) throw "OpenRouter Activity model name is too long.";
274
+ var cost = activityRowCost(row, i);
275
+ var input = tokenCount(row.prompt_tokens);
276
+ var output = tokenCount(row.completion_tokens);
277
+ var combined = tokenCount(input + output);
278
+ var reasoning = tokenCount(row.reasoning_tokens);
279
+ if (reasoning > output) throw "OpenRouter Activity reasoning tokens exceed completion tokens.";
280
+ var identity = JSON.stringify([
281
+ day,
282
+ model,
283
+ rowId(row.endpoint_id),
284
+ clean(row.provider_name) || "",
285
+ clean(row.workspace_id) || "",
286
+ ]);
287
+ var signature = JSON.stringify([input, output, reasoning, tokenCount(row.requests), cost.metered, cost.estimated]);
288
+ if (seen[identity]) {
289
+ if (seen[identity] !== signature) throw "OpenRouter Activity contains conflicting duplicate rows.";
290
+ continue;
291
+ }
292
+ seen[identity] = signature;
293
+ aggregateTokens = tokenCount(aggregateTokens + combined);
294
+ aggregateRequests = tokenCount(aggregateRequests + tokenCount(row.requests));
295
+ total += cost.total;
296
+ estimated += cost.estimated;
297
+ if (!Number.isFinite(total) || !Number.isFinite(estimated)) throw "OpenRouter Activity cost total is invalid.";
298
+ var daily = byDay[day] || { date: day, costUsd: 0, inputTokens: 0, outputTokens: 0, reasoningOutputTokens: 0, totalTokens: 0 };
299
+ daily.costUsd += cost.total;
300
+ daily.inputTokens = tokenCount(daily.inputTokens + input);
301
+ // Reasoning is a subset of completion tokens; the history store sums these fields.
302
+ daily.outputTokens = tokenCount(daily.outputTokens + (output - reasoning));
303
+ daily.reasoningOutputTokens = tokenCount(daily.reasoningOutputTokens + reasoning);
304
+ daily.totalTokens = tokenCount(daily.totalTokens + combined);
305
+ byDay[day] = daily;
306
+ }
307
+ }
308
+
309
+ return { total: total, estimated: estimated, byDay: byDay };
310
+ }
311
+
312
+ function fetchActivityPayload(ctx, managementKey, date) {
313
+ var url = ACTIVITY_URL;
314
+ if (date) url += "?date=" + encodeURIComponent(date);
315
+ var result = requestJson(ctx, {
316
+ method: "GET",
317
+ url: url,
318
+ headers: {
319
+ Authorization: "Bearer " + managementKey,
320
+ Accept: "application/json",
321
+ },
322
+ timeoutMs: 3000,
123
323
  });
324
+ if (result.resp.status === 401 || result.resp.status === 403) {
325
+ throw "OpenRouter management API key is invalid.";
326
+ }
327
+ if (result.resp.status < 200 || result.resp.status >= 300) {
328
+ throw "OpenRouter Activity request failed (HTTP " + result.resp.status + ").";
329
+ }
330
+ return result.json;
124
331
  }
125
332
 
126
- const rateLimit = data.rate_limit || data.rateLimit;
127
- if (rateLimit && rateLimit.requests !== undefined) {
128
- const interval = typeof rateLimit.interval === "string" ? " / " + rateLimit.interval : "";
333
+ function appendActivityMetrics(ctx, metrics, managementKey) {
334
+ var latestCompleted = latestCompletedUtcDay(ctx);
335
+ var summary = summarizeActivityPayloads([
336
+ fetchActivityPayload(ctx, managementKey, null),
337
+ fetchActivityPayload(ctx, managementKey, latestCompleted),
338
+ ], latestCompleted);
339
+
129
340
  metrics.push({
130
341
  type: "text",
131
- label: "Rate Limit",
132
- value: String(rateLimit.requests) + " requests" + interval
342
+ label: "Cost",
343
+ value: money(summary.total),
344
+ subtitle: "Last 30 completed days (UTC)" + (summary.estimated > 0 ? "; includes " + money(summary.estimated) + " estimated BYOK inference" : ""),
133
345
  });
134
- }
135
- }
136
346
 
137
- function readNumber(value) {
138
- if (typeof value === "number" && Number.isFinite(value)) return value;
139
- if (typeof value === "string" && value.trim()) {
140
- const parsed = Number(value);
141
- if (Number.isFinite(parsed)) return parsed;
347
+ var days = Object.keys(summary.byDay).sort();
348
+ var chartPoints = [];
349
+ var dailyRows = [];
350
+ for (var i = 0; i < days.length; i++) {
351
+ var amount = summary.byDay[days[i]].costUsd;
352
+ chartPoints.push({
353
+ label: shortDay(days[i]),
354
+ value: amount,
355
+ valueLabel: money(amount),
356
+ });
357
+ dailyRows.push(summary.byDay[days[i]]);
358
+ }
359
+ if (chartPoints.length) {
360
+ metrics.push({
361
+ type: "barChart",
362
+ label: "Usage Trend",
363
+ points: chartPoints,
364
+ note: "OpenRouter Activity spend from completed UTC days." + (summary.estimated > 0 ? " Includes estimated BYOK inference." : ""),
365
+ color: "#7c3aed",
366
+ });
367
+ }
368
+
369
+ try {
370
+ if (ctx.host.usageDaily && typeof ctx.host.usageDaily.ingest === "function" && dailyRows.length) {
371
+ ctx.host.usageDaily.ingest({
372
+ displayName: "OpenRouter",
373
+ source: summary.estimated > 0 ? "openrouter_activity_estimated" : "openrouter_activity",
374
+ daily: dailyRows,
375
+ });
376
+ }
377
+ } catch (_) {}
142
378
  }
143
- return null;
144
- }
145
379
 
146
- function clamp(value, min, max) {
147
- return Math.max(min, Math.min(max, value));
148
- }
380
+ function probe(ctx) {
381
+ var apiKey = readApiKey(ctx);
382
+ if (!apiKey) throw "No OPENROUTER_API_KEY found.";
383
+ var apiBase = readApiBase(ctx);
384
+ var metrics = [];
149
385
 
150
- function round1(value) {
151
- return Math.round(value * 10) / 10;
152
- }
386
+ var credits = fetchOpenRouterJson(ctx, apiBase + "/credits", apiKey, 15000);
387
+ appendCreditsMetrics(metrics, credits && credits.data ? credits.data : {});
388
+
389
+ try {
390
+ var key = fetchOpenRouterJson(ctx, apiBase + "/key", apiKey, 1000);
391
+ if (key && key.data) appendKeyMetrics(metrics, key.data);
392
+ } catch (error) {
393
+ ctx.host.log.warn("OpenRouter key details request degraded: " + String(error));
394
+ }
395
+
396
+ var managementKey = readManagementKey(ctx);
397
+ if (managementKey) {
398
+ try {
399
+ appendActivityMetrics(ctx, metrics, managementKey);
400
+ } catch (error) {
401
+ ctx.host.log.warn("OpenRouter Activity degraded: " + String(error));
402
+ }
403
+ }
404
+
405
+ return {
406
+ displayName: "OpenRouter",
407
+ source: "api",
408
+ metrics: metrics,
409
+ };
410
+ }
153
411
 
154
- function round2(value) {
155
- return Math.round(value * 100) / 100;
156
- }
412
+ globalThis.__usagestat_plugin = { probe: probe };
413
+ })();
@@ -2,6 +2,12 @@
2
2
  "id": "openrouter",
3
3
  "name": "OpenRouter",
4
4
  "entry": "plugin.js",
5
+ "envVars": ["OPENROUTER_API_KEY", "OPENROUTER_API_BASE", "OPENROUTER_MANAGEMENT_API_KEY"],
6
+ "usageDashboardUrl": "https://openrouter.ai/activity",
7
+ "links": [
8
+ { "label": "Activity", "url": "https://openrouter.ai/activity" },
9
+ { "label": "Credits", "url": "https://openrouter.ai/settings/credits" }
10
+ ],
5
11
  "enabledByDefault": false,
6
12
  "supportedModes": ["api"],
7
13
  "autoMode": "api"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -49,16 +49,12 @@
49
49
  }
50
50
 
51
51
  function parseDate(value) {
52
- if (typeof value === "number" && Number.isFinite(value)) {
53
- return new Date(value > 1000000000000 ? value / 1000 : value * 1000);
54
- }
55
- if (typeof value === "string" && value.trim()) {
56
- var numeric = Number(value);
57
- if (Number.isFinite(numeric)) return parseDate(numeric);
58
- var ms = Date.parse(value);
59
- if (Number.isFinite(ms)) return new Date(ms);
60
- }
61
- return null;
52
+ if (typeof value !== "number" && (typeof value !== "string" || !value.trim())) return null;
53
+ var numeric = Number(value);
54
+ var date = Number.isFinite(numeric)
55
+ ? new Date(Math.abs(numeric) > 100000000000000 ? numeric / 1000 : Math.abs(numeric) > 1000000000000 ? numeric : numeric * 1000)
56
+ : new Date(value);
57
+ return Number.isFinite(date.getTime()) && date.getUTCFullYear() >= 1 && date.getUTCFullYear() <= 9999 ? date : null;
62
58
  }
63
59
 
64
60
  function dayKey(date) {
@@ -77,6 +73,7 @@
77
73
  }
78
74
 
79
75
  function parseHistoryEntry(row) {
76
+ if (!row || typeof row !== "object") return null;
80
77
  var createdAt = parseDate(row.creation_time ?? row.timestamp ?? row.created_at);
81
78
  if (!createdAt) return null;
82
79
  var points = numberValue(row.cost_points ?? row.points ?? row.point_cost) || 0;
@@ -90,10 +87,10 @@
90
87
  };
91
88
  }
92
89
 
93
- function fetchHistory(ctx, key) {
90
+ function fetchHistory(ctx, key, nowMs) {
94
91
  var entries = [];
95
92
  var cursor = null;
96
- var cutoff = Date.now() - 30 * 24 * 3600 * 1000;
93
+ var cutoff = nowMs - 30 * 24 * 3600 * 1000;
97
94
  for (var page = 0; page < 5; page++) {
98
95
  var url = HISTORY_URL + "?limit=100";
99
96
  if (cursor) url += "&starting_after=" + encodeURIComponent(cursor);
@@ -111,7 +108,7 @@
111
108
  var lastEntry = entries[entries.length - 1];
112
109
  if (!cursor || (lastEntry && lastEntry.createdAt.getTime() < cutoff)) break;
113
110
  }
114
- return entries.filter(function (entry) { return entry.createdAt.getTime() >= cutoff; });
111
+ return entries.filter(function (entry) { return entry.createdAt.getTime() >= cutoff && entry.createdAt.getTime() <= nowMs; });
115
112
  }
116
113
 
117
114
  function summarizeHistory(entries) {
@@ -142,12 +139,19 @@
142
139
  return { byDay: byDay, days: days, topModel: topModel, totalPoints: totalPoints, totalCost: hasCost ? totalCost : null, requests: entries.length };
143
140
  }
144
141
 
145
- function appendHistoryLines(lines, ctx, entries) {
146
- if (!entries.length) return;
147
- var summary = summarizeHistory(entries);
142
+ function appendSummary(lines, ctx, label, summary) {
148
143
  var value = fmtNumber(summary.totalPoints) + " points";
149
144
  if (summary.totalCost != null) value += " · $" + summary.totalCost.toFixed(2);
150
- lines.push(ctx.line.text({ label: "Last 30 Days", value: value + " · " + summary.requests + " requests" }));
145
+ lines.push(ctx.line.text({ label: label, value: value + " · " + summary.requests + " requests" }));
146
+ }
147
+
148
+ function appendHistoryLines(lines, ctx, entries, nowMs) {
149
+ if (!entries.length) return;
150
+ var summary = summarizeHistory(entries);
151
+ var today = dayKey(new Date(nowMs));
152
+ appendSummary(lines, ctx, "Today", summarizeHistory(entries.filter(function (entry) { return dayKey(entry.createdAt) === today; })));
153
+ appendSummary(lines, ctx, "Last 7 Days", summarizeHistory(entries.filter(function (entry) { return entry.createdAt.getTime() >= nowMs - 7 * 86400000; })));
154
+ appendSummary(lines, ctx, "Last 30 Days", summary);
151
155
  if (summary.topModel) lines.push(ctx.line.text({ label: "Top Model", value: summary.topModel }));
152
156
 
153
157
  var points = summary.days.map(function (day) {
@@ -178,7 +182,9 @@
178
182
  }
179
183
 
180
184
  try {
181
- appendHistoryLines(lines, ctx, fetchHistory(ctx, key));
185
+ var nowMs = Date.parse(ctx.nowIso);
186
+ if (!Number.isFinite(nowMs)) nowMs = Date.now();
187
+ appendHistoryLines(lines, ctx, fetchHistory(ctx, key, nowMs), nowMs);
182
188
  } catch (e) {
183
189
  ctx.host.log.warn("poe points_history failed: " + String(e));
184
190
  }
@@ -13,6 +13,8 @@
13
13
  "autoMode": "api",
14
14
  "lines": [
15
15
  { "type": "text", "label": "Balance", "scope": "overview", "primaryOrder": 1 },
16
+ { "type": "text", "label": "Today", "scope": "detail" },
17
+ { "type": "text", "label": "Last 7 Days", "scope": "detail" },
16
18
  { "type": "text", "label": "Last 30 Days", "scope": "detail" },
17
19
  { "type": "barChart", "label": "Point History", "scope": "detail" }
18
20
  ]
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
2
+ <path fill="black" d="M12 2.5c5.25 0 9.5 4.25 9.5 9.5s-4.25 9.5-9.5 9.5S2.5 17.25 2.5 12 6.75 2.5 12 2.5Zm0 3.2A6.3 6.3 0 1 0 12 18.3 6.3 6.3 0 0 0 12 5.7Zm0 2.8a3.5 3.5 0 0 1 3.5 3.5c0 .75-.23 1.44-.62 2.01l1.18 1.18-1.98 1.98-1.18-1.18A3.5 3.5 0 1 1 12 8.5Zm0 2.5a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"/>
3
+ </svg>