@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.
- package/LICENSE +0 -0
- package/native-manifest.json +365 -77
- package/package.json +49 -1
- package/plugins/UPSTREAM-LICENSES.md +109 -0
- package/plugins/abacus-ai/icon.svg +0 -0
- package/plugins/abacus-ai/plugin.js +0 -0
- package/plugins/abacus-ai/plugin.json +0 -0
- package/plugins/aiand/icon.svg +6 -0
- package/plugins/aiand/plugin.js +108 -0
- package/plugins/aiand/plugin.json +22 -0
- package/plugins/alibaba/icon-color.svg +0 -0
- package/plugins/alibaba/icon.svg +0 -0
- package/plugins/alibaba/plugin.js +0 -0
- package/plugins/alibaba/plugin.json +0 -0
- package/plugins/alibaba-token-plan/icon-color.svg +0 -0
- package/plugins/alibaba-token-plan/icon.svg +0 -0
- package/plugins/alibaba-token-plan/plugin.js +0 -0
- package/plugins/alibaba-token-plan/plugin.json +0 -0
- package/plugins/amp/icon-color.svg +0 -0
- package/plugins/amp/icon-conflict.svg +0 -0
- package/plugins/amp/icon.svg +0 -0
- package/plugins/amp/plugin.js +0 -0
- package/plugins/amp/plugin.json +0 -0
- package/plugins/antigravity/icon-color.svg +0 -0
- package/plugins/antigravity/icon-conflict.svg +0 -0
- package/plugins/antigravity/icon.svg +0 -0
- package/plugins/antigravity/plugin.js +0 -0
- package/plugins/antigravity/plugin.json +0 -0
- package/plugins/antigravity-cli/icon.svg +0 -0
- package/plugins/antigravity-cli/plugin.js +0 -0
- package/plugins/antigravity-cli/plugin.json +0 -0
- package/plugins/antigravity-ide/icon.svg +0 -0
- package/plugins/antigravity-ide/plugin.js +0 -0
- package/plugins/antigravity-ide/plugin.json +0 -0
- package/plugins/augment/icon.svg +0 -0
- package/plugins/augment/plugin.js +0 -0
- package/plugins/augment/plugin.json +0 -0
- package/plugins/aws-bedrock/icon-color.svg +0 -0
- package/plugins/aws-bedrock/icon.svg +0 -0
- package/plugins/aws-bedrock/plugin.js +0 -0
- package/plugins/aws-bedrock/plugin.json +0 -0
- package/plugins/azure-openai/icon-color.svg +0 -0
- package/plugins/azure-openai/icon.svg +0 -0
- package/plugins/azure-openai/plugin.js +0 -0
- package/plugins/azure-openai/plugin.json +0 -0
- package/plugins/chutes/icon.svg +0 -0
- package/plugins/chutes/plugin.js +0 -0
- package/plugins/chutes/plugin.json +0 -0
- package/plugins/claude/icon-color.svg +0 -0
- package/plugins/claude/icon.svg +0 -0
- package/plugins/claude/plugin.js +70 -23
- package/plugins/claude/plugin.json +0 -0
- package/plugins/clawrouter/icon.svg +7 -0
- package/plugins/clawrouter/plugin.js +45 -0
- package/plugins/clawrouter/plugin.json +22 -0
- package/plugins/clinepass/icon.svg +4 -0
- package/plugins/clinepass/plugin.js +71 -0
- package/plugins/clinepass/plugin.json +23 -0
- package/plugins/codebuddy/plugin.js +331 -0
- package/plugins/codebuddy/plugin.json +30 -0
- package/plugins/codebuff/icon.svg +0 -0
- package/plugins/codebuff/plugin.js +0 -0
- package/plugins/codebuff/plugin.json +0 -0
- package/plugins/codex/icon-color.svg +0 -0
- package/plugins/codex/icon.svg +0 -0
- package/plugins/codex/plugin.js +7 -2
- package/plugins/codex/plugin.json +0 -0
- package/plugins/command-code/icon.svg +0 -0
- package/plugins/command-code/plugin.js +7 -0
- package/plugins/command-code/plugin.json +0 -0
- package/plugins/copilot/icon-color.svg +0 -0
- package/plugins/copilot/icon.svg +0 -0
- package/plugins/copilot/plugin.js +61 -9
- package/plugins/copilot/plugin.json +4 -1
- package/plugins/crof/icon.svg +0 -0
- package/plugins/crof/plugin.js +0 -0
- package/plugins/crof/plugin.json +0 -0
- package/plugins/crofai/icon.svg +0 -0
- package/plugins/crofai/plugin.js +0 -0
- package/plugins/crofai/plugin.json +0 -0
- package/plugins/cursor/icon-conflict.svg +0 -0
- package/plugins/cursor/icon.svg +0 -0
- package/plugins/cursor/plugin.js +46 -9
- package/plugins/cursor/plugin.json +3 -2
- package/plugins/cursor-nightly/icon.png +0 -0
- package/plugins/cursor-nightly/plugin.js +45 -8
- package/plugins/cursor-nightly/plugin.json +3 -2
- package/plugins/deepgram/icon.svg +0 -0
- package/plugins/deepgram/plugin.js +0 -0
- package/plugins/deepgram/plugin.json +0 -0
- package/plugins/deepinfra/icon.svg +4 -0
- package/plugins/deepinfra/plugin.js +97 -0
- package/plugins/deepinfra/plugin.json +28 -0
- package/plugins/deepseek/icon-color.svg +0 -0
- package/plugins/deepseek/icon.svg +0 -0
- package/plugins/deepseek/plugin.js +0 -0
- package/plugins/deepseek/plugin.json +0 -0
- package/plugins/devin/icon.svg +0 -0
- package/plugins/devin/plugin.js +111 -0
- package/plugins/devin/plugin.json +2 -1
- package/plugins/doubao/icon-color.svg +0 -0
- package/plugins/doubao/icon.svg +0 -0
- package/plugins/doubao/plugin.js +0 -0
- package/plugins/doubao/plugin.json +0 -0
- package/plugins/droid/icon.svg +0 -0
- package/plugins/droid/plugin.json +2 -1
- package/plugins/elevenlabs/icon.svg +0 -0
- package/plugins/elevenlabs/plugin.js +17 -2
- package/plugins/elevenlabs/plugin.json +0 -0
- package/plugins/factory/icon-conflict.svg +0 -0
- package/plugins/factory/icon.svg +0 -0
- package/plugins/factory/plugin.js +38 -14
- package/plugins/factory/plugin.json +2 -1
- package/plugins/fireworks-ai/icon.svg +0 -0
- package/plugins/fireworks-ai/plugin.js +0 -0
- package/plugins/fireworks-ai/plugin.json +0 -0
- package/plugins/gemini/icon-color.svg +0 -0
- package/plugins/gemini/icon.svg +0 -0
- package/plugins/gemini/plugin.js +0 -0
- package/plugins/gemini/plugin.json +0 -0
- package/plugins/grok/icon.svg +0 -0
- package/plugins/grok/plugin.js +30 -5
- package/plugins/grok/plugin.json +3 -1
- package/plugins/groqcloud/icon.svg +0 -0
- package/plugins/groqcloud/plugin.js +0 -0
- package/plugins/groqcloud/plugin.json +0 -0
- package/plugins/ibmbob/icon.svg +23 -0
- package/plugins/ibmbob/plugin.js +61 -0
- package/plugins/ibmbob/plugin.json +22 -0
- package/plugins/jetbrains-ai-assistant/icon-color.svg +0 -0
- package/plugins/jetbrains-ai-assistant/icon-conflict.svg +0 -0
- package/plugins/jetbrains-ai-assistant/icon.svg +0 -0
- package/plugins/jetbrains-ai-assistant/plugin.js +0 -0
- package/plugins/jetbrains-ai-assistant/plugin.json +0 -0
- package/plugins/kilo/icon.svg +0 -0
- package/plugins/kilo/plugin.js +0 -0
- package/plugins/kilo/plugin.json +0 -0
- package/plugins/kimi/icon-color.svg +0 -0
- package/plugins/kimi/icon-conflict.svg +0 -0
- package/plugins/kimi/icon.svg +0 -0
- package/plugins/kimi/plugin.js +122 -141
- package/plugins/kimi/plugin.json +5 -2
- package/plugins/kimi-k2/icon-color.svg +0 -0
- package/plugins/kimi-k2/icon.svg +0 -0
- package/plugins/kimi-k2/plugin.js +0 -0
- package/plugins/kimi-k2/plugin.json +0 -0
- package/plugins/kiro/icon-color.svg +0 -0
- package/plugins/kiro/icon-conflict.svg +0 -0
- package/plugins/kiro/icon.svg +0 -0
- package/plugins/kiro/plugin.js +49 -10
- package/plugins/kiro/plugin.json +3 -1
- package/plugins/litellm/icon.svg +0 -0
- package/plugins/litellm/plugin.js +0 -0
- package/plugins/litellm/plugin.json +0 -0
- package/plugins/llm-proxy/icon.svg +0 -0
- package/plugins/llm-proxy/plugin.js +0 -0
- package/plugins/llm-proxy/plugin.json +0 -0
- package/plugins/longcat/icon.svg +4 -0
- package/plugins/longcat/plugin.js +182 -0
- package/plugins/longcat/plugin.json +23 -0
- package/plugins/manus/icon.svg +0 -0
- package/plugins/manus/plugin.js +0 -0
- package/plugins/manus/plugin.json +0 -0
- package/plugins/mimo/icon.svg +0 -0
- package/plugins/mimo/plugin.js +0 -0
- package/plugins/mimo/plugin.json +0 -0
- package/plugins/minimax/icon-color.svg +0 -0
- package/plugins/minimax/icon-conflict.svg +0 -0
- package/plugins/minimax/icon.svg +0 -0
- package/plugins/minimax/plugin.js +25 -31
- package/plugins/minimax/plugin.json +0 -0
- package/plugins/mistral/icon-color.svg +0 -0
- package/plugins/mistral/icon.svg +0 -0
- package/plugins/mistral/plugin.js +0 -0
- package/plugins/mistral/plugin.json +0 -0
- package/plugins/moonshot/icon.svg +0 -0
- package/plugins/moonshot/plugin.js +8 -6
- package/plugins/moonshot/plugin.json +0 -0
- package/plugins/nanogpt/icon-color.svg +0 -0
- package/plugins/nanogpt/icon.svg +0 -0
- package/plugins/nanogpt/plugin.js +0 -0
- package/plugins/nanogpt/plugin.json +0 -0
- package/plugins/neuralwatt/icon.svg +0 -0
- package/plugins/neuralwatt/plugin.js +0 -0
- package/plugins/neuralwatt/plugin.json +0 -0
- package/plugins/notion/icon.svg +1 -0
- package/plugins/notion/plugin.js +282 -0
- package/plugins/notion/plugin.json +29 -0
- package/plugins/ollama/icon.svg +0 -0
- package/plugins/ollama/plugin.js +257 -56
- package/plugins/ollama/plugin.json +6 -3
- package/plugins/openai-api/icon.svg +0 -0
- package/plugins/openai-api/plugin.js +0 -0
- package/plugins/openai-api/plugin.json +0 -0
- package/plugins/opencode/icon.svg +0 -0
- package/plugins/opencode/plugin.js +0 -0
- package/plugins/opencode/plugin.json +0 -0
- package/plugins/opencode-go/icon-conflict.svg +0 -0
- package/plugins/opencode-go/icon.svg +0 -0
- package/plugins/opencode-go/plugin.js +78 -10
- package/plugins/opencode-go/plugin.json +5 -2
- package/plugins/openrouter/icon.svg +0 -0
- package/plugins/openrouter/plugin.js +371 -114
- package/plugins/openrouter/plugin.json +6 -0
- package/plugins/perplexity/icon-color.svg +0 -0
- package/plugins/perplexity/icon-conflict.svg +0 -0
- package/plugins/perplexity/icon.svg +0 -0
- package/plugins/perplexity/plugin.js +0 -0
- package/plugins/perplexity/plugin.json +0 -0
- package/plugins/poe/icon.svg +0 -0
- package/plugins/poe/plugin.js +24 -18
- package/plugins/poe/plugin.json +2 -0
- package/plugins/qoder/icon.svg +3 -0
- package/plugins/qoder/plugin.js +227 -0
- package/plugins/qoder/plugin.json +23 -0
- package/plugins/qwencloud/icon.svg +4 -0
- package/plugins/qwencloud/plugin.js +677 -0
- package/plugins/qwencloud/plugin.json +29 -0
- package/plugins/sakana/icon.svg +5 -0
- package/plugins/sakana/plugin.js +156 -0
- package/plugins/sakana/plugin.json +23 -0
- package/plugins/stepfun/icon-color.svg +0 -0
- package/plugins/stepfun/icon.svg +0 -0
- package/plugins/stepfun/plugin.js +0 -0
- package/plugins/stepfun/plugin.json +0 -0
- package/plugins/sub2api/icon.svg +4 -0
- package/plugins/sub2api/plugin.js +191 -0
- package/plugins/sub2api/plugin.json +16 -0
- package/plugins/synthetic/icon-conflict.svg +0 -0
- package/plugins/synthetic/icon.svg +0 -0
- package/plugins/synthetic/plugin.js +0 -0
- package/plugins/synthetic/plugin.json +0 -0
- package/plugins/t3chat/icon-color.svg +0 -0
- package/plugins/t3chat/icon.svg +0 -0
- package/plugins/t3chat/plugin.js +0 -0
- package/plugins/t3chat/plugin.json +0 -0
- package/plugins/venice/icon-color.svg +0 -0
- package/plugins/venice/icon.svg +0 -0
- package/plugins/venice/plugin.js +0 -0
- package/plugins/venice/plugin.json +0 -0
- package/plugins/vertex-ai/icon-color.svg +0 -0
- package/plugins/vertex-ai/icon.svg +0 -0
- package/plugins/vertex-ai/plugin.js +0 -0
- package/plugins/vertex-ai/plugin.json +0 -0
- package/plugins/warp/icon-color.svg +0 -0
- package/plugins/warp/icon.svg +0 -0
- package/plugins/warp/plugin.js +0 -0
- package/plugins/warp/plugin.json +0 -0
- package/plugins/wayfinder/icon.svg +5 -0
- package/plugins/wayfinder/plugin.js +47 -0
- package/plugins/wayfinder/plugin.json +16 -0
- package/plugins/windsurf/icon-conflict.svg +0 -0
- package/plugins/windsurf/icon.svg +0 -0
- package/plugins/windsurf/plugin.js +0 -0
- package/plugins/windsurf/plugin.json +0 -0
- package/plugins/xai/icon.svg +7 -0
- package/plugins/xai/plugin.js +162 -0
- package/plugins/xai/plugin.json +28 -0
- package/plugins/zai/icon-conflict.svg +0 -0
- package/plugins/zai/icon.svg +0 -0
- package/plugins/zai/plugin.js +111 -161
- package/plugins/zai/plugin.json +4 -1
- package/plugins/zed/icon.svg +0 -0
- package/plugins/zed/plugin.js +0 -0
- package/plugins/zed/plugin.json +0 -0
- package/plugins/zenmux/icon.svg +3 -0
- package/plugins/zenmux/plugin.js +102 -0
- package/plugins/zenmux/plugin.json +23 -0
- package/plugins/zoommate/icon.svg +1 -0
- package/plugins/zoommate/plugin.js +244 -0
- package/plugins/zoommate/plugin.json +29 -0
- package/usagestat +0 -0
- package/usagestatd +0 -0
package/plugins/claude/plugin.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
607
|
-
if (Number.
|
|
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
|
-
|
|
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:
|
|
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 === "
|
|
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
|
|
1338
|
-
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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
|
-
|
|
1476
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|