@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
@@ -20,13 +20,24 @@
20
20
  const MIN_USAGE_FETCH_INTERVAL_MS = 5 * 60 * 1000 // never poll more than once per 5 min
21
21
  const DEFAULT_RATE_LIMIT_BACKOFF_MS = 5 * 60 * 1000 // fallback when no Retry-After header
22
22
  const LIVE_USAGE_CACHE_FILE = "live-usage-cache.json"
23
- const LIVE_USAGE_CACHE_VERSION = 1
23
+ const LIVE_USAGE_CACHE_VERSION = 2
24
24
  const LIVE_USAGE_CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1000
25
25
  let rateLimitedUntilMs = 0 // epoch ms; 0 = not rate-limited
26
26
  let lastUsageFetchMs = 0 // epoch ms of the most-recent OAuth API attempt
27
27
  let cachedUsageData = null // last successful OAuth API response body (parsed JSON)
28
28
  let lastWebUsageFetchMs = 0 // epoch ms of the most-recent Web API attempt
29
29
  let cachedWebUsageData = null // last successful Web API response body (parsed JSON)
30
+ let activeCacheIdentity = null
31
+
32
+ function bindUsageCache(ctx, creds, sessionKey) {
33
+ const identity = ctx.host.crypto.sha256(JSON.stringify([
34
+ creds && creds.oauth && creds.oauth.accessToken || "", sessionKey || "",
35
+ ctx.provider && ctx.provider.workspaceId || "",
36
+ ]))
37
+ if (activeCacheIdentity !== identity) _resetState()
38
+ activeCacheIdentity = identity
39
+ ctx.claudeCacheIdentity = identity
40
+ }
30
41
 
