tokentracker-cli 1.0.6 → 1.1.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 (110) hide show
  1. package/README.de.md +6 -5
  2. package/README.ja.md +6 -5
  3. package/README.ko.md +6 -5
  4. package/README.md +9 -7
  5. package/README.zh-CN.md +6 -5
  6. package/dashboard/dist/assets/{AchievementBadge-CaAqGeBX.js → AchievementBadge-CKSLpTB6.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-BoiTFuxp.js → AchievementsPage-r42Lu6ll.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-CYiF0QS1.js → AchievementsSection-1L_uoCV4.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-BH3jTMAq.js → BadgeDetailModal-DlYT33qj.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-BCn8ladQ.js → BotAnimated-BJilDdvD.js} +1 -1
  11. package/dashboard/dist/assets/{Card-Cf0-OFz3.js → Card-DfMS7eJT.js} +1 -1
  12. package/dashboard/dist/assets/{ClawdAnimated-B32inDYq.js → ClawdAnimated-B2IKU3oX.js} +2 -2
  13. package/dashboard/dist/assets/{CommandPalette-BOCXhSoJ.js → CommandPalette-BQAnKMQp.js} +1 -1
  14. package/dashboard/dist/assets/{CommunityStatsModal-wj3ntE0k.js → CommunityStatsModal-DrYW6eRx.js} +1 -1
  15. package/dashboard/dist/assets/{ConfirmModal-BtXy5Tpg.js → ConfirmModal-DzIeh5dx.js} +1 -1
  16. package/dashboard/dist/assets/{Controls-C6R_k_Yo.js → Controls-Ce-II3Yn.js} +1 -1
  17. package/dashboard/dist/assets/{DashboardPage-3YAD-Ps-.js → DashboardPage-CeQYNbXo.js} +1 -1
  18. package/dashboard/dist/assets/{DevicePage-oQdvRuea.js → DevicePage-DmoCLkPn.js} +1 -1
  19. package/dashboard/dist/assets/{DialogClose-DGg7gIgR.js → DialogClose-DhDGRv1X.js} +1 -1
  20. package/dashboard/dist/assets/DialogDescription-1rzvf7wG.js +1 -0
  21. package/dashboard/dist/assets/{DialogTitle-CRJVqO3Z.js → DialogTitle-BI2-gsHD.js} +1 -1
  22. package/dashboard/dist/assets/{FadeIn-Bal-Jhbn.js → FadeIn-Bm7hEdb4.js} +1 -1
  23. package/dashboard/dist/assets/{HeaderGithubStar-CoxP2fqd.js → HeaderGithubStar-BDAWv2_t.js} +1 -1
  24. package/dashboard/dist/assets/{HoverTooltip-DAQKPLQl.js → HoverTooltip-Z8xawE-X.js} +1 -1
  25. package/dashboard/dist/assets/{IpCheckPage-JbfQR0w5.js → IpCheckPage-BjmCHK-1.js} +1 -1
  26. package/dashboard/dist/assets/{LandingPage-DjKmw_W1.js → LandingPage-DPfKTaC7.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardAvatar-D3jdY9hy.js → LeaderboardAvatar-Cd0_hasU.js} +1 -1
  28. package/dashboard/dist/assets/{LeaderboardPage-fU-M_RcC.js → LeaderboardPage-FbGCyz1h.js} +2 -2
  29. package/dashboard/dist/assets/{LeaderboardProfileModal-Db_AD0nx.js → LeaderboardProfileModal-CzL7x0ts.js} +3 -3
  30. package/dashboard/dist/assets/{LeaderboardProfilePage-CQ5kXnPL.js → LeaderboardProfilePage-CRU4_vJ_.js} +1 -1
  31. package/dashboard/dist/assets/{LimitsPage-DSVL32bb.js → LimitsPage-C8j3oSMF.js} +1 -1
  32. package/dashboard/dist/assets/{LocalOnlyNotice-LgNo6vft.js → LocalOnlyNotice-DMuYjLqc.js} +1 -1
  33. package/dashboard/dist/assets/{LoginCard-BwEFa4pX.js → LoginCard-BTT8hq7T.js} +1 -1
  34. package/dashboard/dist/assets/{LoginModal-rWVKapY3.js → LoginModal-DFyh1GeQ.js} +1 -1
  35. package/dashboard/dist/assets/{LoginPage-DEeIX36-.js → LoginPage-Cylxxqn4.js} +1 -1
  36. package/dashboard/dist/assets/{PetPage-81XOQqLW.js → PetPage-CGgadvAb.js} +1 -1
  37. package/dashboard/dist/assets/{PopoverPopup-nU1lNHRX.js → PopoverPopup-DiBUe7yt.js} +1 -1
  38. package/dashboard/dist/assets/{ProviderIcon-B-OZcL4W.js → ProviderIcon-_jz2BcWs.js} +1 -1
  39. package/dashboard/dist/assets/{ResetPasswordPage-CV3LLQRl.js → ResetPasswordPage-Fv4MB_xt.js} +1 -1
  40. package/dashboard/dist/assets/{SegmentedControl-DnXK5veD.js → SegmentedControl-yi9ALUzr.js} +1 -1
  41. package/dashboard/dist/assets/{Select-CyV3nxTO.js → Select-CiF8kiQJ.js} +1 -1
  42. package/dashboard/dist/assets/{SelectItemText-B9WQ2B5j.js → SelectItemText-Dbzv_uem.js} +1 -1
  43. package/dashboard/dist/assets/{ServiceStatusPage-CjfvzEOn.js → ServiceStatusPage-nIhZrGkz.js} +1 -1
  44. package/dashboard/dist/assets/{SessionsPage-BXTr3S8x.js → SessionsPage-BSyoUCDc.js} +1 -1
  45. package/dashboard/dist/assets/{SettingsPage-C-O5617w.js → SettingsPage-Cl2eEX84.js} +2 -2
  46. package/dashboard/dist/assets/{SkillsPage-DSc5O7u7.js → SkillsPage-Cl8kb4jo.js} +1 -1
  47. package/dashboard/dist/assets/{TokenGalaxy-DIbAWJfY.js → TokenGalaxy-Da8tynGB.js} +1 -1
  48. package/dashboard/dist/assets/{ToolbarRootContext-B7EwUsvt.js → ToolbarRootContext-IQUwRnDp.js} +1 -1
  49. package/dashboard/dist/assets/{TrendMonitor-CeTPoZhn.js → TrendMonitor-lpOCu-La.js} +1 -1
  50. package/dashboard/dist/assets/{WidgetsPage-DycR-Xy_.js → WidgetsPage-Cmby6Cw0.js} +1 -1
  51. package/dashboard/dist/assets/{WrappedPage-Buz1wzSM.js → WrappedPage-CE3GkR5a.js} +1 -1
  52. package/dashboard/dist/assets/{__vite-browser-external-_RSGXjh3.js → __vite-browser-external-dDN0mi9f.js} +3 -3
  53. package/dashboard/dist/assets/{agent-logos-B0aXIO3B.js → agent-logos-D9dYPUzL.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-up-right--SGPnId4.js → arrow-up-right-DwAAGP7p.js} +1 -1
  55. package/dashboard/dist/assets/{check-CJRaiH3e.js → check-D1Lq-9ts.js} +1 -1
  56. package/dashboard/dist/assets/{composite-Bwj_AQ49.js → composite-JEjFHOYa.js} +1 -1
  57. package/dashboard/dist/assets/{copy-DU0cBBCj.js → copy-Cgep9txD.js} +1 -1
  58. package/dashboard/dist/assets/{download-1G25F1-l.js → download-BvCJfrsP.js} +1 -1
  59. package/dashboard/dist/assets/{external-link-BTawmHLx.js → external-link-BvrBuvDE.js} +1 -1
  60. package/dashboard/dist/assets/{flame-Ck7u_g0N.js → flame-CpFTS5jY.js} +1 -1
  61. package/dashboard/dist/assets/{format-tokens-mvtbXN0_.js → format-tokens-DMy_T00F.js} +1 -1
  62. package/dashboard/dist/assets/{icons-C-vySzFI.js → icons-BF-Gt_W6.js} +1 -1
  63. package/dashboard/dist/assets/{index-Bn1BpzgP.js → index-BV_fWDL1.js} +1 -1
  64. package/dashboard/dist/assets/{index-Dl_4Ap2z.js → index-CG8txBGV.js} +1 -1
  65. package/dashboard/dist/assets/{index-oyq0VRzp.js → index-D7gB7QU6.js} +1 -1
  66. package/dashboard/dist/assets/{info-nLiOCn95.js → info-DN-YTwZw.js} +1 -1
  67. package/dashboard/dist/assets/{link-2-DqpkQl5I.js → link-2-C5QllJZa.js} +1 -1
  68. package/dashboard/dist/assets/{loader-circle-D9Uwv0lk.js → loader-circle-Br6MWmXZ.js} +1 -1
  69. package/dashboard/dist/assets/{maximize-2-BZqFpymh.js → maximize-2-DWQUOE9I.js} +1 -1
  70. package/dashboard/dist/assets/{modifiers.esm-D5idO4uG.js → modifiers.esm-BW3ZY-q5.js} +1 -1
  71. package/dashboard/dist/assets/{monitor-smartphone-wrYSytV5.js → monitor-smartphone-C0lh9STU.js} +1 -1
  72. package/dashboard/dist/assets/{plus-Buu86Rru.js → plus-DGzzR4Ta.js} +1 -1
  73. package/dashboard/dist/assets/{provider-display-DMDSBnrj.js → provider-display-C6cpMzsX.js} +1 -1
  74. package/dashboard/dist/assets/{react-Bj8RUcSV.js → react-Bl3I3D1L.js} +1 -1
  75. package/dashboard/dist/assets/{search-C8Vcia84.js → search-C9Xi6QaM.js} +1 -1
  76. package/dashboard/dist/assets/{skills-api-B9V7yAcx.js → skills-api-BPqKd9uy.js} +1 -1
  77. package/dashboard/dist/assets/{sortable.esm-BSct19df.js → sortable.esm-CDX2JVl9.js} +1 -1
  78. package/dashboard/dist/assets/{store-DUbjqECL.js → store-BBp505Pu.js} +1 -1
  79. package/dashboard/dist/assets/{terminal-DVzgSayL.js → terminal-CS1tejfk.js} +1 -1
  80. package/dashboard/dist/assets/{trash-Bo7xY33E.js → trash-CBCEVUxu.js} +1 -1
  81. package/dashboard/dist/assets/{use-community-stats-BCBvwoBJ.js → use-community-stats-W4PO2W2z.js} +1 -1
  82. package/dashboard/dist/assets/{use-latest-request-guard-Drj2VWM_.js → use-latest-request-guard-DkL6VAD-.js} +1 -1
  83. package/dashboard/dist/assets/{use-limits-display-prefs-BGZ8e1eB.js → use-limits-display-prefs-BrBaoX2Q.js} +1 -1
  84. package/dashboard/dist/assets/{use-ordered-list-DskYb1p5.js → use-ordered-list-BH_LviiX.js} +1 -1
  85. package/dashboard/dist/assets/use-reduced-motion-CoktFwg_.js +1 -0
  86. package/dashboard/dist/assets/{use-session-efficiency-pref-BbpqWKhz.js → use-session-efficiency-pref-8vzPyNum.js} +1 -1
  87. package/dashboard/dist/assets/{use-transform-C4LDbETT.js → use-transform-ILmR4oVe.js} +1 -1
  88. package/dashboard/dist/assets/use-usage-limits-CeoHpc3u.js +1 -0
  89. package/dashboard/dist/assets/{useCurrency-fr7T9OGZ.js → useCurrency-_1mDCij1.js} +1 -1
  90. package/dashboard/dist/assets/{useTokenFormat-DKAbaR7B.js → useTokenFormat-DalbmT3S.js} +1 -1
  91. package/dashboard/dist/assets/{zap-CqDAEt0Z.js → zap-DanLIcSw.js} +1 -1
  92. package/dashboard/dist/brand-logos/cline.png +0 -0
  93. package/dashboard/dist/index.html +13 -11
  94. package/dashboard/dist/ip-check.html +1 -1
  95. package/dashboard/dist/leaderboard.html +1 -1
  96. package/dashboard/dist/llms.txt +3 -2
  97. package/dashboard/dist/share.html +1 -1
  98. package/package.json +2 -2
  99. package/src/commands/init.js +25 -0
  100. package/src/commands/status.js +31 -0
  101. package/src/commands/sync.js +39 -0
  102. package/src/lib/pricing/curated-overrides.json +15 -0
  103. package/src/lib/pricing/index.js +6 -3
  104. package/src/lib/pricing/matcher.js +7 -0
  105. package/src/lib/pricing/seed-snapshot.json +1 -1
  106. package/src/lib/rollout.js +540 -0
  107. package/src/lib/zcode-limits.js +147 -16
  108. package/dashboard/dist/assets/DialogDescription-BS-3kTZ6.js +0 -1
  109. package/dashboard/dist/assets/use-reduced-motion-DE2WaiPY.js +0 -1
  110. package/dashboard/dist/assets/use-usage-limits-Cfx1w2nQ.js +0 -1
