tokentracker-cli 0.72.1 → 0.74.0

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 (64) hide show
  1. package/README.de.md +5 -4
  2. package/README.ja.md +5 -4
  3. package/README.ko.md +5 -4
  4. package/README.md +5 -4
  5. package/README.zh-CN.md +5 -4
  6. package/dashboard/dist/assets/{ActivityHeatmap-CtDL5OPO.js → ActivityHeatmap-DkoSqxrd.js} +1 -1
  7. package/dashboard/dist/assets/{Card-C5lKqKNT.js → Card-DleS-r54.js} +1 -1
  8. package/dashboard/dist/assets/{CommunityStatsModal-XnEef7Ra.js → CommunityStatsModal-DV41-F9D.js} +1 -1
  9. package/dashboard/dist/assets/{ConfirmModal-CkZvn-sV.js → ConfirmModal-Dgp07Uka.js} +1 -1
  10. package/dashboard/dist/assets/{DashboardPage-TmZBLaB9.js → DashboardPage-EKy2BzUx.js} +1 -1
  11. package/dashboard/dist/assets/{DevicePage-BhDh3VIP.js → DevicePage-eJInQ2UX.js} +1 -1
  12. package/dashboard/dist/assets/{DialogClose-CddI1gcT.js → DialogClose-DEfesqFq.js} +1 -1
  13. package/dashboard/dist/assets/DialogDescription-D6txiEc4.js +1 -0
  14. package/dashboard/dist/assets/{DialogTitle-Cvpx9Lfv.js → DialogTitle-v1JrTvr4.js} +1 -1
  15. package/dashboard/dist/assets/{FadeIn-BojLJcFD.js → FadeIn-hqIBbKaJ.js} +1 -1
  16. package/dashboard/dist/assets/{HeaderGithubStar-BrSt1RIj.js → HeaderGithubStar-C4zauuP2.js} +1 -1
  17. package/dashboard/dist/assets/{IpCheckPage-DxuF-1tF.js → IpCheckPage-CiRcccks.js} +1 -1
  18. package/dashboard/dist/assets/{LandingPage-DmCjJVwh.js → LandingPage-CbOHycnd.js} +1 -1
  19. package/dashboard/dist/assets/{LeaderboardAvatar-B8SGUidW.js → LeaderboardAvatar-CQL1iOTS.js} +1 -1
  20. package/dashboard/dist/assets/{LeaderboardPage-D7CQCdDr.js → LeaderboardPage-BuEp247d.js} +2 -2
  21. package/dashboard/dist/assets/{LeaderboardProfileModal-Dv7CQVqB.js → LeaderboardProfileModal-DT8hrPkI.js} +1 -1
  22. package/dashboard/dist/assets/{LeaderboardProfilePage-BtGLbQnT.js → LeaderboardProfilePage-B-M0AXMb.js} +1 -1
  23. package/dashboard/dist/assets/{LimitsPage-DRf6h5_2.js → LimitsPage-Df2es3Xs.js} +1 -1
  24. package/dashboard/dist/assets/{LocalOnlyNotice-BnlODmxA.js → LocalOnlyNotice-DJIfKTKo.js} +1 -1
  25. package/dashboard/dist/assets/{LoginPage-CYXdV0X0.js → LoginPage-DtaaIy_8.js} +1 -1
  26. package/dashboard/dist/assets/{PopoverPopup-x5vYN3uF.js → PopoverPopup-CqWH6aB9.js} +1 -1
  27. package/dashboard/dist/assets/{ResetPasswordPage-D8ifLVwK.js → ResetPasswordPage-DCH5GU1c.js} +1 -1
  28. package/dashboard/dist/assets/{Select-CaaIuo5M.js → Select-BwyliVKJ.js} +1 -1
  29. package/dashboard/dist/assets/{SelectItemText-CdJ-E-d1.js → SelectItemText-B5_DKKEv.js} +1 -1
  30. package/dashboard/dist/assets/{SettingsPage-Dc7v5PEC.js → SettingsPage-BoAFdOUa.js} +1 -1
  31. package/dashboard/dist/assets/{SkillsPage-CRbEMatQ.js → SkillsPage-BVdre-kF.js} +1 -1
  32. package/dashboard/dist/assets/{WidgetsPage-xgy8mLMV.js → WidgetsPage-CtjV405P.js} +1 -1
  33. package/dashboard/dist/assets/{WrappedPage-BdpfOWJu.js → WrappedPage-DGmPdtHt.js} +1 -1
  34. package/dashboard/dist/assets/{agent-logos-CrxAAMko.js → agent-logos-DqevdWvx.js} +1 -1
  35. package/dashboard/dist/assets/{arrow-up-right-D23bZC75.js → arrow-up-right-DA5Pybq1.js} +1 -1
  36. package/dashboard/dist/assets/check-BWzAT1oX.js +1 -0
  37. package/dashboard/dist/assets/{copy-hiVk9JEl.js → copy-CsQRTd89.js} +1 -1
  38. package/dashboard/dist/assets/{download-CqLiBDG-.js → download-TEHkAKT0.js} +1 -1
  39. package/dashboard/dist/assets/{format-tokens-Ck5lRlFd.js → format-tokens-CtFO_jkz.js} +1 -1
  40. package/dashboard/dist/assets/{info-CxDHBJXB.js → info-Dxn3fiaX.js} +1 -1
  41. package/dashboard/dist/assets/main-Dxi4sSXf.js +1128 -0
  42. package/dashboard/dist/assets/{use-community-stats-CeaLHlCk.js → use-community-stats-BLNxr-bQ.js} +1 -1
  43. package/dashboard/dist/assets/{use-limits-display-prefs-CvDWYfjp.js → use-limits-display-prefs-CC7gb303.js} +1 -1
  44. package/dashboard/dist/assets/{use-native-settings-HuxDwmn6.js → use-native-settings-SYtjLlxZ.js} +1 -1
  45. package/dashboard/dist/assets/{use-ordered-list-KIV2M5tf.js → use-ordered-list-_O8onJV7.js} +1 -1
  46. package/dashboard/dist/assets/{use-quality-per-dollar-pref-BbsMhRJt.js → use-quality-per-dollar-pref-tuspH_6n.js} +1 -1
  47. package/dashboard/dist/assets/{use-usage-limits-COBOepfU.js → use-usage-limits-whCVzQbf.js} +1 -1
  48. package/dashboard/dist/assets/{useCurrency-DbBq0Fal.js → useCurrency-BiDjYxfm.js} +1 -1
  49. package/dashboard/dist/assets/{useScrollLock-C4eBSnib.js → useScrollLock-DK-ndHcw.js} +1 -1
  50. package/dashboard/dist/index.html +1 -1
  51. package/dashboard/dist/share.html +1 -1
  52. package/package.json +1 -1
  53. package/src/commands/status.js +47 -4
  54. package/src/commands/sync.js +72 -18
  55. package/src/lib/local-api.js +24 -2
  56. package/src/lib/pricing/curated-overrides.json +7 -2
  57. package/src/lib/pricing/seed-snapshot.json +1 -1
  58. package/src/lib/rollout.js +385 -0
  59. package/src/lib/sqlite-reader.js +24 -6
  60. package/src/lib/usage-limits.js +200 -29
  61. package/src/lib/zcode-limits.js +375 -40
  62. package/dashboard/dist/assets/DialogDescription-DeEet5bz.js +0 -1
  63. package/dashboard/dist/assets/check-fkZxgcVQ.js +0 -1
  64. package/dashboard/dist/assets/main-B0LYKUIY.js +0 -1127
