tokentracker-cli 0.74.0 → 0.74.2

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 (54) hide show
  1. package/dashboard/dist/assets/{ActivityHeatmap-DkoSqxrd.js → ActivityHeatmap-DGqoFvkZ.js} +1 -1
  2. package/dashboard/dist/assets/{Card-DleS-r54.js → Card-CKxmfosU.js} +1 -1
  3. package/dashboard/dist/assets/{CommunityStatsModal-DV41-F9D.js → CommunityStatsModal-C6h6BKVI.js} +1 -1
  4. package/dashboard/dist/assets/{ConfirmModal-Dgp07Uka.js → ConfirmModal-wqW-7Vs_.js} +1 -1
  5. package/dashboard/dist/assets/{DashboardPage-EKy2BzUx.js → DashboardPage-10hyXbuM.js} +1 -1
  6. package/dashboard/dist/assets/{DevicePage-eJInQ2UX.js → DevicePage-DosHEknI.js} +1 -1
  7. package/dashboard/dist/assets/{DialogClose-DEfesqFq.js → DialogClose-CRzzOKwV.js} +1 -1
  8. package/dashboard/dist/assets/DialogDescription-DuiVFMP6.js +1 -0
  9. package/dashboard/dist/assets/{DialogTitle-v1JrTvr4.js → DialogTitle-CgVdc49w.js} +1 -1
  10. package/dashboard/dist/assets/{FadeIn-hqIBbKaJ.js → FadeIn-CVkirQtu.js} +1 -1
  11. package/dashboard/dist/assets/{HeaderGithubStar-C4zauuP2.js → HeaderGithubStar-DHW75YEC.js} +1 -1
  12. package/dashboard/dist/assets/{IpCheckPage-CiRcccks.js → IpCheckPage-CMOjeIuH.js} +1 -1
  13. package/dashboard/dist/assets/{LandingPage-CbOHycnd.js → LandingPage-Cca1heHL.js} +97 -37
  14. package/dashboard/dist/assets/{LeaderboardAvatar-CQL1iOTS.js → LeaderboardAvatar-CVMcytB1.js} +1 -1
  15. package/dashboard/dist/assets/{LeaderboardPage-BuEp247d.js → LeaderboardPage-BEfsEAZY.js} +2 -2
  16. package/dashboard/dist/assets/{LeaderboardProfileModal-DT8hrPkI.js → LeaderboardProfileModal-5LuPOMoM.js} +1 -1
  17. package/dashboard/dist/assets/{LeaderboardProfilePage-B-M0AXMb.js → LeaderboardProfilePage-DUQOF16T.js} +1 -1
  18. package/dashboard/dist/assets/{LimitsPage-Df2es3Xs.js → LimitsPage-C6cPHrnU.js} +4 -4
  19. package/dashboard/dist/assets/{LocalOnlyNotice-DJIfKTKo.js → LocalOnlyNotice-B2SjP1L6.js} +1 -1
  20. package/dashboard/dist/assets/{LoginPage-DtaaIy_8.js → LoginPage-D0vP9cX4.js} +1 -1
  21. package/dashboard/dist/assets/{PopoverPopup-CqWH6aB9.js → PopoverPopup-AtVz9OM2.js} +1 -1
  22. package/dashboard/dist/assets/{ResetPasswordPage-DCH5GU1c.js → ResetPasswordPage-6eUhDnAl.js} +1 -1
  23. package/dashboard/dist/assets/{Select-BwyliVKJ.js → Select-Dt-nyHat.js} +1 -1
  24. package/dashboard/dist/assets/{SelectItemText-B5_DKKEv.js → SelectItemText-CFxs6Ipf.js} +1 -1
  25. package/dashboard/dist/assets/{SettingsPage-BoAFdOUa.js → SettingsPage-Ccv_PRTi.js} +1 -1
  26. package/dashboard/dist/assets/{SkillsPage-BVdre-kF.js → SkillsPage-Ckmq2qWZ.js} +1 -1
  27. package/dashboard/dist/assets/{WidgetsPage-CtjV405P.js → WidgetsPage-CuMpK7JS.js} +1 -1
  28. package/dashboard/dist/assets/{WrappedPage-DGmPdtHt.js → WrappedPage-C3sjJiS5.js} +1 -1
  29. package/dashboard/dist/assets/{agent-logos-DqevdWvx.js → agent-logos-CORiXh7-.js} +1 -1
  30. package/dashboard/dist/assets/{arrow-up-right-DA5Pybq1.js → arrow-up-right-D-CX3rHC.js} +1 -1
  31. package/dashboard/dist/assets/check-frbadzCO.js +1 -0
  32. package/dashboard/dist/assets/{copy-CsQRTd89.js → copy-BbbIDe0D.js} +1 -1
  33. package/dashboard/dist/assets/{download-TEHkAKT0.js → download-CnLKP9Xi.js} +1 -1
  34. package/dashboard/dist/assets/{format-tokens-CtFO_jkz.js → format-tokens-Q8buFewN.js} +1 -1
  35. package/dashboard/dist/assets/{info-Dxn3fiaX.js → info-Bg3JHknV.js} +1 -1
  36. package/dashboard/dist/assets/main--nUrKVF3.css +1 -0
  37. package/dashboard/dist/assets/{main-Dxi4sSXf.js → main-BZBZhXIl.js} +20 -17
  38. package/dashboard/dist/assets/{use-community-stats-BLNxr-bQ.js → use-community-stats-BBUKlTpE.js} +1 -1
  39. package/dashboard/dist/assets/{use-limits-display-prefs-CC7gb303.js → use-limits-display-prefs-Dnl2W8Rt.js} +1 -1
  40. package/dashboard/dist/assets/{use-native-settings-SYtjLlxZ.js → use-native-settings-DlQEd871.js} +1 -1
  41. package/dashboard/dist/assets/{use-ordered-list-_O8onJV7.js → use-ordered-list-4WtaZhJf.js} +1 -1
  42. package/dashboard/dist/assets/{use-quality-per-dollar-pref-tuspH_6n.js → use-quality-per-dollar-pref-CS0EFKxa.js} +1 -1
  43. package/dashboard/dist/assets/{use-usage-limits-whCVzQbf.js → use-usage-limits-CJ25xM-V.js} +1 -1
  44. package/dashboard/dist/assets/{useCurrency-BiDjYxfm.js → useCurrency-CaevR9XR.js} +1 -1
  45. package/dashboard/dist/assets/{useScrollLock-DK-ndHcw.js → useScrollLock-BLGPKPXr.js} +1 -1
  46. package/dashboard/dist/index.html +2 -2
  47. package/dashboard/dist/share.html +2 -2
  48. package/package.json +2 -2
  49. package/src/lib/pricing/seed-snapshot.json +1 -1
  50. package/src/lib/usage-limits.js +24 -0
  51. package/src/lib/zcode-limits.js +251 -15
  52. package/dashboard/dist/assets/DialogDescription-D6txiEc4.js +0 -1
  53. package/dashboard/dist/assets/check-BWzAT1oX.js +0 -1
  54. package/dashboard/dist/assets/main-DXM2UPde.css +0 -1
