@hashimkarim/usagestat-linux-x64-gnu 2.0.0-alpha.3 → 2.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/native-manifest.json +731 -65
- package/package.json +112 -1
- package/plugins/UPSTREAM-LICENSES.md +6 -2
- package/plugins/_provider-icons/LICENSE +21 -0
- package/plugins/_provider-icons/NOTICE +12 -0
- package/plugins/_provider-icons/licenses/CodexBar-MIT.txt +21 -0
- package/plugins/_provider-icons/licenses/LobeHub-MIT.txt +21 -0
- package/plugins/_provider-icons/licenses/UsageStat-Bar-MIT.txt +21 -0
- package/plugins/_provider-icons/manifest.json +1219 -0
- package/plugins/_provider-icons/provenance.json +269 -0
- package/plugins/_provider-icons/source.json +4 -0
- package/plugins/abacus-ai/icon-color.svg +18 -0
- package/plugins/aixy/LICENSE +21 -0
- package/plugins/aixy/plugin.js +261 -0
- package/plugins/aixy/plugin.json +24 -0
- package/plugins/antigravity/plugin.js +136 -46
- package/plugins/antigravity-cli/icon-color.svg +1 -0
- package/plugins/antigravity-cli/icon.svg +1 -3
- package/plugins/antigravity-cli/plugin.js +11 -15
- package/plugins/antigravity-ide/icon-color.svg +1 -0
- package/plugins/antigravity-ide/icon.svg +1 -3
- package/plugins/atlascloud/LICENSE +21 -0
- package/plugins/atlascloud/icon-color.svg +1 -0
- package/plugins/atlascloud/icon.svg +1 -0
- package/plugins/atlascloud/plugin.js +49 -0
- package/plugins/atlascloud/plugin.json +23 -0
- package/plugins/augment/icon-color.svg +14 -0
- package/plugins/bifrost/LICENSE +21 -0
- package/plugins/bifrost/plugin.js +361 -0
- package/plugins/bifrost/plugin.json +23 -0
- package/plugins/claude/plugin.js +302 -152
- package/plugins/claude/plugin.json +1 -1
- package/plugins/clinepass/plugin.js +19 -3
- package/plugins/codebuff/icon-color.svg +92 -0
- package/plugins/coderabbit/plugin.js +25 -0
- package/plugins/coderabbit/plugin.json +12 -0
- package/plugins/codex/plugin.js +1 -0
- package/plugins/command-code/icon-color.svg +3 -0
- package/plugins/command-code/plugin.js +7 -3
- package/plugins/copilot/icon.svg +1 -1
- package/plugins/crof/icon-color.svg +3 -0
- package/plugins/crofai/icon-color.svg +3 -0
- package/plugins/crofai/icon.svg +2 -15
- package/plugins/cursor/icon-color.svg +1 -0
- package/plugins/deepinfra/icon-color.svg +1 -0
- package/plugins/deepinfra/icon.svg +1 -4
- package/plugins/deepseek/plugin.js +150 -59
- package/plugins/deepseek/plugin.json +3 -1
- package/plugins/devin/plugin.js +3 -0
- package/plugins/devpass/LICENSE +21 -0
- package/plugins/devpass/plugin.js +115 -0
- package/plugins/devpass/plugin.json +23 -0
- package/plugins/elevenlabs/icon-color.svg +4 -0
- package/plugins/factory/icon-color.svg +1 -0
- package/plugins/fireworks-ai/icon-color.svg +1 -0
- package/plugins/fireworks-ai/icon.svg +1 -6
- package/plugins/gemini-apps/LICENSE +22 -0
- package/plugins/gemini-apps/icon-color.svg +1 -0
- package/plugins/gemini-apps/icon.svg +1 -0
- package/plugins/gemini-apps/plugin.js +470 -0
- package/plugins/gemini-apps/plugin.json +19 -0
- package/plugins/gitkraken/LICENSE +21 -0
- package/plugins/gitkraken/plugin.js +153 -0
- package/plugins/gitkraken/plugin.json +24 -0
- package/plugins/grok/icon-color.svg +1 -0
- package/plugins/groqcloud/icon-color.svg +1 -0
- package/plugins/helmcode/LICENSE +21 -0
- package/plugins/helmcode/plugin.js +188 -0
- package/plugins/helmcode/plugin.json +24 -0
- package/plugins/huggingface/LICENSE +21 -0
- package/plugins/huggingface/icon-color.svg +1 -0
- package/plugins/huggingface/icon.svg +1 -0
- package/plugins/huggingface/plugin.js +293 -0
- package/plugins/huggingface/plugin.json +25 -0
- package/plugins/hyper/LICENSE +21 -0
- package/plugins/hyper/plugin.js +131 -0
- package/plugins/hyper/plugin.json +26 -0
- package/plugins/kilo/icon-color.svg +1 -0
- package/plugins/litellm/plugin.js +54 -3
- package/plugins/llmman/LICENSE +21 -0
- package/plugins/llmman/plugin.js +133 -0
- package/plugins/llmman/plugin.json +23 -0
- package/plugins/longcat/icon-color.svg +1 -0
- package/plugins/longcat/icon.svg +1 -4
- package/plugins/manus/icon-color.svg +6 -0
- package/plugins/mimo/icon-color.svg +4 -0
- package/plugins/mimo/icon.svg +4 -1
- package/plugins/mistral/plugin.js +110 -5
- package/plugins/moonshot/icon-color.svg +1 -0
- package/plugins/muse/LICENSE +21 -0
- package/plugins/muse/plugin.js +319 -0
- package/plugins/muse/plugin.json +26 -0
- package/plugins/nous/LICENSE +21 -0
- package/plugins/nous/icon-color.svg +1 -0
- package/plugins/nous/icon.svg +1 -0
- package/plugins/nous/plugin.js +124 -0
- package/plugins/nous/plugin.json +24 -0
- package/plugins/ollama/icon-color.svg +1 -0
- package/plugins/openai-api/icon-color.svg +1 -0
- package/plugins/opencode/icon-color.svg +1 -0
- package/plugins/opencode-go/icon-color.svg +1 -0
- package/plugins/opencode-go/plugin.js +30 -12
- package/plugins/openrouter/icon-color.svg +1 -0
- package/plugins/pi/plugin.js +113 -0
- package/plugins/pi/plugin.json +11 -0
- package/plugins/qwencloud/icon-color.svg +1 -0
- package/plugins/qwencloud/icon.svg +1 -4
- package/plugins/raycast/LICENSE +21 -0
- package/plugins/raycast/plugin.js +174 -0
- package/plugins/raycast/plugin.json +24 -0
- package/plugins/replicate/LICENSE +21 -0
- package/plugins/replicate/icon-color.svg +1 -0
- package/plugins/replicate/icon.svg +1 -0
- package/plugins/replicate/plugin.js +165 -0
- package/plugins/replicate/plugin.json +24 -0
- package/plugins/synthetic/icon-color.svg +14 -0
- package/plugins/typesafe/LICENSE +21 -0
- package/plugins/typesafe/plugin.js +225 -0
- package/plugins/typesafe/plugin.json +24 -0
- package/plugins/v0/LICENSE +21 -0
- package/plugins/v0/icon-color.svg +1 -0
- package/plugins/v0/icon.svg +1 -0
- package/plugins/v0/plugin.js +214 -0
- package/plugins/v0/plugin.json +24 -0
- package/plugins/venice/plugin.js +50 -1
- package/plugins/venice/plugin.json +2 -1
- package/plugins/vercel/LICENSE +21 -0
- package/plugins/vercel/icon-color.svg +1 -0
- package/plugins/vercel/icon.svg +1 -0
- package/plugins/vercel/plugin.js +55 -0
- package/plugins/vercel/plugin.json +23 -0
- package/plugins/windsurf/icon-color.svg +1 -0
- package/plugins/xai/icon-color.svg +1 -0
- package/plugins/xai/icon.svg +1 -7
- package/plugins/xkiro/LICENSE +21 -0
- package/plugins/xkiro/plugin.js +99 -0
- package/plugins/xkiro/plugin.json +23 -0
- package/plugins/zai/icon-color.svg +1 -0
- package/plugins/zenmux/icon-color.svg +1 -0
- package/plugins/zenmux/icon.svg +1 -3
- package/usagestat +0 -0
- package/usagestatd +0 -0
package/plugins/claude/plugin.js
CHANGED
|
@@ -411,6 +411,8 @@
|
|
|
411
411
|
|
|
412
412
|
const envAccessToken = readEnvText(ctx, "CLAUDE_CODE_OAUTH_TOKEN")
|
|
413
413
|
const stored = loadStoredCredentials(ctx, !!envAccessToken)
|
|
414
|
+
// setup-token credentials cannot read quotas; prefer the signed-in profile.
|
|
415
|
+
if (stored && stored.oauth && stored.oauth.accessToken && hasProfileScope(stored)) return stored
|
|
414
416
|
if (!envAccessToken) {
|
|
415
417
|
return stored
|
|
416
418
|
}
|
|
@@ -594,15 +596,21 @@
|
|
|
594
596
|
|
|
595
597
|
function fetchUsage(ctx, accessToken) {
|
|
596
598
|
const oauthConfig = getOauthConfig(ctx)
|
|
599
|
+
let version = "2.1.0"
|
|
600
|
+
try {
|
|
601
|
+
const result = ctx.host.command.run({program: "claude", args: ["--version"], timeoutMs: 3000})
|
|
602
|
+
const match = result.status === 0 && /^\s*(\d+\.\d+\.\d+)(?:\s|$)/.exec(result.stdout || "")
|
|
603
|
+
if (match) version = match[1]
|
|
604
|
+
} catch (_) { /* The OAuth source also works without a local CLI. */ }
|
|
597
605
|
return ctx.util.request({
|
|
598
606
|
method: "GET",
|
|
599
|
-
url: oauthConfig.usageUrl,
|
|
607
|
+
url: oauthConfig.usageUrl + "?cedar_ember=1&skip_spend=1",
|
|
600
608
|
headers: {
|
|
601
609
|
Authorization: "Bearer " + accessToken.trim(),
|
|
602
610
|
Accept: "application/json",
|
|
603
611
|
"Content-Type": "application/json",
|
|
604
612
|
"anthropic-beta": "oauth-2025-04-20",
|
|
605
|
-
"User-Agent": "claude-code/
|
|
613
|
+
"User-Agent": "claude-code/" + version,
|
|
606
614
|
},
|
|
607
615
|
timeoutMs: 10000,
|
|
608
616
|
})
|
|
@@ -626,6 +634,29 @@
|
|
|
626
634
|
return null
|
|
627
635
|
}
|
|
628
636
|
|
|
637
|
+
function livePlanLabel(ctx, creds) {
|
|
638
|
+
const token = creds.oauth.accessToken
|
|
639
|
+
const tokenHash = ctx.host.crypto.sha256(token)
|
|
640
|
+
const cached = readLiveUsageCache(ctx)
|
|
641
|
+
if (cached && cached.profileTokenHash === tokenHash) return cached.livePlan || null
|
|
642
|
+
let plan = null
|
|
643
|
+
try {
|
|
644
|
+
const result = ctx.util.requestJson({method: "GET", url: getOauthConfig(ctx).baseApiUrl + "/api/oauth/profile",
|
|
645
|
+
headers: {Authorization: "Bearer " + token, Accept: "application/json", "anthropic-beta": "oauth-2025-04-20"}, timeoutMs: 3000})
|
|
646
|
+
const org = result.resp.status === 200 && result.json && result.json.organization
|
|
647
|
+
if (org && typeof org === "object") {
|
|
648
|
+
const type = cleanText(org.organization_type || org.organizationType)
|
|
649
|
+
const tier = cleanText(org.rate_limit_tier || org.rateLimitTier) || creds.oauth.rateLimitTier
|
|
650
|
+
if (type) plan = oauthPlanLabel(ctx, {oauth: {subscriptionType: type.replace(/^claude_/, ""), rateLimitTier: tier}})
|
|
651
|
+
}
|
|
652
|
+
} catch (_) {
|
|
653
|
+
ctx.host.log.warn("Claude profile unavailable; retaining credential plan.")
|
|
654
|
+
}
|
|
655
|
+
// Include failed lookups, so a profile outage cannot add a request on every poll.
|
|
656
|
+
writeLiveUsageCache(ctx, {profileTokenHash: tokenHash, livePlan: plan})
|
|
657
|
+
return plan
|
|
658
|
+
}
|
|
659
|
+
|
|
629
660
|
function fmtRateLimitMinutes(seconds) {
|
|
630
661
|
if (seconds <= 0) return "now"
|
|
631
662
|
const mins = Math.ceil(seconds / 60)
|
|
@@ -1149,9 +1180,9 @@
|
|
|
1149
1180
|
|
|
1150
1181
|
function getSessionKey(ctx) {
|
|
1151
1182
|
const candidates = [
|
|
1183
|
+
providerCookieHeader(ctx),
|
|
1152
1184
|
readEnvText(ctx, "CLAUDE_AI_SESSION_KEY"),
|
|
1153
1185
|
readEnvText(ctx, "CLAUDE_WEB_SESSION_KEY"),
|
|
1154
|
-
providerCookieHeader(ctx),
|
|
1155
1186
|
]
|
|
1156
1187
|
for (let i = 0; i < candidates.length; i++) {
|
|
1157
1188
|
const key = extractSessionKey(candidates[i])
|
|
@@ -1160,9 +1191,12 @@
|
|
|
1160
1191
|
return null
|
|
1161
1192
|
}
|
|
1162
1193
|
|
|
1163
|
-
function buildWebHeaders(sessionKey) {
|
|
1194
|
+
function buildWebHeaders(ctx, sessionKey) {
|
|
1195
|
+
const configured = providerCookieHeader(ctx)
|
|
1196
|
+
const cookie = configured && extractSessionKey(configured) === sessionKey && configured.includes("=")
|
|
1197
|
+
? configured : "sessionKey=" + sessionKey
|
|
1164
1198
|
return {
|
|
1165
|
-
Cookie:
|
|
1199
|
+
Cookie: cookie,
|
|
1166
1200
|
Accept: "application/json",
|
|
1167
1201
|
Origin: "https://claude.ai",
|
|
1168
1202
|
Referer: "https://claude.ai/settings/usage",
|
|
@@ -1171,8 +1205,22 @@
|
|
|
1171
1205
|
}
|
|
1172
1206
|
}
|
|
1173
1207
|
|
|
1208
|
+
function checkWebResponse(resp) {
|
|
1209
|
+
const headers = resp.headers || {}
|
|
1210
|
+
const challenge = String(headers["cf-mitigated"] || headers["Cf-Mitigated"] || "").toLowerCase() === "challenge"
|
|
1211
|
+
|| /<title>\s*just a moment|\/cdn-cgi\/challenge-platform\//i.test(String(resp.bodyText || "").slice(0,65536))
|
|
1212
|
+
if (resp.status === 403 && challenge) {
|
|
1213
|
+
throw "Claude web usage is blocked by a Cloudflare browser challenge. Your Claude login has not been rejected."
|
|
1214
|
+
}
|
|
1215
|
+
if (resp.status === 429) {
|
|
1216
|
+
const error = new Error("Claude web usage updates are temporarily rate-limited, not your message allowance.")
|
|
1217
|
+
error.retryAfterSeconds = parseRetryAfterSeconds(headers)
|
|
1218
|
+
throw error
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1174
1222
|
function fetchWebUsage(ctx, sessionKey) {
|
|
1175
|
-
const headers = buildWebHeaders(sessionKey)
|
|
1223
|
+
const headers = buildWebHeaders(ctx, sessionKey)
|
|
1176
1224
|
|
|
1177
1225
|
let orgResp
|
|
1178
1226
|
try {
|
|
@@ -1187,6 +1235,7 @@
|
|
|
1187
1235
|
throw "Web usage request failed. Check your connection."
|
|
1188
1236
|
}
|
|
1189
1237
|
|
|
1238
|
+
checkWebResponse(orgResp)
|
|
1190
1239
|
if (orgResp.status === 401 || orgResp.status === 403) {
|
|
1191
1240
|
throw "Web session invalid or expired. Set CLAUDE_AI_SESSION_KEY to your claude.ai sessionKey cookie."
|
|
1192
1241
|
}
|
|
@@ -1219,6 +1268,7 @@
|
|
|
1219
1268
|
}
|
|
1220
1269
|
|
|
1221
1270
|
lastUsageStatus = usageResp.status
|
|
1271
|
+
checkWebResponse(usageResp)
|
|
1222
1272
|
if (usageResp.status === 401 || usageResp.status === 403) {
|
|
1223
1273
|
ctx.host.log.info("web mode: usage unavailable for org " + candidateOrgId + " (HTTP " + usageResp.status + ")")
|
|
1224
1274
|
continue
|
|
@@ -1265,33 +1315,46 @@
|
|
|
1265
1315
|
|
|
1266
1316
|
function loadWebUsageData(ctx, sessionKey, nowMs) {
|
|
1267
1317
|
const liveCache = readLiveUsageCache(ctx)
|
|
1318
|
+
if (liveCache && Number(liveCache.webRetryAtMs) > nowMs) {
|
|
1319
|
+
throw liveCache.webError || "Claude web usage updates are temporarily unavailable."
|
|
1320
|
+
}
|
|
1268
1321
|
const cachedLastWebUsageFetchMs = Number(liveCache && liveCache.lastWebUsageFetchMs) || 0
|
|
1269
1322
|
const effectiveLastWebUsageFetchMs = Math.max(lastWebUsageFetchMs, cachedLastWebUsageFetchMs)
|
|
1270
|
-
const cached = cachedWebUsageData || cachedLiveUsageData(liveCache, nowMs)
|
|
1323
|
+
const cached = cachedWebUsageData || (liveCache && liveCache.usageSource === "web" && cachedLiveUsageData(liveCache, nowMs))
|
|
1271
1324
|
if (cached && nowMs - effectiveLastWebUsageFetchMs < MIN_USAGE_FETCH_INTERVAL_MS) {
|
|
1272
1325
|
lastWebUsageFetchMs = effectiveLastWebUsageFetchMs
|
|
1273
1326
|
ctx.host.log.info(
|
|
1274
1327
|
"web usage fetch skipped: last fetch was " +
|
|
1275
1328
|
Math.round((nowMs - effectiveLastWebUsageFetchMs) / 1000) + "s ago"
|
|
1276
1329
|
)
|
|
1277
|
-
return { data: cached, plan: null }
|
|
1330
|
+
return { data: cached, plan: liveCache && liveCache.webPlan || null }
|
|
1278
1331
|
}
|
|
1279
1332
|
|
|
1280
1333
|
lastWebUsageFetchMs = nowMs
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1334
|
+
let result
|
|
1335
|
+
try {
|
|
1336
|
+
result = fetchWebUsage(ctx, sessionKey)
|
|
1337
|
+
const validLines = []
|
|
1338
|
+
addUsageWindowLines(ctx, result.usageData, validLines)
|
|
1339
|
+
if (!validLines.length) throw "Web usage response invalid. Try again later."
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
const delay = error && Number.isFinite(error.retryAfterSeconds) ? error.retryAfterSeconds * 1000 : MIN_USAGE_FETCH_INTERVAL_MS
|
|
1342
|
+
writeLiveUsageCache(ctx, {lastWebUsageFetchMs: nowMs, webRetryAtMs: nowMs + Math.max(MIN_USAGE_FETCH_INTERVAL_MS, delay),
|
|
1343
|
+
webError: error && error.message || String(error)})
|
|
1344
|
+
throw error
|
|
1345
|
+
}
|
|
1285
1346
|
cachedWebUsageData = result.usageData
|
|
1347
|
+
let plan = null
|
|
1348
|
+
if (result.accountInfo && result.accountInfo.rate_limit_tier) {
|
|
1349
|
+
plan = webTierToPlanLabel(result.accountInfo.rate_limit_tier)
|
|
1350
|
+
}
|
|
1286
1351
|
writeLiveUsageCache(ctx, {
|
|
1287
1352
|
usageData: result.usageData,
|
|
1353
|
+
usageSource: "web",
|
|
1288
1354
|
usageFetchedAtMs: nowMs,
|
|
1289
1355
|
lastWebUsageFetchMs: nowMs,
|
|
1356
|
+
webRetryAtMs: 0, webError: null, webPlan: plan,
|
|
1290
1357
|
})
|
|
1291
|
-
let plan = null
|
|
1292
|
-
if (result.accountInfo && result.accountInfo.rate_limit_tier) {
|
|
1293
|
-
plan = webTierToPlanLabel(result.accountInfo.rate_limit_tier)
|
|
1294
|
-
}
|
|
1295
1358
|
return { data: result.usageData, plan: plan }
|
|
1296
1359
|
}
|
|
1297
1360
|
|
|
@@ -1439,6 +1502,24 @@
|
|
|
1439
1502
|
}))
|
|
1440
1503
|
}
|
|
1441
1504
|
|
|
1505
|
+
const grants = data.cedar_ember
|
|
1506
|
+
if (grants && typeof grants === "object" && !Array.isArray(grants)) {
|
|
1507
|
+
let count = 0
|
|
1508
|
+
const expiries = []
|
|
1509
|
+
if (grants.eligible === true && Array.isArray(grants.grants)) {
|
|
1510
|
+
for (const grant of grants.grants) {
|
|
1511
|
+
if (!grant || !Number.isSafeInteger(grant.resets_left) || grant.resets_left <= 0) continue
|
|
1512
|
+
const end = ctx.util.toIso(grant.ends_at)
|
|
1513
|
+
if (end && Date.parse(end) <= Date.parse(ctx.nowIso)) continue
|
|
1514
|
+
if (!Number.isSafeInteger(count + grant.resets_left)) continue
|
|
1515
|
+
count += grant.resets_left
|
|
1516
|
+
if (end) expiries.push(end + " (" + grant.resets_left + ")")
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
lines.push(ctx.line.text({label: "Rate Limit Resets", value: count + " available",
|
|
1520
|
+
subtitle: expiries.length ? "Expires " + expiries.sort().join(", ") : undefined}))
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1442
1523
|
if (data.extra_usage && data.extra_usage.is_enabled) {
|
|
1443
1524
|
const used = moneyMajorUnits(data.extra_usage.used_credits)
|
|
1444
1525
|
const limit = moneyMajorUnits(data.extra_usage.monthly_limit)
|
|
@@ -1488,6 +1569,29 @@
|
|
|
1488
1569
|
|
|
1489
1570
|
// ── probe() ────────────────────────────────────────────────────────────────
|
|
1490
1571
|
|
|
1572
|
+
function readNativeQuota(ctx, creds, probe) {
|
|
1573
|
+
// Never borrow the machine's ambient CLI account for a named account or an
|
|
1574
|
+
// inference-only token. The native host also verifies the profile token.
|
|
1575
|
+
if (!creds || creds.source !== "file" || !hasProfileScope(creds)) return null
|
|
1576
|
+
const host = ctx.host.claude
|
|
1577
|
+
const read = host && host[probe ? "probeQuota" : "readQuota"]
|
|
1578
|
+
if (typeof read !== "function") return null
|
|
1579
|
+
try {
|
|
1580
|
+
const raw = read(ctx.host.crypto.sha256(creds.oauth.accessToken), ctx.provider && ctx.provider.workspaceId || null)
|
|
1581
|
+
const result = typeof raw === "string" ? ctx.util.tryParseJson(raw) : raw
|
|
1582
|
+
if (!result || !result.data || !Number.isFinite(result.fetchedAtMs)) return null
|
|
1583
|
+
const age = Date.now() - result.fetchedAtMs
|
|
1584
|
+
if (age < 0 || age > MIN_USAGE_FETCH_INTERVAL_MS) return null
|
|
1585
|
+
if (result.source !== "cli" && result.source !== "cli-cache") return null
|
|
1586
|
+
const valid = []
|
|
1587
|
+
addUsageWindowLines(ctx, result.data, valid)
|
|
1588
|
+
return valid.length ? result : null
|
|
1589
|
+
} catch (e) {
|
|
1590
|
+
ctx.host.log.info("Claude CLI quota unavailable: " + String(e))
|
|
1591
|
+
return null
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1491
1595
|
function probe(ctx) {
|
|
1492
1596
|
const creds = loadCredentials(ctx)
|
|
1493
1597
|
const sessionKey = getSessionKey(ctx)
|
|
@@ -1497,6 +1601,7 @@
|
|
|
1497
1601
|
const wantsAuto = sourceMode === "auto"
|
|
1498
1602
|
const wantsWeb = sourceMode === "web"
|
|
1499
1603
|
const wantsOAuth = sourceMode === "oauth"
|
|
1604
|
+
const wantsCli = sourceMode === "cli"
|
|
1500
1605
|
const wantsLocal = sourceMode === "local"
|
|
1501
1606
|
const wantsApi = sourceMode === "api"
|
|
1502
1607
|
|
|
@@ -1519,6 +1624,7 @@
|
|
|
1519
1624
|
const homePath = getClaudeHomeOverride(ctx)
|
|
1520
1625
|
let data = null
|
|
1521
1626
|
let quotaSource = null
|
|
1627
|
+
let quotaFetchedAtMs = null
|
|
1522
1628
|
let lines = []
|
|
1523
1629
|
let plan = null
|
|
1524
1630
|
let rateLimited = false
|
|
@@ -1526,149 +1632,194 @@
|
|
|
1526
1632
|
|
|
1527
1633
|
if (hasOAuth) {
|
|
1528
1634
|
plan = oauthPlanLabel(ctx, creds)
|
|
1635
|
+
if (liveCache && liveCache.profileTokenHash === ctx.host.crypto.sha256(creds.oauth.accessToken) && liveCache.livePlan) plan = liveCache.livePlan
|
|
1529
1636
|
}
|
|
1530
1637
|
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1638
|
+
let nativeQuota = null
|
|
1639
|
+
if (wantsAuto || wantsCli) {
|
|
1640
|
+
nativeQuota = readNativeQuota(ctx, creds, false)
|
|
1641
|
+
const cooldownUntil = Math.max(rateLimitedUntilMs, Number(liveCache && liveCache.rateLimitedUntilMs) || 0)
|
|
1642
|
+
const apiFetchedAt = Number(liveCache && liveCache.usageFetchedAtMs) || 0
|
|
1643
|
+
const freshApi = cachedLiveUsageData(liveCache, nowMs) && apiFetchedAt <= nowMs && nowMs - apiFetchedAt < MIN_USAGE_FETCH_INTERVAL_MS
|
|
1644
|
+
if (!nativeQuota && wantsCli && nowMs >= cooldownUntil) {
|
|
1645
|
+
nativeQuota = readNativeQuota(ctx, creds, true)
|
|
1646
|
+
}
|
|
1647
|
+
// Prefer the newer complete observation; passive reads never clear an
|
|
1648
|
+
// HTTP Retry-After deadline or rewrite the REST cache's fetch timestamp.
|
|
1649
|
+
if (nativeQuota && !wantsCli && freshApi && apiFetchedAt > nativeQuota.fetchedAtMs) nativeQuota = null
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
try {
|
|
1653
|
+
if (wantsLocal) {
|
|
1654
|
+
ctx.host.log.info("local mode requested; skipping live usage fetch")
|
|
1655
|
+
} else if (wantsApi) {
|
|
1656
|
+
ctx.host.log.info("api mode requested; skipping live quota fetch")
|
|
1657
|
+
} else if (nativeQuota) {
|
|
1658
|
+
data = nativeQuota.data
|
|
1659
|
+
quotaSource = nativeQuota.source
|
|
1660
|
+
quotaFetchedAtMs = nativeQuota.fetchedAtMs
|
|
1661
|
+
} else if (wantsCli) {
|
|
1662
|
+
const cooldownUntil = Math.max(rateLimitedUntilMs, Number(liveCache && liveCache.rateLimitedUntilMs) || 0)
|
|
1663
|
+
if (nowMs < cooldownUntil) {
|
|
1549
1664
|
rateLimited = true
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
data = cachedData
|
|
1553
|
-
ctx.host.log.info("usage fetch skipped: rate-limited for " + retryAfterSeconds + "s more")
|
|
1665
|
+
retryAfterSeconds = Math.ceil((cooldownUntil - nowMs) / 1000)
|
|
1666
|
+
data = cachedLiveUsageData(liveCache, nowMs)
|
|
1554
1667
|
} else {
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1668
|
+
throw "Claude CLI returned no fresh quota data. Usage checks may be temporarily unavailable."
|
|
1669
|
+
}
|
|
1670
|
+
} else if (hasOAuth && !wantsWeb) {
|
|
1671
|
+
quotaSource = "oauth"
|
|
1672
|
+
// ── OAuth mode ───────────────────────────────────────────────────────
|
|
1673
|
+
let accessToken = creds.oauth.accessToken
|
|
1674
|
+
const canFetchLiveUsage = hasProfileScope(creds)
|
|
1675
|
+
|
|
1676
|
+
if (canFetchLiveUsage) {
|
|
1677
|
+
const cachedData = cachedUsageData || cachedLiveUsageData(liveCache, nowMs)
|
|
1678
|
+
const cachedRateLimitedUntilMs = Number(liveCache && liveCache.rateLimitedUntilMs) || 0
|
|
1679
|
+
const effectiveRateLimitedUntilMs = Math.max(rateLimitedUntilMs, cachedRateLimitedUntilMs)
|
|
1680
|
+
|
|
1681
|
+
if (nowMs < effectiveRateLimitedUntilMs) {
|
|
1682
|
+
// Still within a rate-limit window from a previous probe call — skip the
|
|
1683
|
+
// API request entirely and surface the remaining wait time to the user.
|
|
1684
|
+
rateLimited = true
|
|
1685
|
+
rateLimitedUntilMs = effectiveRateLimitedUntilMs
|
|
1686
|
+
retryAfterSeconds = Math.ceil((effectiveRateLimitedUntilMs - nowMs) / 1000)
|
|
1566
1687
|
data = cachedData
|
|
1567
|
-
ctx.host.log.info(
|
|
1568
|
-
"usage fetch skipped: last fetch was " +
|
|
1569
|
-
Math.round((nowMs - effectiveLastUsageFetchMs) / 1000) + "s ago (min interval " +
|
|
1570
|
-
MIN_USAGE_FETCH_INTERVAL_MS / 1000 + "s)"
|
|
1571
|
-
)
|
|
1688
|
+
ctx.host.log.info("usage fetch skipped: rate-limited for " + retryAfterSeconds + "s more")
|
|
1572
1689
|
} else {
|
|
1573
|
-
//
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
lastUsageFetchMs = nowMs
|
|
1585
|
-
let resp
|
|
1586
|
-
let didRefresh = false
|
|
1587
|
-
try {
|
|
1588
|
-
resp = ctx.util.retryOnceOnAuth({
|
|
1589
|
-
request: (token) => {
|
|
1590
|
-
try {
|
|
1591
|
-
return fetchUsage(ctx, token || accessToken)
|
|
1592
|
-
} catch (e) {
|
|
1593
|
-
ctx.host.log.error("usage request exception: " + String(e))
|
|
1594
|
-
if (didRefresh) {
|
|
1595
|
-
throw "Usage request failed after refresh. Try again."
|
|
1596
|
-
}
|
|
1597
|
-
throw "Usage request failed. Check your connection."
|
|
1598
|
-
}
|
|
1599
|
-
},
|
|
1600
|
-
refresh: () => {
|
|
1601
|
-
ctx.host.log.info("usage returned 401, attempting refresh")
|
|
1602
|
-
didRefresh = true
|
|
1603
|
-
return refreshToken(ctx, creds)
|
|
1604
|
-
},
|
|
1605
|
-
})
|
|
1606
|
-
} catch (e) {
|
|
1607
|
-
if (typeof e === "string") throw e
|
|
1608
|
-
ctx.host.log.error("usage request failed: " + String(e))
|
|
1609
|
-
throw "Usage request failed. Check your connection."
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
if (ctx.util.isAuthStatus(resp.status)) {
|
|
1613
|
-
ctx.host.log.error("usage returned auth error after all retries: status=" + resp.status)
|
|
1614
|
-
throw "Token expired. Run `claude` to log in again."
|
|
1615
|
-
}
|
|
1616
|
-
|
|
1617
|
-
if (resp.status === 429) {
|
|
1618
|
-
rateLimited = true
|
|
1619
|
-
retryAfterSeconds = parseRetryAfterSeconds(resp.headers)
|
|
1620
|
-
const backoffMs = retryAfterSeconds !== null
|
|
1621
|
-
? retryAfterSeconds * 1000
|
|
1622
|
-
: DEFAULT_RATE_LIMIT_BACKOFF_MS
|
|
1623
|
-
rateLimitedUntilMs = nowMs + backoffMs
|
|
1690
|
+
// Rate-limit window has expired (or was never set). Check whether we were
|
|
1691
|
+
// previously rate-limited so we can bypass the min-interval guard: a short
|
|
1692
|
+
// Retry-After (< 5 min) must not be swallowed by the normal poll throttle.
|
|
1693
|
+
const wasRateLimited = effectiveRateLimitedUntilMs > 0
|
|
1694
|
+
rateLimitedUntilMs = 0
|
|
1695
|
+
|
|
1696
|
+
const cachedLastUsageFetchMs = Number(liveCache && liveCache.lastUsageFetchMs) || 0
|
|
1697
|
+
const effectiveLastUsageFetchMs = Math.max(lastUsageFetchMs, cachedLastUsageFetchMs)
|
|
1698
|
+
if (cachedData && !wasRateLimited && nowMs - effectiveLastUsageFetchMs < MIN_USAGE_FETCH_INTERVAL_MS) {
|
|
1699
|
+
// Polled too recently in normal operation — reuse last cached response.
|
|
1700
|
+
lastUsageFetchMs = effectiveLastUsageFetchMs
|
|
1624
1701
|
data = cachedData
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
usageFetchedAtMs: Number(liveCache && liveCache.usageFetchedAtMs) || null,
|
|
1630
|
-
lastRateLimitedAtMs: nowMs,
|
|
1631
|
-
})
|
|
1632
|
-
ctx.host.log.warn(
|
|
1633
|
-
"usage rate limited (429), backing off for " +
|
|
1634
|
-
Math.round(backoffMs / 1000) + "s"
|
|
1702
|
+
ctx.host.log.info(
|
|
1703
|
+
"usage fetch skipped: last fetch was " +
|
|
1704
|
+
Math.round((nowMs - effectiveLastUsageFetchMs) / 1000) + "s ago (min interval " +
|
|
1705
|
+
MIN_USAGE_FETCH_INTERVAL_MS / 1000 + "s)"
|
|
1635
1706
|
)
|
|
1636
|
-
} else if (resp.status < 200 || resp.status >= 300) {
|
|
1637
|
-
ctx.host.log.error("usage returned error: status=" + resp.status)
|
|
1638
|
-
throw "Usage request failed (HTTP " + String(resp.status) + "). Try again later."
|
|
1639
1707
|
} else {
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1708
|
+
// Proactively refresh if token is expired or about to expire
|
|
1709
|
+
if (needsRefresh(ctx, creds.oauth, nowMs)) {
|
|
1710
|
+
ctx.host.log.info("token needs refresh (expired or expiring soon)")
|
|
1711
|
+
const refreshed = refreshToken(ctx, creds)
|
|
1712
|
+
if (refreshed) {
|
|
1713
|
+
accessToken = refreshed
|
|
1714
|
+
} else {
|
|
1715
|
+
ctx.host.log.warn("proactive refresh failed, trying with existing token")
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
lastUsageFetchMs = nowMs
|
|
1720
|
+
let resp
|
|
1721
|
+
let didRefresh = false
|
|
1722
|
+
try {
|
|
1723
|
+
resp = ctx.util.retryOnceOnAuth({
|
|
1724
|
+
request: (token) => {
|
|
1725
|
+
try {
|
|
1726
|
+
return fetchUsage(ctx, token || accessToken)
|
|
1727
|
+
} catch (e) {
|
|
1728
|
+
ctx.host.log.error("usage request exception: " + String(e))
|
|
1729
|
+
if (didRefresh) {
|
|
1730
|
+
throw "Usage request failed after refresh. Try again."
|
|
1731
|
+
}
|
|
1732
|
+
throw "Usage request failed. Check your connection."
|
|
1733
|
+
}
|
|
1734
|
+
},
|
|
1735
|
+
refresh: () => {
|
|
1736
|
+
ctx.host.log.info("usage returned 401, attempting refresh")
|
|
1737
|
+
didRefresh = true
|
|
1738
|
+
return refreshToken(ctx, creds)
|
|
1739
|
+
},
|
|
1740
|
+
})
|
|
1741
|
+
} catch (e) {
|
|
1742
|
+
if (typeof e === "string") throw e
|
|
1743
|
+
ctx.host.log.error("usage request failed: " + String(e))
|
|
1744
|
+
throw "Usage request failed. Check your connection."
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
if (ctx.util.isAuthStatus(resp.status)) {
|
|
1748
|
+
ctx.host.log.error("usage returned auth error after all retries: status=" + resp.status)
|
|
1749
|
+
throw "Token expired. Run `claude` to log in again."
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const responseData = ctx.util.tryParseJson(resp.bodyText)
|
|
1753
|
+
const inBandRateLimit = resp.status >= 200 && resp.status < 300
|
|
1754
|
+
&& responseData && responseData.error && responseData.error.type === "rate_limit_error"
|
|
1755
|
+
if (resp.status === 429 || inBandRateLimit) {
|
|
1756
|
+
rateLimited = true
|
|
1757
|
+
retryAfterSeconds = parseRetryAfterSeconds(resp.headers)
|
|
1758
|
+
const backoffMs = retryAfterSeconds !== null
|
|
1759
|
+
? retryAfterSeconds * 1000
|
|
1760
|
+
: DEFAULT_RATE_LIMIT_BACKOFF_MS
|
|
1761
|
+
rateLimitedUntilMs = nowMs + backoffMs
|
|
1762
|
+
data = cachedData
|
|
1763
|
+
writeLiveUsageCache(ctx, {
|
|
1764
|
+
rateLimitedUntilMs: rateLimitedUntilMs,
|
|
1765
|
+
lastUsageFetchMs: nowMs,
|
|
1766
|
+
usageData: data,
|
|
1767
|
+
usageFetchedAtMs: Number(liveCache && liveCache.usageFetchedAtMs) || null,
|
|
1768
|
+
lastRateLimitedAtMs: nowMs,
|
|
1769
|
+
})
|
|
1770
|
+
ctx.host.log.warn(
|
|
1771
|
+
"usage rate limited (429), backing off for " +
|
|
1772
|
+
Math.round(backoffMs / 1000) + "s"
|
|
1773
|
+
)
|
|
1774
|
+
} else if (resp.status < 200 || resp.status >= 300) {
|
|
1775
|
+
ctx.host.log.error("usage returned error: status=" + resp.status)
|
|
1776
|
+
throw "Usage request failed (HTTP " + String(resp.status) + "). Try again later."
|
|
1777
|
+
} else {
|
|
1778
|
+
ctx.host.log.info("usage fetch succeeded")
|
|
1779
|
+
data = responseData
|
|
1780
|
+
const validLines = []
|
|
1781
|
+
if (data && typeof data === "object") addUsageWindowLines(ctx, data, validLines)
|
|
1782
|
+
if (!validLines.length) {
|
|
1783
|
+
throw "Usage response invalid. Try again later."
|
|
1784
|
+
}
|
|
1785
|
+
cachedUsageData = data
|
|
1786
|
+
rateLimitedUntilMs = 0
|
|
1787
|
+
writeLiveUsageCache(ctx, {
|
|
1788
|
+
rateLimitedUntilMs: 0,
|
|
1789
|
+
lastUsageFetchMs: nowMs,
|
|
1790
|
+
usageData: data,
|
|
1791
|
+
usageSource: "oauth",
|
|
1792
|
+
usageFetchedAtMs: nowMs,
|
|
1793
|
+
lastRateLimitedAtMs: null,
|
|
1794
|
+
})
|
|
1795
|
+
plan = livePlanLabel(ctx, creds) || plan
|
|
1646
1796
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1797
|
+
} // end fetch else-branch
|
|
1798
|
+
}
|
|
1799
|
+
} else {
|
|
1800
|
+
ctx.host.log.info("skipping live usage fetch for inference-only token")
|
|
1801
|
+
}
|
|
1802
|
+
} else if (sessionKey) {
|
|
1803
|
+
// ── Web mode ─────────────────────────────────────────────────────────
|
|
1804
|
+
const webResult = loadWebUsageData(ctx, sessionKey, nowMs)
|
|
1805
|
+
data = webResult.data
|
|
1806
|
+
quotaSource = "web"
|
|
1807
|
+
if (webResult.plan) {
|
|
1808
|
+
plan = webResult.plan
|
|
1658
1809
|
}
|
|
1659
1810
|
} else {
|
|
1660
|
-
ctx.host.log.info("
|
|
1661
|
-
}
|
|
1662
|
-
} else if (sessionKey) {
|
|
1663
|
-
// ── Web mode ─────────────────────────────────────────────────────────
|
|
1664
|
-
const webResult = loadWebUsageData(ctx, sessionKey, nowMs)
|
|
1665
|
-
data = webResult.data
|
|
1666
|
-
quotaSource = "web"
|
|
1667
|
-
if (webResult.plan) {
|
|
1668
|
-
plan = webResult.plan
|
|
1811
|
+
ctx.host.log.info("auto mode using available Claude usage history")
|
|
1669
1812
|
}
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1813
|
+
} catch (primaryError) {
|
|
1814
|
+
const cooldownUntil = Math.max(rateLimitedUntilMs, Number(liveCache && liveCache.rateLimitedUntilMs) || 0)
|
|
1815
|
+
const fallback = wantsAuto && nowMs >= cooldownUntil &&
|
|
1816
|
+
(readNativeQuota(ctx, creds, false) || readNativeQuota(ctx, creds, true))
|
|
1817
|
+
if (!fallback) throw primaryError
|
|
1818
|
+
data = fallback.data
|
|
1819
|
+
quotaSource = fallback.source
|
|
1820
|
+
quotaFetchedAtMs = fallback.fetchedAtMs
|
|
1821
|
+
rateLimited = false
|
|
1822
|
+
retryAfterSeconds = null
|
|
1672
1823
|
}
|
|
1673
1824
|
|
|
1674
1825
|
if (rateLimited && wantsAuto && sessionKey) {
|
|
@@ -1738,12 +1889,10 @@
|
|
|
1738
1889
|
? fmtRateLimitMinutes(retryAfterSeconds)
|
|
1739
1890
|
: null
|
|
1740
1891
|
const waitText = retryText
|
|
1741
|
-
? "
|
|
1742
|
-
: "
|
|
1892
|
+
? "Usage updates paused; retry in ~" + retryText
|
|
1893
|
+
: "Usage updates temporarily unavailable"
|
|
1743
1894
|
lines.unshift(ctx.line.badge({ label: "Status", text: waitText, color: "#f59e0b" }))
|
|
1744
|
-
const noteText =
|
|
1745
|
-
? "Live usage rate limited — retry in ~" + retryText
|
|
1746
|
-
: "Live usage rate limited — data may be stale"
|
|
1895
|
+
const noteText = "Anthropic limited usage checks, not your message allowance." + (data ? " Showing the last successful reading." : "")
|
|
1747
1896
|
lines.push(ctx.line.text({ label: "Note", value: noteText }))
|
|
1748
1897
|
} else if (lines.length === 0) {
|
|
1749
1898
|
if (noLiveCredentials && wantsAuto) throw "Not logged in and no local Claude usage found. Run `claude` to authenticate."
|
|
@@ -1751,8 +1900,9 @@
|
|
|
1751
1900
|
}
|
|
1752
1901
|
|
|
1753
1902
|
const finalCache = readLiveUsageCache(ctx)
|
|
1754
|
-
const fetchedAt = data &&
|
|
1903
|
+
const fetchedAt = data && ctx.util.toIso(quotaFetchedAtMs || finalCache && finalCache.usageFetchedAtMs)
|
|
1755
1904
|
return { plan: plan, lines: lines, fetchedAt: fetchedAt || ctx.nowIso,
|
|
1905
|
+
state: rateLimited ? "failed" : lines.length ? "ready" : "no-data",
|
|
1756
1906
|
source: data ? (rateLimited ? "cached" : quotaSource) : wantsApi ? "api" : "local" }
|
|
1757
1907
|
}
|
|
1758
1908
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{ "label": "Status", "url": "https://status.anthropic.com/" },
|
|
11
11
|
{ "label": "Console", "url": "https://console.anthropic.com/" }
|
|
12
12
|
],
|
|
13
|
-
"supportedModes": ["oauth", "web", "local", "api"],
|
|
13
|
+
"supportedModes": ["oauth", "cli", "web", "local", "api"],
|
|
14
14
|
"autoMode": "oauth",
|
|
15
15
|
"webUrl": "https://claude.ai",
|
|
16
16
|
"usageDashboardUrl": "https://claude.ai/settings/usage",
|
|
@@ -14,7 +14,23 @@
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function apiKey(ctx) {
|
|
17
|
-
|
|
17
|
+
const provider = ctx.provider || {};
|
|
18
|
+
if (Object.prototype.hasOwnProperty.call(provider, "apiKey")) return clean(provider.apiKey);
|
|
19
|
+
const scoped = provider.instanceId && provider.instanceId !== 'clinepass';
|
|
20
|
+
if (scoped) return null;
|
|
21
|
+
const explicit = env(ctx, "CLINEPASS_API_KEY") || env(ctx, "CLINE_API_KEY");
|
|
22
|
+
if (explicit) return explicit;
|
|
23
|
+
const settings = provider.settings || {};
|
|
24
|
+
const dir = env(ctx, 'CLINE_DATA_DIR') || (env(ctx, 'CLINE_DIR') || ctx.host.fs.homeDir + '/.cline') + '/data';
|
|
25
|
+
const file = clean(settings.authPath) || env(ctx, 'CLINE_PROVIDER_SETTINGS_PATH') || dir + '/settings/providers.json';
|
|
26
|
+
let saved;
|
|
27
|
+
try { saved = JSON.parse(ctx.host.fs.readText(file))?.providers?.cline?.settings; } catch (_) { return null; }
|
|
28
|
+
const access = clean(saved?.auth?.accessToken);
|
|
29
|
+
if (access) {
|
|
30
|
+
ctx.clineAuthSource = 'oauth';
|
|
31
|
+
return access.startsWith('workos:') ? access : 'workos:' + access;
|
|
32
|
+
}
|
|
33
|
+
return clean(saved?.apiKey) || clean(saved?.auth?.apiKey);
|
|
18
34
|
}
|
|
19
35
|
|
|
20
36
|
function windowFor(ctx, entry) {
|
|
@@ -39,7 +55,7 @@
|
|
|
39
55
|
|
|
40
56
|
function probe(ctx) {
|
|
41
57
|
var key = apiKey(ctx);
|
|
42
|
-
if (!key) throw "ClinePass API key
|
|
58
|
+
if (!key) throw "ClinePass credentials missing. Set an API key or run cline auth to sign in.";
|
|
43
59
|
|
|
44
60
|
var result = ctx.util.requestJson({
|
|
45
61
|
method: "GET",
|
|
@@ -64,7 +80,7 @@
|
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
if (!metrics.length) throw "ClinePass response missing five_hour window.";
|
|
67
|
-
return { displayName: "ClinePass", source: "api", plan: "API key", lines: metrics };
|
|
83
|
+
return { displayName: "ClinePass", source: ctx.clineAuthSource || "api", plan: ctx.clineAuthSource ? "Browser" : "API key", lines: metrics };
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
globalThis.__openusage_plugin = { id: "clinepass", probe: probe };
|