@@ -1,8 +1,11 @@
1
1
  const fs = require("node:fs");
2
+ const crypto = require("node:crypto");
2
3
  const os = require("node:os");
3
4
  const path = require("node:path");
4
5
 
5
6
  const DEFAULT_BILLING_BASE_URL = "https://zcode.z.ai/api/v1/zcode-plan";
7
+ const DEFAULT_ZCODE_APP_VERSION = "3.2.5";
8
+ const DEFAULT_ZCODE_LOG_FALLBACK_MAX_AGE_MS = 6 * 60 * 60 * 1000;
6
9
 
7
10
  function resolveZcodeHome({ home, env = process.env } = {}) {
8
11
  if (typeof env.TOKENTRACKER_ZCODE_HOME === "string" && env.TOKENTRACKER_ZCODE_HOME.trim()) {
@@ -23,6 +26,51 @@ function resolveZcodeBillingBaseUrl(env = process.env) {
23
26
  return DEFAULT_BILLING_BASE_URL;
24
27
  }
25
28
 
29
+ function parsePositiveInteger(value, fallback) {
30
+ const n = Number(value);
31
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
32
+ }
33
+
34
+ function readZcodeAppVersionFromPlist(plistPath) {
35
+ try {
36
+ if (!fs.existsSync(plistPath)) return null;
37
+ const text = fs.readFileSync(plistPath, "utf8");
38
+ const keyIndex = text.indexOf("<key>CFBundleShortVersionString</key>");
39
+ if (keyIndex < 0) return null;
40
+ const match = text.slice(keyIndex).match(/<string>([^<]+)<\/string>/);
41
+ const version = typeof match?.[1] === "string" ? match[1].trim() : "";
42
+ return version || null;
43
+ } catch (_error) {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function resolveZcodeAppVersion({ home, env = process.env } = {}) {
49
+ const explicit =
50
+ typeof env.TOKENTRACKER_ZCODE_APP_VERSION === "string"
51
+ ? env.TOKENTRACKER_ZCODE_APP_VERSION.trim()
52
+ : "";
53
+ if (explicit) return explicit;
54
+
55
+ const appPath =
56
+ typeof env.TOKENTRACKER_ZCODE_APP_PATH === "string" && env.TOKENTRACKER_ZCODE_APP_PATH.trim()
57
+ ? env.TOKENTRACKER_ZCODE_APP_PATH.trim()
58
+ : null;
59
+ const candidates = appPath
60
+ ? [path.join(appPath, "Contents", "Info.plist")]
61
+ : [
62
+ "/Applications/ZCode.app/Contents/Info.plist",
63
+ path.join(home || os.homedir(), "Applications", "ZCode.app", "Contents", "Info.plist"),
64
+ ];
65
+ for (const plistPath of candidates) {
66
+ const version = readZcodeAppVersionFromPlist(plistPath);
67
+ if (version) return version;
68
+ }
69
+ // The API currently rejects balance requests without app_version. The value is
70
+ // not used for local accounting, so keep a conservative fallback for CLI-only installs.
71
+ return DEFAULT_ZCODE_APP_VERSION;
72
+ }
73
+
26
74
  function isZcodeInstalled({ home, env } = {}) {
27
75
  const zcodeHome = resolveZcodeHome({ home, env });
28
76
  const configPath = path.join(zcodeHome, "v2", "config.json");
@@ -31,34 +79,173 @@ function isZcodeInstalled({ home, env } = {}) {
31
79
  return fs.existsSync(dbPath);
32
80
  }
33
81
 
34
- function loadZcodeApiKey({ home, env } = {}) {
82
+ function loadZcodeProviderAvailability({ home, env } = {}) {
83
+ const zcodeHome = resolveZcodeHome({ home, env });
84
+ const cachePath = path.join(zcodeHome, "v2", "coding-plan-cache.json");
85
+ if (!fs.existsSync(cachePath)) return {};
86
+ try {
87
+ const cache = JSON.parse(fs.readFileSync(cachePath, "utf8"));
88
+ const items = cache?.entryStatus?.items;
89
+ return items && typeof items === "object" ? items : {};
90
+ } catch (_error) {
91
+ return {};
92
+ }
93
+ }
94
+
95
+ function resolveZcodeCredentialsPath({ home, env } = {}) {
96
+ return path.join(resolveZcodeHome({ home, env }), "v2", "credentials.json");
97
+ }
98
+
99
+ function createZcodeCredentialSecret({ home, env = process.env } = {}) {
100
+ if (typeof env.ZCODE_CREDENTIAL_SECRET === "string" && env.ZCODE_CREDENTIAL_SECRET) {
101
+ return env.ZCODE_CREDENTIAL_SECRET;
102
+ }
103
+ let username = "";
104
+ try {
105
+ username = os.userInfo().username || "";
106
+ } catch (_error) {
107
+ username = "";
108
+ }
109
+ return `zcode-credential-fallback:${process.platform}:${home || os.homedir()}:${username}`;
110
+ }
111
+
112
+ function decryptZcodeCredentialValue(value, { home, env } = {}) {
113
+ if (typeof value !== "string") return null;
114
+ if (!value.startsWith("enc:v1:")) return value;
115
+ const encoded = value.slice("enc:v1:".length);
116
+ const parts = encoded.split(".");
117
+ if (parts.length !== 3) return null;
118
+ try {
119
+ const [ivPart, tagPart, encryptedPart] = parts;
120
+ const iv = Buffer.from(ivPart, "base64url");
121
+ const encrypted = Buffer.from(encryptedPart, "base64url");
122
+ const tag = Buffer.from(tagPart, "base64url");
123
+ const key = crypto.createHash("sha256").update(createZcodeCredentialSecret({ home, env })).digest();
124
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
125
+ decipher.setAuthTag(tag);
126
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
127
+ } catch (_error) {
128
+ return null;
129
+ }
130
+ }
131
+
132
+ function loadZcodeCredentials({ home, env } = {}) {
133
+ const credentialsPath = resolveZcodeCredentialsPath({ home, env });
134
+ if (!fs.existsSync(credentialsPath)) return {};
135
+ try {
136
+ const raw = JSON.parse(fs.readFileSync(credentialsPath, "utf8"));
137
+ return raw && typeof raw === "object" ? raw : {};
138
+ } catch (_error) {
139
+ return {};
140
+ }
141
+ }
142
+
143
+ function loadZcodeCredential(name, { home, env } = {}) {
144
+ const credentials = loadZcodeCredentials({ home, env });
145
+ const decrypted = decryptZcodeCredentialValue(credentials?.[name], { home, env });
146
+ return typeof decrypted === "string" && decrypted.trim() ? decrypted.trim() : "";
147
+ }
148
+
149
+ function loadZcodeActiveProvider({ home, env } = {}) {
150
+ return loadZcodeCredential("oauth:active_provider", { home, env });
151
+ }
152
+
153
+ function resolveZcodeCredentialAuth(providerKey, { home, env } = {}) {
154
+ const activeProvider = loadZcodeActiveProvider({ home, env });
155
+ if (
156
+ (providerKey === "builtin:zai-start-plan" && activeProvider === "zai") ||
157
+ (providerKey === "builtin:bigmodel-start-plan" && activeProvider === "bigmodel")
158
+ ) {
159
+ return loadZcodeCredential("zcodejwttoken", { home, env });
160
+ }
161
+ return "";
162
+ }
163
+
164
+ function isZcodeBuiltinPlanProvider(providerKey) {
165
+ return /^builtin:(bigmodel|zai)-(start|coding)-plan$/.test(providerKey);
166
+ }
167
+
168
+ function resolveZcodeProviderBillingBaseUrl(providerKey, provider, env = process.env) {
169
+ const explicit = resolveZcodeBillingBaseUrl(env);
170
+ if (explicit !== DEFAULT_BILLING_BASE_URL) return explicit;
171
+ if (isZcodeBuiltinPlanProvider(providerKey)) return DEFAULT_BILLING_BASE_URL;
172
+ const baseUrl = typeof provider?.options?.baseURL === "string" ? provider.options.baseURL.trim() : "";
173
+ if (/\/zcode-plan\/anthropic\/?$/i.test(baseUrl)) return baseUrl.replace(/\/anthropic\/?$/i, "");
174
+ return null;
175
+ }
176
+
177
+ function loadZcodeAuthCandidates({ home, env } = {}) {
35
178
  const zcodeHome = resolveZcodeHome({ home, env });
36
179
  const configPath = path.join(zcodeHome, "v2", "config.json");
37
- if (!fs.existsSync(configPath)) return null;
180
+ if (!fs.existsSync(configPath)) return [];
38
181
  try {
39
182
  const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
40
- if (!config || typeof config !== "object") return null;
183
+ if (!config || typeof config !== "object") return [];
41
184
  const providers = config.provider || {};
42
- // Try the active start-plan first, then coding-plan variants
43
- const candidates = [
185
+ const defaultCandidates = [
44
186
  "builtin:bigmodel-start-plan",
45
187
  "builtin:zai-start-plan",
46
188
  "builtin:bigmodel-coding-plan",
47
189
  "builtin:zai-coding-plan",
48
190
  ];
191
+ const availability = loadZcodeProviderAvailability({ home, env });
192
+ const hasAvailability = Object.keys(availability).length > 0;
193
+ const availableCandidates = defaultCandidates.filter((key) => availability?.[key]?.status === "available");
194
+ const candidates = [...availableCandidates, ...defaultCandidates.filter((key) => !availableCandidates.includes(key))];
195
+ const auths = [];
49
196
  for (const key of candidates) {
50
197
  const provider = providers[key];
51
198
  if (!provider || typeof provider !== "object") continue;
52
199
  if (provider.enabled === false) continue;
200
+ if (hasAvailability && availability?.[key]?.status && availability[key].status !== "available") continue;
53
201
  const apiKey = typeof provider?.options?.apiKey === "string" ? provider.options.apiKey.trim() : "";
54
- if (apiKey) return { apiKey, providerKey: key, baseUrl: provider?.options?.baseURL || null };
202
+ const billingBaseUrl = resolveZcodeProviderBillingBaseUrl(key, provider, env);
203
+ const credentialApiKey = resolveZcodeCredentialAuth(key, { home, env });
204
+ const authEntries = [
205
+ credentialApiKey ? { apiKey: credentialApiKey, authSource: "credential:zcodejwttoken" } : null,
206
+ apiKey ? { apiKey, authSource: "provider:config" } : null,
207
+ ].filter(Boolean);
208
+ const seenKeys = new Set();
209
+ for (const entry of authEntries) {
210
+ if (!billingBaseUrl || seenKeys.has(entry.apiKey)) continue;
211
+ seenKeys.add(entry.apiKey);
212
+ auths.push({
213
+ apiKey: entry.apiKey,
214
+ auth_source: entry.authSource,
215
+ providerKey: key,
216
+ baseUrl: provider?.options?.baseURL || null,
217
+ billingBaseUrl,
218
+ availability: availability?.[key]?.status || null,
219
+ });
220
+ }
55
221
  }
56
- return null;
222
+ return auths;
57
223
  } catch (_error) {
58
- return null;
224
+ return [];
59
225
  }
60
226
  }
61
227
 
228
+ function loadZcodeApiKey({ home, env } = {}) {
229
+ return loadZcodeAuthCandidates({ home, env })[0] || null;
230
+ }
231
+
232
+ function buildZcodeSourceHeaders({ home, env } = {}) {
233
+ const headers = {
234
+ "User-Agent": `ZCode/${resolveZcodeAppVersion({ home, env })}`,
235
+ "HTTP-Referer": "https://zcode.z.ai/",
236
+ "X-ZCode-App-Version": resolveZcodeAppVersion({ home, env }),
237
+ "X-Platform": process.platform,
238
+ "X-Release-Channel": "stable",
239
+ "X-Client-Language": Intl.DateTimeFormat().resolvedOptions().locale || "en-US",
240
+ "X-Client-Timezone": Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
241
+ "X-Os-Category": process.platform,
242
+ "X-Os-Version": os.release(),
243
+ };
244
+ const deviceMid = loadZcodeCredential("zcodefeedbackclientid", { home, env });
245
+ if (deviceMid) headers["X-Device-Mid"] = deviceMid;
246
+ return headers;
247
+ }
248
+
62
249
  function zcodeValNumber(value) {
63
250
  if (value == null) return null;
64
251
  const n = Number(value);
@@ -108,7 +295,14 @@ function normalizeZcodeBalanceResponse(body) {
108
295
 
109
296
  const balances = Array.isArray(data.balances) ? data.balances : [];
110
297
  if (!balances.length) {
111
- throw new Error("Could not parse ZCode balance: no balance buckets");
298
+ return {
299
+ server_time: zcodeValNumber(data.server_time),
300
+ plan_id: null,
301
+ plan_label: null,
302
+ buckets: [],
303
+ primary_window: null,
304
+ secondary_window: null,
305
+ };
112
306
  }
113
307
 
114
308
  const serverTime = zcodeValNumber(data.server_time);
@@ -150,61 +344,202 @@ function normalizeZcodeBalanceResponse(body) {
150
344
  };
151
345
  }
152
346
 
153
- async function fetchZcodeBilling(apiKey, { fetchImpl = fetch, baseUrl, env } = {}) {
347
+ async function fetchZcodeBilling(apiKey, { fetchImpl = fetch, baseUrl, env, home } = {}) {
154
348
  const root = (baseUrl || resolveZcodeBillingBaseUrl(env)).replace(/\/$/, "");
155
- const res = await fetchImpl(`${root}/billing/balance`, {
349
+ const url = new URL(`${root}/billing/balance`);
350
+ const appVersion = resolveZcodeAppVersion({ home, env });
351
+ if (appVersion) url.searchParams.set("app_version", appVersion);
352
+ const headers = {
353
+ Authorization: `Bearer ${apiKey}`,
354
+ Accept: "application/json",
355
+ };
356
+ if (url.origin === new URL(DEFAULT_BILLING_BASE_URL).origin) {
357
+ Object.assign(headers, buildZcodeSourceHeaders({ home, env }));
358
+ }
359
+ const res = await fetchImpl(url.toString(), {
156
360
  method: "GET",
157
- headers: {
158
- Authorization: `Bearer ${apiKey}`,
159
- Accept: "application/json",
160
- },
361
+ headers,
161
362
  });
162
363
  if (res.status === 401 || res.status === 403) {
163
364
  throw new Error("Not authenticated with ZCode. Run `zcode` in Terminal to log in.");
164
365
  }
165
366
  if (!res.ok) {
166
- throw new Error(`ZCode billing API returned ${res.status}`);
367
+ let detail = "";
368
+ try {
369
+ const body = await res.json();
370
+ if (body && typeof body === "object") {
371
+ const code = body.code != null ? ` code=${body.code}` : "";
372
+ const msg = body.msg ? ` msg=${body.msg}` : "";
373
+ detail = `${code}${msg}`;
374
+ }
375
+ } catch (_error) {
376
+ detail = "";
377
+ }
378
+ throw new Error(`ZCode billing API returned ${res.status}${detail}`);
167
379
  }
168
380
  return res.json();
169
381
  }
170
382
 
171
- async function fetchZcodeLimits({ home, env, fetchImpl = fetch } = {}) {
383
+ function parseZcodeLogTimestamp(line) {
384
+ const match = line.match(/^\[(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3})\]/);
385
+ if (!match) return null;
386
+ const [, year, month, day, hour, minute, second, ms] = match;
387
+ const timestamp = new Date(
388
+ Number(year),
389
+ Number(month) - 1,
390
+ Number(day),
391
+ Number(hour),
392
+ Number(minute),
393
+ Number(second),
394
+ Number(ms),
395
+ );
396
+ const time = timestamp.getTime();
397
+ return Number.isFinite(time) ? time : null;
398
+ }
399
+
400
+ function formatZcodeLogDate(date) {
401
+ const y = date.getFullYear();
402
+ const m = String(date.getMonth() + 1).padStart(2, "0");
403
+ const d = String(date.getDate()).padStart(2, "0");
404
+ return `${y}-${m}-${d}`;
405
+ }
406
+
407
+ function recentZcodeLogPaths({ home, env, nowMs = Date.now() } = {}) {
408
+ const logsDir = path.join(resolveZcodeHome({ home, env }), "v2", "logs");
409
+ const today = new Date(nowMs);
410
+ const yesterday = new Date(nowMs - 24 * 60 * 60 * 1000);
411
+ return [today, yesterday].map((d) => path.join(logsDir, `${formatZcodeLogDate(d)}.log`));
412
+ }
413
+
414
+ function extractZcodeBalanceLogRecord(line) {
415
+ if (!line.includes("[usage-stats] billing/balance 请求完成")) return null;
416
+ const jsonStart = line.indexOf("{");
417
+ if (jsonStart < 0) return null;
418
+ try {
419
+ const entry = JSON.parse(line.slice(jsonStart));
420
+ const body = entry?.payload;
421
+ if (!entry?.success || entry?.code !== 0 || !body || body?.code !== 0) return null;
422
+ if (!Array.isArray(body?.data?.balances) || body.data.balances.length === 0) return null;
423
+ const timestampMs = parseZcodeLogTimestamp(line);
424
+ if (timestampMs == null) return null;
425
+ return {
426
+ body,
427
+ providerKey: typeof entry.providerId === "string" ? entry.providerId : null,
428
+ timestampMs,
429
+ log_timestamp: new Date(timestampMs).toISOString(),
430
+ };
431
+ } catch (_error) {
432
+ return null;
433
+ }
434
+ }
435
+
436
+ function loadLatestZcodeBalanceFromLogs({ home, env = process.env, providerKeys = [], nowMs = Date.now() } = {}) {
437
+ if (env.TOKENTRACKER_ZCODE_DISABLE_LOG_FALLBACK === "1") return null;
438
+ const maxAgeMs = parsePositiveInteger(env.TOKENTRACKER_ZCODE_LOG_MAX_AGE_MS, DEFAULT_ZCODE_LOG_FALLBACK_MAX_AGE_MS);
439
+ const preferredProviders = new Set(providerKeys.filter(Boolean));
440
+ let preferred = null;
441
+ let fallback = null;
442
+ for (const logPath of recentZcodeLogPaths({ home, env, nowMs })) {
443
+ if (!fs.existsSync(logPath)) continue;
444
+ let text = "";
445
+ try {
446
+ text = fs.readFileSync(logPath, "utf8");
447
+ } catch (_error) {
448
+ continue;
449
+ }
450
+ const lines = text.split(/\r?\n/);
451
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
452
+ const record = extractZcodeBalanceLogRecord(lines[i]);
453
+ if (!record) continue;
454
+ const ageMs = nowMs - record.timestampMs;
455
+ if (ageMs < 0 || ageMs > maxAgeMs) continue;
456
+ if (!fallback || record.timestampMs > fallback.timestampMs) fallback = record;
457
+ if (preferredProviders.has(record.providerKey) && (!preferred || record.timestampMs > preferred.timestampMs)) {
458
+ preferred = record;
459
+ }
460
+ }
461
+ }
462
+ return preferred || fallback;
463
+ }
464
+
465
+ function zcodeLogFallbackResult(logRecord, errors = []) {
466
+ if (!logRecord) return null;
467
+ return {
468
+ configured: true,
469
+ error: null,
470
+ source: "zcode-log",
471
+ provider_key: logRecord.providerKey,
472
+ log_timestamp: logRecord.log_timestamp,
473
+ provider_errors: errors,
474
+ ...normalizeZcodeBalanceResponse(logRecord.body),
475
+ };
476
+ }
477
+
478
+ async function fetchZcodeLimits({ home, env, fetchImpl = fetch, nowMs = Date.now() } = {}) {
172
479
  if (!isZcodeInstalled({ home, env })) {
173
480
  return { configured: false };
174
481
  }
175
- const auth = loadZcodeApiKey({ home, env });
176
- if (!auth) {
482
+ const authCandidates = loadZcodeAuthCandidates({ home, env });
483
+ if (!authCandidates.length) {
484
+ const logFallback = loadLatestZcodeBalanceFromLogs({ home, env, nowMs });
485
+ if (logFallback) return zcodeLogFallbackResult(logFallback, []);
177
486
  return { configured: false };
178
487
  }
179
- try {
180
- const body = await fetchZcodeBilling(auth.apiKey, {
181
- fetchImpl,
182
- // Coding-plan baseURL is ".../zcode-plan/anthropic"; strip the trailing
183
- // "/anthropic" (with or without a trailing slash) to reach the billing root.
184
- baseUrl: auth.baseUrl ? auth.baseUrl.replace(/\/anthropic\/?$/, "") : undefined,
185
- env,
186
- });
187
- const apiCode = typeof body?.code === "number" ? body.code : null;
188
- if (apiCode !== null && apiCode !== 0) {
189
- throw new Error(`ZCode billing API error: code=${apiCode} msg=${body?.msg || "unknown"}`);
488
+ const errors = [];
489
+ let emptySuccess = null;
490
+ for (const auth of authCandidates) {
491
+ 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"}`);
501
+ }
502
+ const normalized = normalizeZcodeBalanceResponse(body);
503
+ const result = {
504
+ configured: true,
505
+ error: null,
506
+ provider_key: auth.providerKey,
507
+ ...normalized,
508
+ };
509
+ if (Array.isArray(normalized.buckets) && normalized.buckets.length === 0 && authCandidates.length > 1) {
510
+ emptySuccess = emptySuccess || result;
511
+ continue;
512
+ }
513
+ return result;
514
+ } catch (error) {
515
+ errors.push(`${auth.providerKey}: ${error?.message || "Unknown error"}`);
190
516
  }
191
- return {
192
- configured: true,
193
- error: null,
194
- ...normalizeZcodeBalanceResponse(body),
195
- };
196
- } catch (error) {
197
- return {
198
- configured: true,
199
- error: error?.message || "Unknown error",
200
- };
201
517
  }
518
+ if (emptySuccess) return emptySuccess;
519
+ const logFallback = loadLatestZcodeBalanceFromLogs({
520
+ home,
521
+ env,
522
+ providerKeys: authCandidates.map((auth) => auth.providerKey),
523
+ nowMs,
524
+ });
525
+ if (logFallback) return zcodeLogFallbackResult(logFallback, errors);
526
+ return {
527
+ configured: true,
528
+ error: errors[0] || "Unknown error",
529
+ provider_errors: errors,
530
+ };
202
531
  }
203
532
 
204
533
  module.exports = {
205
534
  resolveZcodeHome,
206
535
  resolveZcodeBillingBaseUrl,
536
+ resolveZcodeAppVersion,
207
537
  isZcodeInstalled,
538
+ loadZcodeProviderAvailability,
539
+ loadZcodeCredential,
540
+ loadLatestZcodeBalanceFromLogs,
541
+ resolveZcodeProviderBillingBaseUrl,
542
+ loadZcodeAuthCandidates,
208
543
  loadZcodeApiKey,
209
544
  deriveZcodePlanLabel,
210
545
  normalizeZcodeBalanceResponse,
@@ -1 +0,0 @@
1
- import{r as n,b9 as a}from"./main-B0LYKUIY.js";import{u as p}from"./DialogTitle-Cvpx9Lfv.js";import{u as c}from"./useScrollLock-C4eBSnib.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-B0LYKUIY.js";const e=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],t=c("check",e);export{t as C};