@@ -2929,6 +2929,12 @@ async function fetchUsageLimitsUncached({
2929
2929
  seven_day_opus: claudeResult.value.seven_day_opus,
2930
2930
  weekly_scoped: claudeResult.value.weekly_scoped,
2931
2931
  extra_usage: claudeResult.value.extra_usage,
2932
+ // A live read is current as of now. `stale`/`cached_at` are always present so
2933
+ // the client can render an "Updated Xm ago" age without guessing whether the
2934
+ // response was live or served from the disk-cache fallback (the cached paths
2935
+ // above already carry them). Mirrors the Codex live-success block below.
2936
+ stale: false,
2937
+ cached_at: new Date(nowMs).toISOString(),
2932
2938
  };
2933
2939
  writeClaudeLimitsCache(claude, { home, nowMs });
2934
2940
  clearClaudeRateLimitCooldown({ home });
@@ -2954,6 +2960,19 @@ async function fetchUsageLimitsUncached({
2954
2960
  }
2955
2961
  }
2956
2962
 
2963
+ // Expose the active 429 cool-down expiry (if any) on the Claude object so the
2964
+ // client can (a) show when the next refresh is due next to stale bars and
2965
+ // (b) schedule a one-shot refresh the instant the cool-down lifts instead of
2966
+ // waiting out the poll interval. Re-read here (not the pre-fetch value) so a
2967
+ // cool-down just armed by this cycle's 429 is included; a successful read above
2968
+ // cleared the file, so this is null in the happy path.
2969
+ if (claude.configured) {
2970
+ const claudeCooldownMs = readClaudeRateLimitRetryAtMs({ home, nowMs });
2971
+ if (claudeCooldownMs) {
2972
+ claude.retry_at = new Date(claudeCooldownMs).toISOString();
2973
+ }
2974
+ }
2975
+
2957
2976
  let codex;
2958
2977
  if (!codexToken) {
2959
2978
  codex = { configured: false };
@@ -2986,6 +3005,11 @@ async function fetchUsageLimitsUncached({
2986
3005
  spark_primary_window: codexResult.value.spark_primary_window,
2987
3006
  spark_secondary_window: codexResult.value.spark_secondary_window,
2988
3007
  reset_credits: codexResult.value.reset_credits,
3008
+ // Live read is current as of now; the stale fallback path above serves the
3009
+ // disk cache with `stale: true`. Always emitting both lets the client show a
3010
+ // data-age label uniformly (see the Claude live-success block).
3011
+ stale: false,
3012
+ cached_at: new Date(nowMs).toISOString(),
2989
3013
  };
2990
3014
  writeCodexLimitsCache(codex, { home, nowMs });
2991
3015
  }
@@ -4,6 +4,9 @@ const os = require("node:os");
4
4
  const path = require("node:path");
5
5
 
6
6
  const DEFAULT_BILLING_BASE_URL = "https://zcode.z.ai/api/v1/zcode-plan";
7
+ const DEFAULT_ZAI_MONITOR_BASE_URL = "https://api.z.ai";
8
+ const DEFAULT_BIGMODEL_MONITOR_BASE_URL = "https://bigmodel.cn";
9
+ const ZCODE_MONITOR_QUOTA_PATH = "/api/monitor/usage/quota/limit";
7
10
  const DEFAULT_ZCODE_APP_VERSION = "3.2.5";
8
11
  const DEFAULT_ZCODE_LOG_FALLBACK_MAX_AGE_MS = 6 * 60 * 60 * 1000;
9
12
 
@@ -92,6 +95,28 @@ function loadZcodeProviderAvailability({ home, env } = {}) {
92
95
  }
93
96
  }
94
97
 
98
+ function loadZcodeSelectedPlanProviderKeys({ home, env } = {}) {
99
+ const zcodeHome = resolveZcodeHome({ home, env });
100
+ const settingPath = path.join(zcodeHome, "v2", "setting.json");
101
+ if (!fs.existsSync(settingPath)) return [];
102
+ try {
103
+ const setting = JSON.parse(fs.readFileSync(settingPath, "utf8"));
104
+ const selected = setting?.modelProviderFamilySelectedKeys;
105
+ if (!selected || typeof selected !== "object") return [];
106
+ const domain = typeof setting?.providerFamilyDomain === "string" ? setting.providerFamilyDomain : "";
107
+ const domains = [domain, ...Object.keys(selected)].filter(Boolean);
108
+ const out = [];
109
+ for (const key of domains) {
110
+ const raw = typeof selected[key] === "string" ? selected[key].trim() : "";
111
+ const match = raw.match(/builtin:(?:bigmodel|zai)-(?:start|coding)-plan/);
112
+ if (match && !out.includes(match[0])) out.push(match[0]);
113
+ }
114
+ return out;
115
+ } catch (_error) {
116
+ return [];
117
+ }
118
+ }
119
+
95
120
  function resolveZcodeCredentialsPath({ home, env } = {}) {
96
121
  return path.join(resolveZcodeHome({ home, env }), "v2", "credentials.json");
97
122
  }
@@ -165,15 +190,62 @@ function isZcodeBuiltinPlanProvider(providerKey) {
165
190
  return /^builtin:(bigmodel|zai)-(start|coding)-plan$/.test(providerKey);
166
191
  }
167
192
 
193
+ function isZcodeCodingPlanProvider(providerKey) {
194
+ return /^builtin:(bigmodel|zai)-coding-plan$/.test(providerKey);
195
+ }
196
+
197
+ function isZcodeStartPlanProvider(providerKey) {
198
+ return /^builtin:(bigmodel|zai)-start-plan$/.test(providerKey);
199
+ }
200
+
201
+ function readEnvString(env, names) {
202
+ for (const name of names) {
203
+ const value = typeof env?.[name] === "string" ? env[name].trim() : "";
204
+ if (value) return value;
205
+ }
206
+ return "";
207
+ }
208
+
168
209
  function resolveZcodeProviderBillingBaseUrl(providerKey, provider, env = process.env) {
169
210
  const explicit = resolveZcodeBillingBaseUrl(env);
170
211
  if (explicit !== DEFAULT_BILLING_BASE_URL) return explicit;
171
- if (isZcodeBuiltinPlanProvider(providerKey)) return DEFAULT_BILLING_BASE_URL;
212
+ if (isZcodeStartPlanProvider(providerKey)) return DEFAULT_BILLING_BASE_URL;
172
213
  const baseUrl = typeof provider?.options?.baseURL === "string" ? provider.options.baseURL.trim() : "";
173
214
  if (/\/zcode-plan\/anthropic\/?$/i.test(baseUrl)) return baseUrl.replace(/\/anthropic\/?$/i, "");
174
215
  return null;
175
216
  }
176
217
 
218
+ function normalizeUrlOrigin(value) {
219
+ try {
220
+ const url = new URL(value);
221
+ return url.origin.replace(/\/$/, "");
222
+ } catch (_error) {
223
+ return null;
224
+ }
225
+ }
226
+
227
+ function resolveZcodeProviderQuotaUrl(providerKey, provider, env = process.env) {
228
+ if (!isZcodeCodingPlanProvider(providerKey)) return null;
229
+ const explicit = readEnvString(env, ["TOKENTRACKER_ZCODE_MONITOR_QUOTA_URL"]);
230
+ if (explicit) return explicit;
231
+
232
+ const baseUrl = typeof provider?.options?.baseURL === "string" ? provider.options.baseURL.trim() : "";
233
+ const baseOrigin = normalizeUrlOrigin(baseUrl);
234
+ const isZai = providerKey === "builtin:zai-coding-plan";
235
+ const origin = isZai
236
+ ? readEnvString(env, [
237
+ "TOKENTRACKER_ZCODE_ZAI_MONITOR_BASE_URL",
238
+ "ZAI_BUSINESS_BASE_URL",
239
+ "ZAI_PRODUCTION_BUSINESS_BASE_URL",
240
+ ]) || (baseOrigin && /(^|\.)api\.z\.ai$/i.test(new URL(baseOrigin).hostname) ? baseOrigin : DEFAULT_ZAI_MONITOR_BASE_URL)
241
+ : readEnvString(env, [
242
+ "TOKENTRACKER_ZCODE_BIGMODEL_MONITOR_BASE_URL",
243
+ "BIGMODEL_API_BASE_URL",
244
+ "BIGMODEL_PRODUCTION_API_BASE_URL",
245
+ ]) || DEFAULT_BIGMODEL_MONITOR_BASE_URL;
246
+ return `${origin.replace(/\/$/, "")}${ZCODE_MONITOR_QUOTA_PATH}`;
247
+ }
248
+
177
249
  function loadZcodeAuthCandidates({ home, env } = {}) {
178
250
  const zcodeHome = resolveZcodeHome({ home, env });
179
251
  const configPath = path.join(zcodeHome, "v2", "config.json");
@@ -190,8 +262,14 @@ function loadZcodeAuthCandidates({ home, env } = {}) {
190
262
  ];
191
263
  const availability = loadZcodeProviderAvailability({ home, env });
192
264
  const hasAvailability = Object.keys(availability).length > 0;
265
+ const selectedCandidates = loadZcodeSelectedPlanProviderKeys({ home, env })
266
+ .filter((key) => defaultCandidates.includes(key));
193
267
  const availableCandidates = defaultCandidates.filter((key) => availability?.[key]?.status === "available");
194
- const candidates = [...availableCandidates, ...defaultCandidates.filter((key) => !availableCandidates.includes(key))];
268
+ const candidates = [
269
+ ...selectedCandidates,
270
+ ...availableCandidates,
271
+ ...defaultCandidates,
272
+ ].filter((key, index, all) => all.indexOf(key) === index);
195
273
  const auths = [];
196
274
  for (const key of candidates) {
197
275
  const provider = providers[key];
@@ -200,6 +278,7 @@ function loadZcodeAuthCandidates({ home, env } = {}) {
200
278
  if (hasAvailability && availability?.[key]?.status && availability[key].status !== "available") continue;
201
279
  const apiKey = typeof provider?.options?.apiKey === "string" ? provider.options.apiKey.trim() : "";
202
280
  const billingBaseUrl = resolveZcodeProviderBillingBaseUrl(key, provider, env);
281
+ const quotaUrl = resolveZcodeProviderQuotaUrl(key, provider, env);
203
282
  const credentialApiKey = resolveZcodeCredentialAuth(key, { home, env });
204
283
  const authEntries = [
205
284
  credentialApiKey ? { apiKey: credentialApiKey, authSource: "credential:zcodejwttoken" } : null,
@@ -207,14 +286,16 @@ function loadZcodeAuthCandidates({ home, env } = {}) {
207
286
  ].filter(Boolean);
208
287
  const seenKeys = new Set();
209
288
  for (const entry of authEntries) {
210
- if (!billingBaseUrl || seenKeys.has(entry.apiKey)) continue;
289
+ if ((!billingBaseUrl && !quotaUrl) || seenKeys.has(entry.apiKey)) continue;
211
290
  seenKeys.add(entry.apiKey);
212
291
  auths.push({
213
292
  apiKey: entry.apiKey,
214
293
  auth_source: entry.authSource,
215
294
  providerKey: key,
295
+ planKind: isZcodeCodingPlanProvider(key) ? "coding-plan" : "start-plan",
216
296
  baseUrl: provider?.options?.baseURL || null,
217
297
  billingBaseUrl,
298
+ quotaUrl,
218
299
  availability: availability?.[key]?.status || null,
219
300
  });
220
301
  }
@@ -297,11 +378,13 @@ function normalizeZcodeBalanceResponse(body) {
297
378
  if (!balances.length) {
298
379
  return {
299
380
  server_time: zcodeValNumber(data.server_time),
381
+ plan_kind: "start-plan",
300
382
  plan_id: null,
301
383
  plan_label: null,
302
384
  buckets: [],
303
385
  primary_window: null,
304
386
  secondary_window: null,
387
+ tertiary_window: null,
305
388
  };
306
389
  }
307
390
 
@@ -336,11 +419,13 @@ function normalizeZcodeBalanceResponse(body) {
336
419
  const planId = typeof balances[0]?.plan_id === "string" ? balances[0].plan_id : null;
337
420
  return {
338
421
  server_time: serverTime,
422
+ plan_kind: "start-plan",
339
423
  plan_id: planId,
340
424
  plan_label: deriveZcodePlanLabel(planId),
341
425
  buckets: sorted,
342
426
  primary_window: sorted[0]?.window || null,
343
427
  secondary_window: sorted[1]?.window || null,
428
+ tertiary_window: sorted[2]?.window || null,
344
429
  };
345
430
  }
346
431
 
@@ -380,6 +465,136 @@ async function fetchZcodeBilling(apiKey, { fetchImpl = fetch, baseUrl, env, home
380
465
  return res.json();
381
466
  }
382
467
 
468
+ async function fetchZcodeCodingPlanQuota(apiKey, { fetchImpl = fetch, quotaUrl } = {}) {
469
+ const res = await fetchImpl(quotaUrl, {
470
+ method: "GET",
471
+ headers: {
472
+ authorization: apiKey,
473
+ Accept: "application/json",
474
+ },
475
+ });
476
+ if (res.status === 401 || res.status === 403) {
477
+ throw new Error("Not authenticated with ZCode coding plan. Run `zcode` in Terminal to log in.");
478
+ }
479
+ let body = null;
480
+ try {
481
+ body = await res.json();
482
+ } catch (_error) {
483
+ body = null;
484
+ }
485
+ if (!res.ok) {
486
+ const code = body?.code != null ? ` code=${body.code}` : "";
487
+ const msg = body?.msg || body?.message ? ` msg=${body.msg || body.message}` : "";
488
+ throw new Error(`ZCode coding plan API returned ${res.status}${code}${msg}`);
489
+ }
490
+ return body;
491
+ }
492
+
493
+ function quotaTimestampToIso(value) {
494
+ if (typeof value === "string" && value.trim()) {
495
+ const parsed = new Date(value.trim());
496
+ return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
497
+ }
498
+ const n = zcodeValNumber(value);
499
+ if (n == null || n <= 0) return null;
500
+ return new Date(n > 10_000_000_000 ? n : n * 1000).toISOString();
501
+ }
502
+
503
+ function findZcodeQuotaLimit(limits, type, unit, number = null) {
504
+ if (!Array.isArray(limits)) return null;
505
+ return (
506
+ limits.find((limit) => {
507
+ if (!limit || typeof limit !== "object") return false;
508
+ if (limit.type !== type) return false;
509
+ if (zcodeValNumber(limit.unit) !== unit) return false;
510
+ if (number == null) return true;
511
+ return zcodeValNumber(limit.number) === number;
512
+ }) || null
513
+ );
514
+ }
515
+
516
+ // ZCode 3.3.x coding-plan quota rows expose `percentage` as already-used %
517
+ // (0–100). `unit`/`number` identify the window (5h / weekly / tools), not a
518
+ // token total — deriving used% from usage/number turns an unused TIME_LIMIT
519
+ // (usage=100, number=1, percentage=0) into a false 100% bar.
520
+ function normalizeZcodeQuotaLimit(limit, { showName } = {}) {
521
+ if (!limit || typeof limit !== "object") return null;
522
+ const total = zcodeValNumber(limit.number);
523
+ const used = zcodeValNumber(limit.usage) ?? zcodeValNumber(limit.currentValue);
524
+ const remaining = zcodeValNumber(limit.remaining);
525
+ const rawPercentage = zcodeValNumber(limit.percentage);
526
+ let usedPercent = null;
527
+ if (rawPercentage != null) {
528
+ usedPercent = rawPercentage;
529
+ } else if (total != null && total > 0 && used != null) {
530
+ usedPercent = (used / total) * 100;
531
+ } else if (total != null && total > 0 && remaining != null && remaining <= total) {
532
+ usedPercent = ((total - remaining) / total) * 100;
533
+ }
534
+
535
+ const detail = Array.isArray(limit.usageDetails) ? limit.usageDetails.find((item) => item && typeof item === "object") : null;
536
+ const resolvedShowName =
537
+ (typeof showName === "string" && showName.trim())
538
+ || (typeof detail?.displayName === "string" && detail.displayName.trim())
539
+ || (typeof detail?.modelCode === "string" && detail.modelCode.trim())
540
+ || (typeof limit.type === "string" && limit.type.trim())
541
+ || "Coding plan";
542
+
543
+ return {
544
+ show_name: resolvedShowName,
545
+ entitlement_id: typeof limit.type === "string" ? limit.type : "",
546
+ total_units: rawPercentage != null ? null : total,
547
+ used_units: rawPercentage != null ? null : used,
548
+ remaining_units: rawPercentage != null ? null : remaining,
549
+ window: buildWindow({ usedPercent, resetAt: quotaTimestampToIso(limit.nextResetTime) }),
550
+ };
551
+ }
552
+
553
+ function normalizeZcodeCodingPlanQuotaResponse(body) {
554
+ const code = typeof body?.code === "number" ? body.code : null;
555
+ if (code !== null && code !== 0 && code !== 200) {
556
+ throw new Error(`ZCode coding plan API error: code=${code} msg=${body?.msg || body?.message || "unknown"}`);
557
+ }
558
+ if (body?.success === false) {
559
+ throw new Error(`ZCode coding plan API error: msg=${body?.msg || body?.message || "unknown"}`);
560
+ }
561
+ const data = body?.data;
562
+ if (!data || typeof data !== "object") {
563
+ throw new Error("Could not parse ZCode coding plan quota: missing data");
564
+ }
565
+ const limits = Array.isArray(data.limits) ? data.limits : [];
566
+ // Match ZCode's own sidebar: TOKENS_LIMIT(unit=3,number=5)=5h,
567
+ // TOKENS_LIMIT(unit=6)=weekly, TIME_LIMIT(unit=5,number=1)=tool calls.
568
+ const fiveHourLimit = findZcodeQuotaLimit(limits, "TOKENS_LIMIT", 3, 5);
569
+ const weeklyLimit = findZcodeQuotaLimit(limits, "TOKENS_LIMIT", 6);
570
+ const toolsLimit = findZcodeQuotaLimit(limits, "TIME_LIMIT", 5, 1);
571
+ const hasNamedWindows = Boolean(fiveHourLimit || weeklyLimit || toolsLimit);
572
+
573
+ let buckets;
574
+ if (hasNamedWindows) {
575
+ buckets = [
576
+ fiveHourLimit ? normalizeZcodeQuotaLimit(fiveHourLimit, { showName: "5h" }) : null,
577
+ weeklyLimit ? normalizeZcodeQuotaLimit(weeklyLimit, { showName: "Weekly" }) : null,
578
+ toolsLimit ? normalizeZcodeQuotaLimit(toolsLimit, { showName: "Tools" }) : null,
579
+ ].filter((bucket) => bucket?.window);
580
+ } else {
581
+ buckets = limits.map((limit) => normalizeZcodeQuotaLimit(limit)).filter((bucket) => bucket?.window);
582
+ buckets.sort((a, b) => (b.total_units || 0) - (a.total_units || 0));
583
+ }
584
+
585
+ const level = typeof data.level === "string" && data.level.trim() ? data.level.trim() : null;
586
+ return {
587
+ server_time: null,
588
+ plan_kind: "coding-plan",
589
+ plan_id: level,
590
+ plan_label: level ? deriveZcodePlanLabel(level) || level : "Coding",
591
+ buckets,
592
+ primary_window: buckets[0]?.window || null,
593
+ secondary_window: buckets[1]?.window || null,
594
+ tertiary_window: buckets[2]?.window || null,
595
+ };
596
+ }
597
+
383
598
  function parseZcodeLogTimestamp(line) {
384
599
  const match = line.match(/^\[(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})\]/);
385
600
  if (!match) return null;
@@ -433,7 +648,13 @@ function extractZcodeBalanceLogRecord(line) {
433
648
  }
434
649
  }
435
650
 
436
- function loadLatestZcodeBalanceFromLogs({ home, env = process.env, providerKeys = [], nowMs = Date.now() } = {}) {
651
+ function loadLatestZcodeBalanceFromLogs({
652
+ home,
653
+ env = process.env,
654
+ providerKeys = [],
655
+ nowMs = Date.now(),
656
+ requireProviderMatch = false,
657
+ } = {}) {
437
658
  if (env.TOKENTRACKER_ZCODE_DISABLE_LOG_FALLBACK === "1") return null;
438
659
  const maxAgeMs = parsePositiveInteger(env.TOKENTRACKER_ZCODE_LOG_MAX_AGE_MS, DEFAULT_ZCODE_LOG_FALLBACK_MAX_AGE_MS);
439
660
  const preferredProviders = new Set(providerKeys.filter(Boolean));
@@ -459,7 +680,8 @@ function loadLatestZcodeBalanceFromLogs({ home, env = process.env, providerKeys
459
680
  }
460
681
  }
461
682
  }
462
- return preferred || fallback;
683
+ if (preferred) return preferred;
684
+ return requireProviderMatch && preferredProviders.size > 0 ? null : fallback;
463
685
  }
464
686
 
465
687
  function zcodeLogFallbackResult(logRecord, errors = []) {
@@ -489,17 +711,26 @@ async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now
489
711
  let emptySuccess = null;
490
712
  for (const auth of authCandidates) {
491
713
  try {
492
- const body = await fetchZcodeBilling(auth.apiKey, {
493
- fetchImpl,
494
- baseUrl: auth.billingBaseUrl,
495
- env,
496
- home,
497
- });
498
- const apiCode = typeof body?.code === "number" ? body.code : null;
499
- if (apiCode !== null && apiCode !== 0) {
500
- throw new Error(`ZCode billing API error: code=${apiCode} msg=${body?.msg || "unknown"}`);
714
+ const body = auth.planKind === "coding-plan"
715
+ ? await fetchZcodeCodingPlanQuota(auth.apiKey, {
716
+ fetchImpl,
717
+ quotaUrl: auth.quotaUrl,
718
+ })
719
+ : await fetchZcodeBilling(auth.apiKey, {
720
+ fetchImpl,
721
+ baseUrl: auth.billingBaseUrl,
722
+ env,
723
+ home,
724
+ });
725
+ const normalized = auth.planKind === "coding-plan"
726
+ ? normalizeZcodeCodingPlanQuotaResponse(body)
727
+ : normalizeZcodeBalanceResponse(body);
728
+ if (auth.planKind !== "coding-plan") {
729
+ const apiCode = typeof body?.code === "number" ? body.code : null;
730
+ if (apiCode !== null && apiCode !== 0) {
731
+ throw new Error(`ZCode billing API error: code=${apiCode} msg=${body?.msg || "unknown"}`);
732
+ }
501
733
  }
502
- const normalized = normalizeZcodeBalanceResponse(body);
503
734
  const result = {
504
735
  configured: true,
505
736
  error: null,
@@ -521,6 +752,7 @@ async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now
521
752
  env,
522
753
  providerKeys: authCandidates.map((auth) => auth.providerKey),
523
754
  nowMs,
755
+ requireProviderMatch: authCandidates.some((auth) => auth.planKind === "coding-plan"),
524
756
  });
525
757
  if (logFallback) return zcodeLogFallbackResult(logFallback, errors);
526
758
  return {
@@ -536,13 +768,17 @@ module.exports = {
536
768
  resolveZcodeAppVersion,
537
769
  isZcodeInstalled,
538
770
  loadZcodeProviderAvailability,
771
+ loadZcodeSelectedPlanProviderKeys,
539
772
  loadZcodeCredential,
540
773
  loadLatestZcodeBalanceFromLogs,
541
774
  resolveZcodeProviderBillingBaseUrl,
775
+ resolveZcodeProviderQuotaUrl,
542
776
  loadZcodeAuthCandidates,
543
777
  loadZcodeApiKey,
544
778
  deriveZcodePlanLabel,
545
779
  normalizeZcodeBalanceResponse,
780
+ normalizeZcodeCodingPlanQuotaResponse,
546
781
  fetchZcodeBilling,
782
+ fetchZcodeCodingPlanQuota,
547
783
  fetchZcodeLimits,
548
784
  };
@@ -1 +0,0 @@
1
- import{r as n,b9 as a}from"./main-Dxi4sSXf.js";import{u as p}from"./DialogTitle-v1JrTvr4.js";import{u as c}from"./useScrollLock-DK-ndHcw.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,{store:i}=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
@@ -1 +0,0 @@
1
- import{c}from"./main-Dxi4sSXf.js";const e=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],t=c("check",e);export{t as C};