@@ -74,10 +74,13 @@ function resolveZcodeAppVersion({ home, env = process.env } = {}) {
74
74
  return DEFAULT_ZCODE_APP_VERSION;
75
75
  }
76
76
 
77
+ /** True when any ZCode layout is present: legacy v2/config.json, 3.14+ credentials, or the CLI db. */
77
78
  function isZcodeInstalled({ home, env } = {}) {
78
79
  const zcodeHome = resolveZcodeHome({ home, env });
79
80
  const configPath = path.join(zcodeHome, "v2", "config.json");
80
81
  if (fs.existsSync(configPath)) return true;
82
+ // ZCode 3.14+ no longer writes v2/config.json; a signed-in desktop app still keeps credentials.
83
+ if (fs.existsSync(path.join(zcodeHome, "v2", "credentials.json"))) return true;
81
84
  const dbPath = path.join(zcodeHome, "cli", "db", "db.sqlite");
82
85
  return fs.existsSync(dbPath);
83
86
  }
@@ -128,8 +131,10 @@ function loadZcodeSelectedPlans({ home, env } = {}) {
128
131
  try {
129
132
  const setting = JSON.parse(fs.readFileSync(settingPath, "utf8"));
130
133
  const selected = setting?.modelProviderFamilySelectedKeys;
131
- if (!selected || typeof selected !== "object") return [];
132
134
  const domain = typeof setting?.providerFamilyDomain === "string" ? setting.providerFamilyDomain : "";
135
+ if (!selected || typeof selected !== "object") {
136
+ return loadZcodeConnectionSelectedPlans(setting?.providerFamilyConnectionSelections, domain);
137
+ }
133
138
  const domains = [domain, ...Object.keys(selected)].filter(Boolean);
134
139
  const out = [];
135
140
  for (const key of domains) {
@@ -144,6 +149,26 @@ function loadZcodeSelectedPlans({ home, env } = {}) {
144
149
  }
145
150
  }
146
151
 
152
+ /**
153
+ * ZCode 3.14+ records the chosen plan per family as `providerFamilyConnectionSelections`
154
+ * (`{ zai: { kind: "individual-coding-plan" } }`) instead of `modelProviderFamilySelectedKeys`.
155
+ * Team scope for these selections lives on the account credential, not here.
156
+ */
157
+ function loadZcodeConnectionSelectedPlans(selections, domain) {
158
+ if (!selections || typeof selections !== "object") return [];
159
+ const families = [domain, ...Object.keys(selections)].filter((family) => family === "bigmodel" || family === "zai");
160
+ const out = [];
161
+ for (const family of families) {
162
+ const kind = typeof selections[family]?.kind === "string" ? selections[family].kind : "";
163
+ const planKind = /coding-plan$/.test(kind) ? "coding" : kind === "start-plan" ? "start" : null;
164
+ if (!planKind) continue;
165
+ const providerKey = `builtin:${family}-${planKind}-plan`;
166
+ if (out.some((plan) => plan.providerKey === providerKey)) continue;
167
+ out.push({ providerKey, teamContext: null });
168
+ }
169
+ return out;
170
+ }
171
+
147
172
  /** Return the ordered provider keys without exposing the internal team context. */
148
173
  function loadZcodeSelectedPlanProviderKeys(options = {}) {
149
174
  return loadZcodeSelectedPlans(options).map((plan) => plan.providerKey);
@@ -218,6 +243,47 @@ function resolveZcodeCredentialAuth(providerKey, { home, env } = {}) {
218
243
  return "";
219
244
  }
220
245
 
246
+ /**
247
+ * Map a ZCode 3.14+ per-account key name onto the built-in coding-plan provider it serves:
248
+ * account-provider:coding-plan:account:<family>-individual-coding-plan:account:<id>:api-key
249
+ * account-provider:team:<providerId>:<productId>:<orgId>:<projectId>:account:<id>:api-key
250
+ * Team segments are URI-encoded by ZCode. Start-plan balances use the zcodejwttoken instead.
251
+ * @returns {{ providerKey: string, teamContext: { organizationId: string, projectId: string } | null } | null}
252
+ */
253
+ function parseZcodeAccountProviderCredentialName(name) {
254
+ const match = typeof name === "string" ? name.match(/^account-provider:(.+):account:[^:]+:api-key$/) : null;
255
+ if (!match) return null;
256
+ const scope = match[1];
257
+ const individual = scope.match(/^coding-plan:account:(bigmodel|zai)-individual-coding-plan$/);
258
+ if (individual) return { providerKey: `builtin:${individual[1]}-coding-plan`, teamContext: null };
259
+ if (!scope.startsWith("team:")) return null;
260
+ const parts = scope.slice("team:".length).split(":");
261
+ if (parts.length !== 4) return null;
262
+ try {
263
+ const [providerId, productId, organizationId, projectId] = parts.map((part) => decodeURIComponent(part).trim());
264
+ const team = providerId.match(/^account:(bigmodel|zai)-team-coding-plan$/);
265
+ if (!team || !productId || !organizationId || !projectId) return null;
266
+ return { providerKey: `builtin:${team[1]}-coding-plan`, teamContext: { organizationId, projectId } };
267
+ } catch (_error) {
268
+ return null;
269
+ }
270
+ }
271
+
272
+ /** Collect decrypted per-account coding-plan keys, grouped by built-in provider key. */
273
+ function loadZcodeAccountProviderAuths({ home, env } = {}) {
274
+ const credentials = loadZcodeCredentials({ home, env });
275
+ const out = {};
276
+ for (const name of Object.keys(credentials)) {
277
+ const parsed = parseZcodeAccountProviderCredentialName(name);
278
+ if (!parsed) continue;
279
+ const decrypted = decryptZcodeCredentialValue(credentials[name], { home, env });
280
+ const apiKey = typeof decrypted === "string" ? decrypted.trim() : "";
281
+ if (!apiKey) continue;
282
+ (out[parsed.providerKey] ||= []).push({ apiKey, teamContext: parsed.teamContext });
283
+ }
284
+ return out;
285
+ }
286
+
221
287
  function isZcodeBuiltinPlanProvider(providerKey) {
222
288
  return /^builtin:(bigmodel|zai)-(start|coding)-plan$/.test(providerKey);
223
289
  }
@@ -281,15 +347,19 @@ function resolveZcodeProviderQuotaUrl(providerKey, provider, env = process.env)
281
347
  /**
282
348
  * Build quota/billing candidates from enabled providers and their existing keys.
283
349
  * Selected plans take precedence; team scope stays attached to its own provider.
350
+ * ZCode 3.14+ dropped v2/config.json: there, candidates come from credentials.json
351
+ * alone (the active family's zcodejwttoken for start plans, per-account coding-plan keys).
284
352
  */
285
353
  function loadZcodeAuthCandidates({ home, env } = {}) {
286
354
  const zcodeHome = resolveZcodeHome({ home, env });
287
355
  const configPath = path.join(zcodeHome, "v2", "config.json");
288
- if (!fs.existsSync(configPath)) return [];
356
+ const hasConfig = fs.existsSync(configPath);
289
357
  try {
290
- const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
358
+ const config = hasConfig ? JSON.parse(fs.readFileSync(configPath, "utf8")) : {};
291
359
  if (!config || typeof config !== "object") return [];
292
360
  const providers = config.provider || {};
361
+ // Per-account keys are a 3.14+ concept; a legacy config.json stays the only key source there.
362
+ const accountAuths = hasConfig ? {} : loadZcodeAccountProviderAuths({ home, env });
293
363
  const defaultCandidates = [
294
364
  "builtin:bigmodel-start-plan",
295
365
  "builtin:zai-start-plan",
@@ -309,18 +379,25 @@ function loadZcodeAuthCandidates({ home, env } = {}) {
309
379
  ].filter((key, index, all) => all.indexOf(key) === index);
310
380
  const auths = [];
311
381
  for (const key of candidates) {
312
- const provider = providers[key];
313
- if (!provider || typeof provider !== "object") continue;
314
- if (provider.enabled === false) continue;
382
+ const configured = providers[key];
383
+ const provider = configured && typeof configured === "object" ? configured : null;
384
+ if (provider?.enabled === false) continue;
315
385
  if (hasAvailability && availability?.[key]?.status && availability[key].status !== "available") continue;
316
386
  const apiKey = typeof provider?.options?.apiKey === "string" ? provider.options.apiKey.trim() : "";
317
387
  const billingBaseUrl = resolveZcodeProviderBillingBaseUrl(key, provider, env);
318
388
  const quotaUrl = resolveZcodeProviderQuotaUrl(key, provider, env);
319
389
  const teamContext = selectedPlans.find((plan) => plan.providerKey === key)?.teamContext;
320
- const credentialApiKey = resolveZcodeCredentialAuth(key, { home, env });
390
+ // Legacy layouts only trust the shared JWT for providers config.json still lists.
391
+ const credentialApiKey = provider || !hasConfig ? resolveZcodeCredentialAuth(key, { home, env }) : "";
392
+ const accountEntries = (accountAuths[key] || []).map((entry) => ({
393
+ apiKey: entry.apiKey,
394
+ authSource: "credential:account-provider",
395
+ teamContext: entry.teamContext,
396
+ }));
321
397
  const authEntries = [
322
- credentialApiKey ? { apiKey: credentialApiKey, authSource: "credential:zcodejwttoken" } : null,
323
- apiKey ? { apiKey, authSource: "provider:config" } : null,
398
+ credentialApiKey ? { apiKey: credentialApiKey, authSource: "credential:zcodejwttoken", teamContext } : null,
399
+ ...accountEntries,
400
+ apiKey ? { apiKey, authSource: "provider:config", teamContext } : null,
324
401
  ].filter(Boolean);
325
402
  const seenKeys = new Set();
326
403
  for (const entry of authEntries) {
@@ -334,7 +411,7 @@ function loadZcodeAuthCandidates({ home, env } = {}) {
334
411
  baseUrl: provider?.options?.baseURL || null,
335
412
  billingBaseUrl,
336
413
  quotaUrl,
337
- ...(teamContext ? { teamContext } : {}),
414
+ ...(entry.teamContext ? { teamContext: entry.teamContext } : {}),
338
415
  availability: availability?.[key]?.status || null,
339
416
  });
340
417
  }
@@ -349,6 +426,7 @@ function loadZcodeApiKey({ home, env } = {}) {
349
426
  return loadZcodeAuthCandidates({ home, env })[0] || null;
350
427
  }
351
428
 
429
+ /** Client-identification headers ZCode sends to its own billing host, including X-Device-Mid when known. */
352
430
  function buildZcodeSourceHeaders({ home, env } = {}) {
353
431
  const headers = {
354
432
  "User-Agent": `ZCode/${resolveZcodeAppVersion({ home, env })}`,
@@ -361,11 +439,29 @@ function buildZcodeSourceHeaders({ home, env } = {}) {
361
439
  "X-Os-Category": process.platform,
362
440
  "X-Os-Version": os.release(),
363
441
  };
364
- const deviceMid = loadZcodeCredential("zcodefeedbackclientid", { home, env });
442
+ const deviceMid = loadZcodeTelemetryDeviceMid({ home, env })
443
+ || loadZcodeCredential("zcodefeedbackclientid", { home, env });
365
444
  if (deviceMid) headers["X-Device-Mid"] = deviceMid;
366
445
  return headers;
367
446
  }
368
447
 
448
+ /**
449
+ * ZCode 3.14+ keeps the device id in v2/telemetry-state.json, and billing/balance
450
+ * rejects requests without X-Device-Mid as `code=3001 parameter error`.
451
+ * @returns {string} the device id, or "" when absent or not header-safe
452
+ */
453
+ function loadZcodeTelemetryDeviceMid({ home, env } = {}) {
454
+ const statePath = path.join(resolveZcodeHome({ home, env }), "v2", "telemetry-state.json");
455
+ if (!fs.existsSync(statePath)) return "";
456
+ try {
457
+ const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
458
+ const deviceMid = typeof state?.deviceMid === "string" ? state.deviceMid.trim() : "";
459
+ return /^[\x20-\x7e]+$/.test(deviceMid) ? deviceMid : "";
460
+ } catch (_error) {
461
+ return "";
462
+ }
463
+ }
464
+
369
465
  function zcodeValNumber(value) {
370
466
  if (value == null) return null;
371
467
  const n = Number(value);
@@ -407,6 +503,10 @@ function deriveZcodePlanLabel(planId) {
407
503
  return m[1].charAt(0).toUpperCase() + m[1].slice(1);
408
504
  }
409
505
 
506
+ /**
507
+ * Normalize a start-plan billing/balance payload into labelled buckets plus the
508
+ * primary/secondary/tertiary windows (daily allowances first, promotional grants after).
509
+ */
410
510
  function normalizeZcodeBalanceResponse(body) {
411
511
  const data = body?.data;
412
512
  if (!data || typeof data !== "object") {
@@ -428,6 +528,20 @@ function normalizeZcodeBalanceResponse(body) {
428
528
  }
429
529
 
430
530
  const serverTime = zcodeValNumber(data.server_time);
531
+ // Promotional grants ("free eggs", weekend builds) arrive as extra plans whose
532
+ // entitlements are one-time; index them so each bucket can say where it came from.
533
+ const plans = Array.isArray(data.plans) ? data.plans : [];
534
+ const hasPlans = plans.length > 0;
535
+ const planByUserPlanId = new Map();
536
+ const periodByEntitlementId = new Map();
537
+ for (const plan of plans) {
538
+ if (typeof plan?.user_plan_id === "string") planByUserPlanId.set(plan.user_plan_id, plan);
539
+ for (const ent of Array.isArray(plan?.entitlements) ? plan.entitlements : []) {
540
+ if (typeof ent?.entitlement_id === "string" && typeof ent?.period === "string") {
541
+ periodByEntitlementId.set(ent.entitlement_id, ent.period);
542
+ }
543
+ }
544
+ }
431
545
  const buckets = balances.map((b) => {
432
546
  const total = zcodeValNumber(b.total_units);
433
547
  const used = zcodeValNumber(b.used_units);
@@ -437,9 +551,21 @@ function normalizeZcodeBalanceResponse(body) {
437
551
  const usedPercent =
438
552
  total != null && total > 0 && used != null ? (used / total) * 100 : null;
439
553
 
554
+ const showName = typeof b.show_name === "string" ? b.show_name : "";
555
+ const entitlementId = typeof b.entitlement_id === "string" ? b.entitlement_id : "";
556
+ const plan = planByUserPlanId.get(b.user_plan_id) || null;
557
+ const period = periodByEntitlementId.get(entitlementId) || null;
558
+ const oneTime = period === "one_time";
559
+ const planName = typeof plan?.name === "string" && plan.name.trim() ? plan.name.trim() : null;
560
+
440
561
  return {
441
- show_name: typeof b.show_name === "string" ? b.show_name : "",
442
- entitlement_id: typeof b.entitlement_id === "string" ? b.entitlement_id : "",
562
+ show_name: showName,
563
+ label: oneTime && planName ? `${showName} · ${planName}` : showName,
564
+ entitlement_id: entitlementId,
565
+ plan_id: typeof b.plan_id === "string" ? b.plan_id : null,
566
+ plan_name: planName,
567
+ period,
568
+ priority: zcodeValNumber(b.priority),
443
569
  total_units: total,
444
570
  used_units: used,
445
571
  remaining_units: remaining,
@@ -447,15 +573,20 @@ function normalizeZcodeBalanceResponse(body) {
447
573
  };
448
574
  });
449
575
 
450
- // Primary window: highest-priority bucket (GLM-5.2 typically)
451
- // Secondary window: second bucket (GLM-5-Turbo typically)
576
+ // Recurring (daily) allowances first so the primary/secondary windows keep meaning
577
+ // "today's main models"; one-time promotional grants follow. Within each group, ZCode's
578
+ // own bucket priority (flagship model first), then larger totals.
452
579
  const sorted = buckets.slice().sort((a, b) => {
580
+ const aOneTime = a.period === "one_time" ? 1 : 0;
581
+ const bOneTime = b.period === "one_time" ? 1 : 0;
582
+ if (aOneTime !== bOneTime) return aOneTime - bOneTime;
583
+ if (hasPlans && a.priority != null && b.priority != null && a.priority !== b.priority) return b.priority - a.priority;
453
584
  const aTotal = a.total_units || 0;
454
585
  const bTotal = b.total_units || 0;
455
586
  return bTotal - aTotal;
456
587
  });
457
588
 
458
- const planId = typeof balances[0]?.plan_id === "string" ? balances[0].plan_id : null;
589
+ const planId = sorted[0]?.plan_id || (typeof balances[0]?.plan_id === "string" ? balances[0].plan_id : null);
459
590
  return {
460
591
  server_time: serverTime,
461
592
  plan_kind: "start-plan",
@@ -1 +0,0 @@
1
- import{r as n,v as a}from"./__vite-browser-external-_RSGXjh3.js";import{u as p}from"./DialogTitle-CRJVqO3Z.js";import{u as c}from"./composite-Bwj_AQ49.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,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{r as o}from"./__vite-browser-external-_RSGXjh3.js";import{h as r,i as t,p as s}from"./react-Bj8RUcSV.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};
@@ -1 +0,0 @@
1
- import{J as S,ab as x,r as d,c as N,ac as O,ad as A,ae as g}from"./__vite-browser-external-_RSGXjh3.js";import{l as R,i as _,L as P,a as q}from"./use-limits-display-prefs-BGZ8e1eB.js";import{u as z}from"./use-latest-request-guard-Drj2VWM_.js";const F="native:notificationPermission";let M=null;function I(){return typeof window<"u"&&!!window.chrome?.webview}function L(){return S()?M||"unknown":I()?"granted":typeof Notification>"u"?"unsupported":Notification.permission}function j(e){if(typeof window>"u")return()=>{};const i=n=>{const w=n?.detail?.status;typeof w=="string"&&(M=w),e(L())};return window.addEventListener(F,i),e(L()),S()&&x({type:"getNotificationStatus"}),()=>window.removeEventListener(F,i)}async function T(){if(S()){x({type:"requestNotificationPermission"});return}I()||typeof Notification<"u"&&Notification.permission==="default"&&await Notification.requestPermission().catch(()=>"denied")}const v="tt.limitAlerts.enabled";function $(){try{return window.localStorage.getItem(v)==="1"}catch{return!1}}function te(){const[e,i]=d.useState($),[n,w]=d.useState("unknown");d.useEffect(()=>j(w),[]);const u=d.useCallback(async o=>{const r=!!o;r&&await T();try{window.localStorage.setItem(v,r?"1":"0")}catch{}i(r)},[]);return{enabled:e,setEnabled:u,permissionBlocked:n==="denied"}}function B(e,i){if(!(e>0))return null;const n=(e-i)/e;return Number.isFinite(n)?Math.min(Math.max(n,0),1):null}function G(e,i,n=.03){return e>i+n}function V(e){const i=Math.max(0,Math.floor(e)),n=Math.floor(i/3600);return n>24?`${Math.floor(n/24)}d`:n>0?`${n}h`:`${Math.floor(i/60)}m`}function J(e,i){if(typeof e.windowSeconds=="number")return e.windowSeconds;if(e.windowSecondsField&&i){const n=i[e.windowSecondsField];return typeof n=="number"&&n>0?n:null}return null}function U(e){return e==null?NaN:typeof e=="number"?e*1e3:Date.parse(e)}function H({usedPercent:e,windowSeconds:i,resetMs:n,mode:w,now:u=Date.now()}){const o=Math.min(Math.max(Number(e)||0,0),100)/100,r={pacePercent:null,paceOver:!1,expectedPercent:null,runsOutEta:null,projectedEnd:null};if(!(i>0)||!Number.isFinite(n))return r;const l=Math.max(0,(n-u)/1e3),a=B(i,l);if(a==null)return r;if(r.expectedPercent=Math.round(a*100),r.paceOver=G(o,a),o>=.05){const c=w==="remaining"?1-a:a;r.pacePercent=c*100}if(a>.02&&o>0){const c=i*a,m=o/c,f=o/a;f>=1&&m>0?r.runsOutEta=V((1-o)/m):r.projectedEnd=Math.round(Math.min(f,1)*100)}return r}const W={claude:{windows(e){const i=Array.isArray(e.weekly_scoped)?e.weekly_scoped.map(n=>({key:`scoped-${n.label}`,label:n.label,window:n,pctField:"utilization",resetField:"resets_at",windowSeconds:604800})):[];return[{key:"5h",labelKey:"limits.label.claude_5h",window:e.five_hour,pctField:"utilization",resetField:"resets_at",windowSeconds:5*3600},{key:"7d",labelKey:"limits.label.claude_7d",window:e.seven_day,pctField:"utilization",resetField:"resets_at",windowSeconds:7*86400},{key:"opus",labelKey:"limits.label.claude_opus",window:e.seven_day_opus,pctField:"utilization",resetField:"resets_at",windowSeconds:7*86400},...i]}},codex:{extra:"codex_meta",windows(e){return[{key:"5h",labelKey:"limits.label.codex_5h",window:e.primary_window,windowSecondsField:"limit_window_seconds"},{key:"7d",labelKey:"limits.label.codex_7d",window:e.secondary_window,windowSecondsField:"limit_window_seconds"},{key:"credits",labelKey:"limits.label.codex_credits",window:e.credit_window},{key:"spark-5h",labelKey:"limits.label.codex_spark_5h",window:e.spark_primary_window,windowSecondsField:"limit_window_seconds"},{key:"spark-7d",labelKey:"limits.label.codex_spark_7d",window:e.spark_secondary_window,windowSecondsField:"limit_window_seconds"}]}},cursor:{windows(e){return[{key:"plan",labelKey:"limits.label.cursor_plan",window:e.primary_window,windowSecondsField:"limit_window_seconds"},{key:"auto",labelKey:"limits.label.cursor_auto",window:e.secondary_window,windowSecondsField:"limit_window_seconds"},{key:"api",labelKey:"limits.label.cursor_api",window:e.tertiary_window,windowSecondsField:"limit_window_seconds"},{key:"grok-bot",labelKey:"limits.label.cursor_grok_bot",window:e.quaternary_window,windowSecondsField:"limit_window_seconds"}]}},gemini:{windows(e){return[{key:"pro",labelKey:"limits.label.gemini_pro",window:e.primary_window},{key:"flash",labelKey:"limits.label.gemini_flash",window:e.secondary_window},{key:"lite",labelKey:"limits.label.gemini_lite",window:e.tertiary_window}]}},kimi:{extra:"kimi_parallel",windows(e){return[{key:"weekly",labelKey:"limits.label.kimi_weekly",window:e.primary_window,windowSeconds:7*86400},{key:"5h",labelKey:"limits.label.kimi_5h",window:e.secondary_window,windowSeconds:5*3600},{key:"total",labelKey:"limits.label.kimi_total",window:e.tertiary_window}]}},kiro:{extra:"kiro_credits",windows(e){return[{key:"month",labelKey:"limits.label.kiro_month",window:e.primary_window},{key:"bonus",labelKey:"limits.label.kiro_bonus",window:e.secondary_window}]}},grok:{windows(e){const i=typeof e.period_type=="string"?e.period_type:null,n=i==="weekly"?"limits.label.grok_week":i==="daily"?"limits.label.grok_day":"limits.label.grok_month",w=i==="weekly"?7*86400:i==="daily"?86400:void 0;return[{key:i==="weekly"?"week":i==="daily"?"day":"month",labelKey:n,window:e.primary_window,windowSeconds:w},{key:"ondemand",labelKey:"limits.label.grok_ondemand",window:e.secondary_window}]}},antigravity:{windows(e){return[{key:"claude-weekly",labelKey:"limits.label.antigravity_claude_weekly",window:e.primary_window},{key:"claude-5h",labelKey:"limits.label.antigravity_claude_5h",window:e.secondary_window},{key:"gemini-weekly",labelKey:"limits.label.antigravity_gemini_weekly",window:e.tertiary_window},{key:"gemini-5h",labelKey:"limits.label.antigravity_gemini_5h",window:e.quaternary_window}]}},copilot:{extra:"copilot_otel",windows(e){return[{key:"premium",labelKey:"limits.label.copilot_premium",window:e.primary_window},{key:"chat",labelKey:"limits.label.copilot_chat",window:e.secondary_window}]}},zcode:{windows(e){return e.plan_kind==="coding-plan"?[{key:"5h",labelKey:"limits.label.zcode_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.zcode_weekly",window:e.secondary_window},{key:"tools",labelKey:"limits.label.zcode_tools",window:e.tertiary_window}]:[{key:"glm52",labelKey:"limits.label.zcode_glm52",window:e.primary_window},{key:"glm5t",labelKey:"limits.label.zcode_glm5t",window:e.secondary_window}]}},opencodeGo:{windows(e){return[{key:"5h",labelKey:"limits.label.opencode_go_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.opencode_go_weekly",window:e.secondary_window},{key:"monthly",labelKey:"limits.label.opencode_go_monthly",window:e.tertiary_window}]}},qoder:{windows(e){return[{key:"credits",labelKey:"limits.label.qoder_credits",window:e.primary_window},{key:"ultimate",labelKey:"limits.label.qoder_ultimate",window:e.secondary_window,timeKind:"expiry"}]}},qoderCn:{windows(e){return[{key:"credits",labelKey:"limits.label.qoder_cn_credits",window:e.primary_window},{key:"ultimate",labelKey:"limits.label.qoder_cn_ultimate",window:e.secondary_window,timeKind:"expiry"}]}},codingPlan:{windows(e){return[{key:"5h",labelKey:"limits.label.ark_coding_plan_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.ark_coding_plan_weekly",window:e.secondary_window},{key:"monthly",labelKey:"limits.label.ark_coding_plan_monthly",window:e.tertiary_window}]}},commandCode:{windows(e){return[{key:"5h",labelKey:"limits.label.command_code_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.command_code_weekly",window:e.secondary_window}]}},agentPlan:{windows(e){return[{key:"5h",labelKey:"limits.label.ark_agent_plan_5h",window:e.primary_window},{key:"weekly",labelKey:"limits.label.ark_agent_plan_weekly",window:e.secondary_window},{key:"monthly",labelKey:"limits.label.ark_agent_plan_monthly",window:e.tertiary_window}]}},devin:{windows(e){return[{key:"daily",labelKey:"limits.label.devin_daily",window:e.primary_window,windowSecondsField:"limit_window_seconds"},{key:"weekly",labelKey:"limits.label.devin_weekly",window:e.secondary_window,windowSecondsField:"limit_window_seconds"}]}}},D="tt.limitAlerts.cycles.v1";function Y(){try{return JSON.parse(window.localStorage.getItem(D)||"{}")}catch{return{}}}function Q(e){try{window.localStorage.setItem(D,JSON.stringify(e))}catch{}}function X(e,{now:i=Date.now()}={}){const n=[],w=W;for(const[u,o]of Object.entries(e||{})){const r=w[u];if(!(!o?.configured||o.error||!r))for(const l of r.windows(o)){if(!l.window)continue;const a=Number(l.pctField==="utilization"?l.window.utilization:l.window.used_percent),c=l.resetField==="resets_at"?l.window.resets_at:l.window.reset_at,m=U(c),f=H({usedPercent:a,windowSeconds:J(l,l.window),resetMs:m,mode:"used",now:i});!f.runsOutEta||!f.paceOver||a<20||!(m>i)||n.push({id:`${u}:${l.key}:${m}`,providerId:u,windowKey:l.key,resetMs:m,usedPercent:a,runsOutEta:f.runsOutEta})}}return n}function Z(e){const i=Y(),n=X(e);for(const o of n){if(i[o.id])continue;const r=R(o.providerId),l=N("limits.alert.title",{provider:r}),a=N("limits.alert.body",{eta:o.runsOutEta,percent:Math.round(o.usedPercent)});let c=O({title:l,body:a,id:o.id});!c&&typeof Notification<"u"&&Notification.permission==="granted"&&(new Notification(l,{body:a,tag:o.id}),c=!0),c&&(i[o.id]=Date.now())}const w=Date.now()-864e5,u=Object.fromEntries(Object.entries(i).filter(([o,r])=>{const l=Number(o.split(":").at(-1));return Number.isFinite(l)?l>=w:Number(r)>=w}).slice(-100));return Q(u),n.length}function k(e,i){return!e||i||!e.devin||e.devin.configured===!1&&Object.keys(e.devin).length===1?e:{...e,devin:{configured:!1}}}function oe(e){const i=!!e?.initialState,[n,w]=d.useState(_),[u,o]=d.useState(()=>i?k(e?.initialState?.data??null,_()):null),[r,l]=d.useState(()=>i?e?.initialState?.error??null:null),[a,c]=d.useState(!i),m=!!e?.initialRefresh,f=!!e?.publishToPreloadCache,b=z([n]);d.useEffect(()=>{if(typeof window>"u")return;const s=()=>w(_()),t=y=>{q(y.key)&&s()};return window.addEventListener(P,s),window.addEventListener("storage",t),()=>{window.removeEventListener(P,s),window.removeEventListener("storage",t)}},[]),d.useEffect(()=>{if(!(!u||typeof window>"u"))try{window.localStorage.getItem(v)==="1"&&Z(u)}catch{}},[u]);const p=d.useCallback((s,t)=>{!f||!s||typeof s!="object"||A(k(s,_()),{source:t})},[f]),C=d.useCallback(async()=>{const s=b();try{const t=await g({refresh:!0,devinEnabled:_()});if(!s())return;const y=t&&typeof t=="object"?t:null;o(k(y,_())),l(null),c(!1),p(y,"manual-refresh")}catch(t){if(!s())return;l(t?.message||String(t)),c(!1)}},[b,p]),h=d.useCallback(async()=>{const s=b();try{const t=await g({devinEnabled:_()});if(!s())return;const y=t&&typeof t=="object"?t:null;o(k(y,_())),l(null),c(!1),p(y,"page-load")}catch(t){if(!s())return;l(t?.message||String(t)),c(!1)}},[b,p]);d.useEffect(()=>{if(typeof window>"u"||typeof document>"u")return;const s=15e3;let t=Date.now();const y=()=>{if(document.visibilityState!=="visible")return;const E=Date.now();E-t<s||(t=E,h())};return window.addEventListener("focus",y),document.addEventListener("visibilitychange",y),()=>{window.removeEventListener("focus",y),document.removeEventListener("visibilitychange",y)}},[h]);const K=d.useRef(n);return d.useEffect(()=>{K.current!==n&&(K.current=n,n||o(s=>k(s,!1)),h())},[n,h]),d.useEffect(()=>{if(i&&!m)return;const s=b();(async()=>{try{const t=await g({devinEnabled:_()});if(!s())return;const y=t&&typeof t=="object"?t:null;o(k(y,_())),l(null),p(y,"page-load")}catch(t){if(!s())return;l(t?.message||String(t))}finally{s()&&c(!1)}})()},[b,i,m,p]),{data:u,error:r,isLoading:a,refresh:C}}export{W as P,J as a,te as b,H as c,U as r,Z as s,oe as u};