31
42
  function utf8DecodeBytes(bytes) {
32
43
  // Prefer native TextDecoder when available (QuickJS may not expose it).
@@ -194,7 +205,7 @@
194
205
  if (!path || !ctx.host.fs.exists(path)) return null
195
206
  try {
196
207
  const parsed = ctx.util.tryParseJson(ctx.host.fs.readText(path))
197
- if (!parsed || typeof parsed !== "object") return null
208
+ if (!parsed || typeof parsed !== "object" || parsed.accountHash !== ctx.claudeCacheIdentity) return null
198
209
  return parsed
199
210
  } catch {
200
211
  return null
@@ -210,6 +221,7 @@
210
221
  cache[key] = updates[key]
211
222
  }
212
223
  cache.version = LIVE_USAGE_CACHE_VERSION
224
+ cache.accountHash = ctx.claudeCacheIdentity
213
225
  cache.updatedAtMs = Date.now()
214
226
  ctx.host.fs.writeText(path, JSON.stringify(cache, null, 2))
215
227
  } catch (e) {
@@ -569,6 +581,7 @@
569
581
  // Persist updated credentials back to the same source we read from.
570
582
  fullData.claudeAiOauth = oauth
571
583
  saveCredentials(ctx, source, creds.serviceName, fullData)
584
+ bindUsageCache(ctx, creds, getSessionKey(ctx))
572
585
 
573
586
  ctx.host.log.info("refresh succeeded, new token expires in " + (body.expires_in || "unknown") + "s")
574
587
  return newAccessToken
@@ -603,8 +616,8 @@
603
616
  if (!str) return null
604
617
  // Retry-After can be a delay-seconds or HTTP-date (RFC 7231).
605
618
  // 0 means "retry immediately" — return 0 as a valid value.
606
- const seconds = parseInt(str, 10)
607
- if (Number.isFinite(seconds) && seconds >= 0) return seconds
619
+ const seconds = /^\d+$/.test(str) ? Number(str) : NaN
620
+ if (Number.isSafeInteger(seconds)) return seconds
608
621
  const dateMs = Date.parse(str)
609
622
  if (Number.isFinite(dateMs)) {
610
623
  const delay = Math.ceil((dateMs - Date.now()) / 1000)
@@ -1254,17 +1267,21 @@
1254
1267
  const liveCache = readLiveUsageCache(ctx)
1255
1268
  const cachedLastWebUsageFetchMs = Number(liveCache && liveCache.lastWebUsageFetchMs) || 0
1256
1269
  const effectiveLastWebUsageFetchMs = Math.max(lastWebUsageFetchMs, cachedLastWebUsageFetchMs)
1257
- if (nowMs - effectiveLastWebUsageFetchMs < MIN_USAGE_FETCH_INTERVAL_MS) {
1270
+ const cached = cachedWebUsageData || cachedLiveUsageData(liveCache, nowMs)
1271
+ if (cached && nowMs - effectiveLastWebUsageFetchMs < MIN_USAGE_FETCH_INTERVAL_MS) {
1258
1272
  lastWebUsageFetchMs = effectiveLastWebUsageFetchMs
1259
1273
  ctx.host.log.info(
1260
1274
  "web usage fetch skipped: last fetch was " +
1261
1275
  Math.round((nowMs - effectiveLastWebUsageFetchMs) / 1000) + "s ago"
1262
1276
  )
1263
- return { data: cachedWebUsageData || cachedLiveUsageData(liveCache, nowMs), plan: null }
1277
+ return { data: cached, plan: null }
1264
1278
  }
1265
1279
 
1266
1280
  lastWebUsageFetchMs = nowMs
1267
1281
  const result = fetchWebUsage(ctx, sessionKey)
1282
+ const validLines = []
1283
+ addUsageWindowLines(ctx, result.usageData, validLines)
1284
+ if (!validLines.length) throw "Web usage response invalid. Try again later."
1268
1285
  cachedWebUsageData = result.usageData
1269
1286
  writeLiveUsageCache(ctx, {
1270
1287
  usageData: result.usageData,
@@ -1282,7 +1299,9 @@
1282
1299
  const t = (tier || "").toLowerCase()
1283
1300
  if (t === "free") return "Claude Free"
1284
1301
  if (t === "pro" || t === "claude_pro") return "Claude Pro"
1285
- if (t === "max" || t === "claude_max_5" || t === "claude_max_20") return "Claude Max"
1302
+ if (t === "claude_max_5" || t === "claude_max_5x") return "Claude Max 5x"
1303
+ if (t === "claude_max_20" || t === "claude_max_20x") return "Claude Max 20x"
1304
+ if (t === "max") return "Claude Max"
1286
1305
  if (t === "team") return "Claude Team"
1287
1306
  if (t === "enterprise") return "Claude Enterprise"
1288
1307
  return "Claude (" + tier + ")"
@@ -1299,6 +1318,8 @@
1299
1318
  }
1300
1319
 
1301
1320
  function moneyMajorUnits(value) {
1321
+ if (typeof value !== "number" && typeof value !== "string") return null
1322
+ if (typeof value === "string" && !value.trim()) return null
1302
1323
  const n = Number(value)
1303
1324
  if (!Number.isFinite(n)) return null
1304
1325
  // Claude usage APIs return Extra usage amounts in minor units (cents).
@@ -1333,9 +1354,18 @@
1333
1354
  return months[d.getUTCMonth()] + " " + String(d.getUTCDate())
1334
1355
  }
1335
1356
 
1357
+ function currentUsageWindow(data, kind, fallback) {
1358
+ const entries = Array.isArray(data.limits) ? data.limits.filter((entry) =>
1359
+ entry && entry.kind === kind && Number.isFinite(entry.percent) &&
1360
+ !(entry.scope && (entry.scope.model || entry.scope.surface))) : []
1361
+ const entry = entries.find((item) => item.is_active === true) || entries[0]
1362
+ return entry ? { utilization: entry.percent, resets_at: entry.resets_at } : fallback
1363
+ }
1364
+
1336
1365
  function addUsageWindowLines(ctx, data, lines) {
1337
- const fiveHour = data.five_hour
1338
- if (fiveHour && typeof fiveHour.utilization === "number") {
1366
+ const settings = ctx.provider && ctx.provider.settings || {}
1367
+ const fiveHour = currentUsageWindow(data, "session", data.five_hour)
1368
+ if (fiveHour && Number.isFinite(fiveHour.utilization)) {
1339
1369
  lines.push(ctx.line.progress({
1340
1370
  label: "Session",
1341
1371
  used: fiveHour.utilization,
@@ -1346,8 +1376,8 @@
1346
1376
  }))
1347
1377
  }
1348
1378
 
1349
- const sevenDay = data.seven_day
1350
- if (sevenDay && typeof sevenDay.utilization === "number") {
1379
+ const sevenDay = currentUsageWindow(data, "weekly_all", data.seven_day)
1380
+ if (sevenDay && Number.isFinite(sevenDay.utilization)) {
1351
1381
  lines.push(ctx.line.progress({
1352
1382
  label: "Weekly",
1353
1383
  used: sevenDay.utilization,
@@ -1359,7 +1389,7 @@
1359
1389
  }
1360
1390
 
1361
1391
  const sevenDaySonnet = data.seven_day_sonnet
1362
- if (sevenDaySonnet && typeof sevenDaySonnet.utilization === "number") {
1392
+ if (settings.showModelQuotas === true && sevenDaySonnet && Number.isFinite(sevenDaySonnet.utilization)) {
1363
1393
  lines.push(ctx.line.progress({
1364
1394
  label: "Sonnet",
1365
1395
  used: sevenDaySonnet.utilization,
@@ -1371,7 +1401,7 @@
1371
1401
  }
1372
1402
 
1373
1403
  const sevenDayOpus = data.seven_day_opus
1374
- if (sevenDayOpus && typeof sevenDayOpus.utilization === "number") {
1404
+ if (settings.showModelQuotas === true && sevenDayOpus && Number.isFinite(sevenDayOpus.utilization)) {
1375
1405
  lines.push(ctx.line.progress({
1376
1406
  label: "Opus",
1377
1407
  used: sevenDayOpus.utilization,
@@ -1382,13 +1412,23 @@
1382
1412
  }))
1383
1413
  }
1384
1414
 
1415
+ if (settings.showModelQuotas === true && Array.isArray(data.limits)) {
1416
+ for (const entry of data.limits) {
1417
+ if (!entry || entry.kind !== "weekly_scoped" || !Number.isFinite(entry.percent)) continue
1418
+ const model = entry.scope && entry.scope.model && entry.scope.model.display_name
1419
+ if (typeof model !== "string" || !model.trim() || lines.some((line) => line.label === model.trim())) continue
1420
+ lines.push(ctx.line.progress({ label: model.trim(), used: entry.percent, limit: 100,
1421
+ format: { kind: "percent" }, resetsAt: ctx.util.toIso(entry.resets_at), periodDurationMs: 7 * 24 * 60 * 60 * 1000 }))
1422
+ }
1423
+ }
1424
+
1385
1425
  // Routines: check canonical name first, then legacy aliases
1386
1426
  const routinesWindow = pickUsageWindow(data, [
1387
1427
  "seven_day_routines",
1388
1428
  "seven_day_cowork",
1389
1429
  "seven_day_claude_routines",
1390
1430
  ])
1391
- if (routinesWindow && typeof routinesWindow.utilization === "number") {
1431
+ if (settings.hideDailyRoutines !== true && routinesWindow && Number.isFinite(routinesWindow.utilization)) {
1392
1432
  lines.push(ctx.line.progress({
1393
1433
  label: "Routines",
1394
1434
  used: routinesWindow.utilization,
@@ -1472,15 +1512,13 @@
1472
1512
  ctx.host.log.error("api mode requested but no Claude Admin API key found")
1473
1513
  throw "Claude API usage needs an Anthropic Admin API key. Set ANTHROPIC_ADMIN_KEY."
1474
1514
  }
1475
- if (!wantsLocal && !wantsApi && !hasOAuth && !sessionKey && !adminApiKey) {
1476
- ctx.host.log.error("probe failed: no credentials or session key")
1477
- throw "Not logged in. Run `claude` to authenticate, set CLAUDE_AI_SESSION_KEY, or set ANTHROPIC_ADMIN_KEY."
1478
- }
1479
-
1515
+ const noLiveCredentials = !hasOAuth && !sessionKey && !adminApiKey
1516
+ bindUsageCache(ctx, creds, sessionKey)
1480
1517
  const nowMs = Date.now()
1481
1518
  const liveCache = readLiveUsageCache(ctx)
1482
1519
  const homePath = getClaudeHomeOverride(ctx)
1483
1520
  let data = null
1521
+ let quotaSource = null
1484
1522
  let lines = []
1485
1523
  let plan = null
1486
1524
  let rateLimited = false
@@ -1495,6 +1533,7 @@
1495
1533
  } else if (wantsApi) {
1496
1534
  ctx.host.log.info("api mode requested; skipping live quota fetch")
1497
1535
  } else if (hasOAuth && !wantsWeb) {
1536
+ quotaSource = "oauth"
1498
1537
  // ── OAuth mode ───────────────────────────────────────────────────────
1499
1538
  let accessToken = creds.oauth.accessToken
1500
1539
  const canFetchLiveUsage = hasProfileScope(creds)
@@ -1521,7 +1560,7 @@
1521
1560
 
1522
1561
  const cachedLastUsageFetchMs = Number(liveCache && liveCache.lastUsageFetchMs) || 0
1523
1562
  const effectiveLastUsageFetchMs = Math.max(lastUsageFetchMs, cachedLastUsageFetchMs)
1524
- if (!wasRateLimited && nowMs - effectiveLastUsageFetchMs < MIN_USAGE_FETCH_INTERVAL_MS) {
1563
+ if (cachedData && !wasRateLimited && nowMs - effectiveLastUsageFetchMs < MIN_USAGE_FETCH_INTERVAL_MS) {
1525
1564
  // Polled too recently in normal operation — reuse last cached response.
1526
1565
  lastUsageFetchMs = effectiveLastUsageFetchMs
1527
1566
  data = cachedData
@@ -1600,7 +1639,9 @@
1600
1639
  } else {
1601
1640
  ctx.host.log.info("usage fetch succeeded")
1602
1641
  data = ctx.util.tryParseJson(resp.bodyText)
1603
- if (data === null) {
1642
+ const validLines = []
1643
+ if (data && typeof data === "object") addUsageWindowLines(ctx, data, validLines)
1644
+ if (!validLines.length) {
1604
1645
  throw "Usage response invalid. Try again later."
1605
1646
  }
1606
1647
  cachedUsageData = data
@@ -1622,11 +1663,12 @@
1622
1663
  // ── Web mode ─────────────────────────────────────────────────────────
1623
1664
  const webResult = loadWebUsageData(ctx, sessionKey, nowMs)
1624
1665
  data = webResult.data
1666
+ quotaSource = "web"
1625
1667
  if (webResult.plan) {
1626
1668
  plan = webResult.plan
1627
1669
  }
1628
1670
  } else {
1629
- ctx.host.log.info("auto mode using Claude Admin API history only")
1671
+ ctx.host.log.info("auto mode using available Claude usage history")
1630
1672
  }
1631
1673
 
1632
1674
  if (rateLimited && wantsAuto && sessionKey) {
@@ -1635,6 +1677,7 @@
1635
1677
  const webResult = loadWebUsageData(ctx, sessionKey, nowMs)
1636
1678
  if (webResult.data) {
1637
1679
  data = webResult.data
1680
+ quotaSource = "web"
1638
1681
  rateLimited = false
1639
1682
  retryAfterSeconds = null
1640
1683
  if (webResult.plan) {
@@ -1703,10 +1746,14 @@
1703
1746
  : "Live usage rate limited — data may be stale"
1704
1747
  lines.push(ctx.line.text({ label: "Note", value: noteText }))
1705
1748
  } else if (lines.length === 0) {
1749
+ if (noLiveCredentials && wantsAuto) throw "Not logged in and no local Claude usage found. Run `claude` to authenticate."
1706
1750
  lines.push(ctx.line.badge({ label: "Status", text: "No usage data", color: "#a3a3a3" }))
1707
1751
  }
1708
1752
 
1709
- return { plan: plan, lines: lines }
1753
+ const finalCache = readLiveUsageCache(ctx)
1754
+ const fetchedAt = data && finalCache && ctx.util.toIso(finalCache.usageFetchedAtMs)
1755
+ return { plan: plan, lines: lines, fetchedAt: fetchedAt || ctx.nowIso,
1756
+ source: data ? (rateLimited ? "cached" : quotaSource) : wantsApi ? "api" : "local" }
1710
1757
  }
1711
1758
 
1712
1759
  // _resetState is a testing hook — resets module-scope rate-limit state between tests.
File without changes
@@ -0,0 +1,7 @@
1
+ <svg viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="8" cy="7" r="3" fill="currentColor" stroke="none"/>
3
+ <circle cx="24" cy="8" r="3" fill="currentColor" stroke="none"/>
4
+ <circle cx="24" cy="24" r="3" fill="currentColor" stroke="none"/>
5
+ <path d="M8 10v6c0 4.4 3.6 8 8 8h5"/>
6
+ <path d="M11 8h10"/>
7
+ </svg>
@@ -0,0 +1,45 @@
1
+ (function () {
2
+ function clean(value) { return typeof value === "string" && value.trim() ? value.trim() : null }
3
+ function integer(value) {
4
+ if (!Number.isSafeInteger(value) || value < 0) throw "Invalid ClawRouter usage counters."
5
+ return value
6
+ }
7
+ function money(value) { return "$" + (integer(value) / 1000000).toFixed(6) }
8
+ function probe(ctx) {
9
+ const provider = ctx.provider || {}
10
+ const settings = provider.settings || {}
11
+ const key = clean(provider.apiKey) || clean(ctx.host.env.get("CLAWROUTER_API_KEY"))
12
+ if (!key) throw "Set CLAWROUTER_API_KEY or the ClawRouter provider API key."
13
+ let base = ctx.host.http.validateBaseUrl(clean(settings.baseUrl) || clean(ctx.host.env.get("CLAWROUTER_BASE_URL")) || "https://clawrouter.openclaw.ai", false)
14
+ if (!base.endsWith("/v1")) base += "/v1"
15
+ const result = ctx.util.requestJson({ method: "GET", url: base + "/usage",
16
+ headers: { Authorization: "Bearer " + key, Accept: "application/json" }, timeoutMs: 10000 })
17
+ if (ctx.util.isAuthStatus(result.resp.status)) throw "ClawRouter rejected the API key."
18
+ if (result.resp.status < 200 || result.resp.status >= 300) throw "ClawRouter returned HTTP " + result.resp.status + "."
19
+ const data = result.json
20
+ if (!data || !data.budget || !data.usage || !data.usage.summary || !Array.isArray(data.usage.providers)) throw "Invalid ClawRouter usage response."
21
+ const budget = data.budget
22
+ const summary = data.usage.summary
23
+ const lines = [
24
+ ctx.line.text({ label: "Requests", value: String(integer(summary.requestCount)),
25
+ subtitle: integer(summary.successCount) + " succeeded; " + integer(summary.errorCount) + " failed" }),
26
+ ctx.line.text({ label: "Tokens", value: String(integer(summary.totalTokens)),
27
+ subtitle: integer(summary.inputTokens) + " input; " + integer(summary.outputTokens) + " output" }),
28
+ ctx.line.text({ label: "Actual cost", value: money(summary.actualCostMicros) }),
29
+ ]
30
+ if (budget.spentMicros !== null && budget.spentMicros !== undefined && budget.limitMicros !== null && budget.limitMicros !== undefined) {
31
+ const month = typeof budget.windowKey === "string" && budget.windowKey.match(/(\d{4})-(0[1-9]|1[0-2])$/)
32
+ lines.unshift(ctx.line.progress({ label: "Monthly budget", used: integer(budget.spentMicros) / 1000000,
33
+ limit: integer(budget.limitMicros) / 1000000, format: { kind: "dollars" },
34
+ resetsAt: month ? new Date(Date.UTC(Number(month[1]), Number(month[2]), 1)).toISOString() : undefined,
35
+ periodDurationMs: 30 * 24 * 60 * 60 * 1000 }))
36
+ }
37
+ const routes = data.usage.providers.map((row) => {
38
+ if (!row || !clean(row.provider)) throw "Invalid ClawRouter routed provider."
39
+ return { label: row.provider, value: integer(row.actualCostMicros) / 1000000, valueLabel: money(row.actualCostMicros) }
40
+ }).sort((a, b) => b.value - a.value)
41
+ if (routes.length) lines.push(ctx.line.barChart({ label: "Routed provider costs", points: routes.slice(0, 20) }))
42
+ return { displayName: "ClawRouter", source: "api", plan: budget.configured ? "Monthly budget" : "Unmetered", lines }
43
+ }
44
+ globalThis.__openusage_plugin = { id: "clawrouter", probe }
45
+ })()
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "clawrouter",
4
+ "name": "ClawRouter",
5
+ "version": "0.1.0",
6
+ "entry": "plugin.js",
7
+ "enabledByDefault": false,
8
+ "envVars": [
9
+ "CLAWROUTER_API_KEY",
10
+ "CLAWROUTER_BASE_URL"
11
+ ],
12
+ "supportedModes": [
13
+ "api"
14
+ ],
15
+ "autoMode": "api",
16
+ "links": [
17
+ {
18
+ "label": "Dashboard",
19
+ "url": "https://clawrouter.openclaw.ai"
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 466.73 487.04">
2
+ <!-- Official Cline bot icon: https://cline.bot/brand -->
3
+ <path d="M463.6,275.08l-29.26-58.75v-33.83c0-56.08-45.01-101.5-100.53-101.5h-50.01c3.62-7.43,5.61-15.79,5.61-24.61,0-31.17-25.08-56.39-56.07-56.39s-56.07,25.22-56.07,56.39c0,8.82,1.99,17.17,5.61,24.61h-50.01c-55.51,0-100.52,45.42-100.52,101.5v33.83l-29.87,58.59c-3.01,5.9-3.01,12.92,0,18.81l29.87,57.93v33.83c0,56.08,45.01,101.5,100.52,101.5h200.95c55.51,0,100.53-45.42,100.53-101.5v-33.83l29.21-58.13c2.9-5.79,2.9-12.61.05-18.46ZM202.75,322.96c0,25.48-20.54,46.14-45.88,46.14s-45.88-20.66-45.88-46.14v-82.02c0-25.48,20.54-46.14,45.88-46.14s45.88,20.66,45.88,46.14v82.02ZM350.58,322.96c0,25.48-20.54,46.14-45.88,46.14s-45.88-20.66-45.88-46.14v-82.02c0-25.48,20.54-46.14,45.88-46.14s45.88,20.66,45.88,46.14v82.02Z"/>
4
+ </svg>
@@ -0,0 +1,71 @@
1
+ (function () {
2
+ var USAGE_URL = "https://api.cline.bot/api/v1/users/me/plan/usage-limits";
3
+
4
+ function clean(value) {
5
+ return typeof value === "string" && value.trim() ? value.trim() : null;
6
+ }
7
+
8
+ function env(ctx, name) {
9
+ try {
10
+ return clean(ctx.host.env.get(name));
11
+ } catch (_) {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ function apiKey(ctx) {
17
+ return clean(ctx.provider && ctx.provider.apiKey) || env(ctx, "CLINEPASS_API_KEY") || env(ctx, "CLINE_API_KEY");
18
+ }
19
+
20
+ function windowFor(ctx, entry) {
21
+ var type = String(entry && entry.type || "");
22
+ var minutes = type === "five_hour" ? 5 * 60 : type === "weekly" ? 7 * 24 * 60 : type === "monthly" ? 30 * 24 * 60 : null;
23
+ if (!minutes) return null;
24
+ var label = type === "five_hour" ? "5-hour" : type === "weekly" ? "Weekly" : "Monthly";
25
+ var raw = entry.percentUsed;
26
+ var percent = Number(raw);
27
+ if ((typeof raw !== "number" && typeof raw !== "string") || String(raw).trim() === "" || !Number.isFinite(percent) || percent < 0) throw "Invalid ClinePass usage percentage.";
28
+ var opts = {
29
+ label: label,
30
+ used: Math.max(0, Math.min(100, percent)),
31
+ limit: 100,
32
+ format: { kind: "percent" },
33
+ periodDurationMs: minutes * 60 * 1000,
34
+ };
35
+ var iso = ctx.util.toIso(entry.resetsAt);
36
+ if (iso) opts.resetsAt = iso;
37
+ return ctx.line.progress(opts);
38
+ }
39
+
40
+ function probe(ctx) {
41
+ var key = apiKey(ctx);
42
+ if (!key) throw "ClinePass API key not found. Set CLINEPASS_API_KEY, CLINE_API_KEY, or provider apiKey.";
43
+
44
+ var result = ctx.util.requestJson({
45
+ method: "GET",
46
+ url: USAGE_URL,
47
+ headers: { Authorization: "Bearer " + key, Accept: "application/json" },
48
+ timeoutMs: 15000,
49
+ });
50
+ if (ctx.util.isAuthStatus(result.resp.status)) throw "ClinePass API key invalid or expired.";
51
+ if (result.resp.status < 200 || result.resp.status >= 300) throw "ClinePass API error: HTTP " + result.resp.status + ".";
52
+ if (!result.json || !result.json.success || !result.json.data || !Array.isArray(result.json.data.limits)) {
53
+ throw "Failed to parse ClinePass usage.";
54
+ }
55
+
56
+ var ordered = ["five_hour", "weekly", "monthly"];
57
+ var metrics = [];
58
+ for (var i = 0; i < ordered.length; i++) {
59
+ for (var j = 0; j < result.json.data.limits.length; j++) {
60
+ if (result.json.data.limits[j] && result.json.data.limits[j].type === ordered[i]) {
61
+ var line = windowFor(ctx, result.json.data.limits[j]);
62
+ if (line) metrics.push(line);
63
+ }
64
+ }
65
+ }
66
+ if (!metrics.length) throw "ClinePass response missing five_hour window.";
67
+ return { displayName: "ClinePass", source: "api", plan: "API key", lines: metrics };
68
+ }
69
+
70
+ globalThis.__openusage_plugin = { id: "clinepass", probe: probe };
71
+ })();
@@ -0,0 +1,23 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "clinepass",
4
+ "name": "ClinePass",
5
+ "version": "0.1.0",
6
+ "entry": "plugin.js",
7
+ "enabledByDefault": false,
8
+ "supportedModes": [
9
+ "api"
10
+ ],
11
+ "autoMode": "api",
12
+ "usageDashboardUrl": "https://app.cline.bot/dashboard/subscription?personal=true",
13
+ "links": [
14
+ {
15
+ "label": "Dashboard",
16
+ "url": "https://app.cline.bot/dashboard/subscription?personal=true"
17
+ }
18
+ ],
19
+ "envVars": [
20
+ "CLINEPASS_API_KEY",
21
+ "CLINE_API_KEY"
22
+ ]
23
+ }