tokentracker-cli 0.87.15 → 0.87.16

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 (109) hide show
  1. package/README.de.md +2 -2
  2. package/README.ja.md +2 -2
  3. package/README.ko.md +2 -2
  4. package/README.md +3 -3
  5. package/README.zh-CN.md +2 -2
  6. package/dashboard/dist/assets/{AchievementBadge-Ca-tQO2m.js → AchievementBadge-DaKHLTL2.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-l8piG098.js → AchievementsPage-DJdtjodG.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-DILM1j6f.js → AchievementsSection-DVtxGDIP.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-CP4kIXJ6.js → BadgeDetailModal-CF2dlQkT.js} +1 -1
  10. package/dashboard/dist/assets/{Card-vJSP5AIR.js → Card-DuQWS5aB.js} +1 -1
  11. package/dashboard/dist/assets/{ClawdAnimated-DRXRgleI.js → ClawdAnimated-l8AN6_pc.js} +1 -1
  12. package/dashboard/dist/assets/{CommandPalette-CFYDM40w.js → CommandPalette-DWKyGhWG.js} +1 -1
  13. package/dashboard/dist/assets/{CommunityStatsModal-DeBswg2S.js → CommunityStatsModal-CoJ9i3QH.js} +1 -1
  14. package/dashboard/dist/assets/{ConfirmModal-DnHQ6ZjR.js → ConfirmModal-DM6A6p4g.js} +1 -1
  15. package/dashboard/dist/assets/{Controls-CEgdStDg.js → Controls-DBUMibne.js} +1 -1
  16. package/dashboard/dist/assets/{DashboardPage-CRA9C12j.js → DashboardPage-BgVXjT0v.js} +1 -1
  17. package/dashboard/dist/assets/{DevicePage-DJHYCHvk.js → DevicePage-Beby6BhC.js} +1 -1
  18. package/dashboard/dist/assets/{DialogClose-Dfv009JY.js → DialogClose-nwx-N3WL.js} +1 -1
  19. package/dashboard/dist/assets/DialogDescription-BFVB627n.js +1 -0
  20. package/dashboard/dist/assets/{DialogTitle-B50QLs-X.js → DialogTitle-Cw53obf0.js} +1 -1
  21. package/dashboard/dist/assets/{FadeIn-C0gR94fP.js → FadeIn-CTTwoVh-.js} +1 -1
  22. package/dashboard/dist/assets/{HeaderGithubStar-C640OuU1.js → HeaderGithubStar-BvaEHM9B.js} +1 -1
  23. package/dashboard/dist/assets/{HoverTooltip-DX83f-xN.js → HoverTooltip-CbJqz6cF.js} +1 -1
  24. package/dashboard/dist/assets/{IpCheckPage-BSFyNVPV.js → IpCheckPage-I0REWeew.js} +1 -1
  25. package/dashboard/dist/assets/{LandingPage-SsOTjgMK.js → LandingPage-CuMWPQBC.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardAvatar-B2195RbN.js → LeaderboardAvatar-MHfIP4_U.js} +1 -1
  27. package/dashboard/dist/assets/{LeaderboardPage-B2HVZ1LA.js → LeaderboardPage-DfDM-dH9.js} +2 -2
  28. package/dashboard/dist/assets/{LeaderboardProfileModal-CV07_l8k.js → LeaderboardProfileModal-hmpU-JV7.js} +3 -3
  29. package/dashboard/dist/assets/{LeaderboardProfilePage-Dx3je17Y.js → LeaderboardProfilePage-DtUb4dDZ.js} +1 -1
  30. package/dashboard/dist/assets/LimitsPage-CtA0c2G5.js +4 -0
  31. package/dashboard/dist/assets/{LocalOnlyNotice-CIcgZ_DK.js → LocalOnlyNotice-WI3Oke4U.js} +1 -1
  32. package/dashboard/dist/assets/{LoginCard-RoSvQEcf.js → LoginCard-DMprjRI7.js} +1 -1
  33. package/dashboard/dist/assets/{LoginModal-CyyqwoxT.js → LoginModal-DffmK0ro.js} +1 -1
  34. package/dashboard/dist/assets/{LoginPage-Df1TNN5I.js → LoginPage-B2ZgB6K7.js} +1 -1
  35. package/dashboard/dist/assets/{PetPage-DNzJwiHw.js → PetPage-D_U7yXgF.js} +1 -1
  36. package/dashboard/dist/assets/{PopoverPopup-DQeWO0iF.js → PopoverPopup-Ph-9l9jU.js} +1 -1
  37. package/dashboard/dist/assets/{ProviderIcon-lzUcnmCH.js → ProviderIcon-D2s_KSYC.js} +1 -1
  38. package/dashboard/dist/assets/{ResetPasswordPage-zMccm6ur.js → ResetPasswordPage-Cpg2C8BI.js} +1 -1
  39. package/dashboard/dist/assets/{SegmentedControl-CJFGvTrI.js → SegmentedControl-C89jlPzS.js} +1 -1
  40. package/dashboard/dist/assets/{Select-C3Omtbu7.js → Select-BLPgobGl.js} +1 -1
  41. package/dashboard/dist/assets/{SelectItemText-yhAb3Grm.js → SelectItemText-CLTEQqQZ.js} +1 -1
  42. package/dashboard/dist/assets/{ServiceStatusPage-DKO2U7tP.js → ServiceStatusPage-Bd1ec7bz.js} +1 -1
  43. package/dashboard/dist/assets/{SessionsPage-Db7QQKkq.js → SessionsPage-HPbQDLZ5.js} +1 -1
  44. package/dashboard/dist/assets/{SettingsPage-Wt0c9Tom.js → SettingsPage-CwfcRYYu.js} +1 -1
  45. package/dashboard/dist/assets/{SkillsPage-CiIK-x9u.js → SkillsPage-DflhF8pa.js} +1 -1
  46. package/dashboard/dist/assets/{TokenGalaxy-lenaRGrK.js → TokenGalaxy-CylVSV2J.js} +1 -1
  47. package/dashboard/dist/assets/{ToolbarRootContext-DM5LAbiN.js → ToolbarRootContext-BzBAJ8C5.js} +1 -1
  48. package/dashboard/dist/assets/{TrendMonitor-DsjkB17f.js → TrendMonitor-B0IzqLaN.js} +1 -1
  49. package/dashboard/dist/assets/{WidgetsPage-DRHgj8mv.js → WidgetsPage-Bvt5JCoS.js} +1 -1
  50. package/dashboard/dist/assets/{WrappedPage-CnqvO_pe.js → WrappedPage-BXjDpjD_.js} +1 -1
  51. package/dashboard/dist/assets/{__vite-browser-external-Dt0UC4VW.js → __vite-browser-external-CDv96ZcG.js} +14 -11
  52. package/dashboard/dist/assets/{agent-logos-B5opZwZN.js → agent-logos-bPIt1QVl.js} +1 -1
  53. package/dashboard/dist/assets/{arrow-up-right-DthPMeHa.js → arrow-up-right-C_6NrnRB.js} +1 -1
  54. package/dashboard/dist/assets/{check-BjhrWH3T.js → check-ovHAYNwt.js} +1 -1
  55. package/dashboard/dist/assets/{chevron-right-BllZqKsq.js → chevron-right-BeHvjzPf.js} +1 -1
  56. package/dashboard/dist/assets/{copy-DPdzUcUh.js → copy-DRQHwFKm.js} +1 -1
  57. package/dashboard/dist/assets/{download-BSl1NoxY.js → download-qPfSWDyf.js} +1 -1
  58. package/dashboard/dist/assets/{external-link-DpSJMzDV.js → external-link-t7Qbyp7r.js} +1 -1
  59. package/dashboard/dist/assets/{flame-BuP9lXyr.js → flame-DlRdMOxO.js} +1 -1
  60. package/dashboard/dist/assets/{format-tokens-DxpuHAMo.js → format-tokens-DP1DrOxZ.js} +1 -1
  61. package/dashboard/dist/assets/{icons-N_skbXnV.js → icons-DBgKdjpV.js} +1 -1
  62. package/dashboard/dist/assets/{index-CkdHt2G8.js → index-BFaAjCwg.js} +1 -1
  63. package/dashboard/dist/assets/{index-VgEi64Pa.js → index-C1wNjCL_.js} +1 -1
  64. package/dashboard/dist/assets/{index-BJbM1KfA.js → index-Dtzl1NhK.js} +1 -1
  65. package/dashboard/dist/assets/{info-B7vTy0RN.js → info-D9fn_ZQQ.js} +1 -1
  66. package/dashboard/dist/assets/limits-providers-JyTKN-3e.js +1 -0
  67. package/dashboard/dist/assets/{link-2-BL9btf_U.js → link-2-CmnuQiZs.js} +1 -1
  68. package/dashboard/dist/assets/{loader-circle-B3ZUl9Xl.js → loader-circle-Bm3FqDLX.js} +1 -1
  69. package/dashboard/dist/assets/{maximize-2-B3oqZR-c.js → maximize-2-CutKDynW.js} +1 -1
  70. package/dashboard/dist/assets/{modifiers.esm-CJv-4uLD.js → modifiers.esm-C94MH21S.js} +1 -1
  71. package/dashboard/dist/assets/{monitor-smartphone-DpSeKStM.js → monitor-smartphone-wSLhNWq5.js} +1 -1
  72. package/dashboard/dist/assets/{provider-display-ChgG1v12.js → provider-display-CR8o_M3v.js} +1 -1
  73. package/dashboard/dist/assets/{react-CiQpGAt7.js → react-Cc1Ca2uE.js} +1 -1
  74. package/dashboard/dist/assets/{search-CR5Y7wkH.js → search-BXeDDAWS.js} +1 -1
  75. package/dashboard/dist/assets/{skills-api-CYV3Aau7.js → skills-api-DBP8kM7W.js} +1 -1
  76. package/dashboard/dist/assets/{sortable.esm-Cp2tEWCE.js → sortable.esm-Cd_ODlnX.js} +1 -1
  77. package/dashboard/dist/assets/{store-D579LpDz.js → store-lmptvFHG.js} +1 -1
  78. package/dashboard/dist/assets/{terminal-B1Afo2zN.js → terminal-965BMhR0.js} +1 -1
  79. package/dashboard/dist/assets/{trash-2-O7jz3Z3p.js → trash-2-wq5YeaAT.js} +1 -1
  80. package/dashboard/dist/assets/{use-community-stats-DpHf0WZG.js → use-community-stats-BCIgHdTZ.js} +1 -1
  81. package/dashboard/dist/assets/{use-limits-display-prefs-BVCtTqo4.js → use-limits-display-prefs-D0MB2ewv.js} +1 -1
  82. package/dashboard/dist/assets/{use-ordered-list-C7mhlVII.js → use-ordered-list-DrQsh022.js} +1 -1
  83. package/dashboard/dist/assets/use-reduced-motion-DhNppA5X.js +1 -0
  84. package/dashboard/dist/assets/{use-session-efficiency-pref-C3HNT8gg.js → use-session-efficiency-pref-BeBSADa-.js} +1 -1
  85. package/dashboard/dist/assets/{use-transform-Cn4pqfbJ.js → use-transform-fRpwp-G3.js} +1 -1
  86. package/dashboard/dist/assets/{use-usage-limits-BAF6oNaO.js → use-usage-limits-C0Tk-ghm.js} +1 -1
  87. package/dashboard/dist/assets/{useCurrency-DmofuCKt.js → useCurrency-BAZNRyKh.js} +1 -1
  88. package/dashboard/dist/assets/{useScrollLock-2HXHWZMK.js → useScrollLock-CU659jBG.js} +1 -1
  89. package/dashboard/dist/assets/{useTokenFormat-CSI-l9pP.js → useTokenFormat-l0Xd62a5.js} +1 -1
  90. package/dashboard/dist/assets/{zap-BcxNvHjJ.js → zap-CWFkq16a.js} +1 -1
  91. package/dashboard/dist/index.html +4 -4
  92. package/dashboard/dist/ip-check.html +1 -1
  93. package/dashboard/dist/leaderboard.html +1 -1
  94. package/dashboard/dist/share.html +1 -1
  95. package/package.json +1 -1
  96. package/src/commands/serve.js +11 -3
  97. package/src/commands/status.js +34 -0
  98. package/src/commands/sync.js +95 -1
  99. package/src/lib/diagnostics.js +10 -0
  100. package/src/lib/fs.js +79 -1
  101. package/src/lib/pricing/seed-snapshot.json +1 -1
  102. package/src/lib/qoder-limits.js +89 -34
  103. package/src/lib/rollout.js +226 -43
  104. package/src/lib/session-analytics.js +45 -5
  105. package/src/lib/usage-limits.js +93 -14
  106. package/dashboard/dist/assets/DialogDescription-BcNmkFVH.js +0 -1
  107. package/dashboard/dist/assets/LimitsPage-uQNPuFCp.js +0 -4
  108. package/dashboard/dist/assets/limits-providers-DXlRXQck.js +0 -1
  109. package/dashboard/dist/assets/use-reduced-motion-CWJDDawC.js +0 -1
@@ -11,12 +11,14 @@ const QODER_SITES = {
11
11
  id: "international",
12
12
  origin: "https://qoder.com",
13
13
  usageUrl: "https://qoder.com/api/v2/me/usages/big_model_credits",
14
+ activityUrl: "https://openapi.qoder.sh/algo/api/v2/activity",
14
15
  domains: new Set(["qoder.com", "www.qoder.com"]),
15
16
  },
16
17
  china: {
17
18
  id: "china",
18
19
  origin: "https://qoder.com.cn",
19
20
  usageUrl: "https://qoder.com.cn/api/v2/me/usages/big_model_credits",
21
+ activityUrl: "https://openapi.qoder.com.cn/algo/api/v2/activity",
20
22
  domains: new Set(["qoder.com.cn", "www.qoder.com.cn"]),
21
23
  },
22
24
  };
@@ -142,28 +144,33 @@ function qoderDataRoot({
142
144
  home = os.homedir(),
143
145
  env = process.env,
144
146
  platform = process.platform,
147
+ appDir = "Qoder",
148
+ envPrefix = "QODER",
145
149
  } = {}) {
146
- if (typeof env.QODER_HOME === "string" && env.QODER_HOME.trim()) {
147
- return path.resolve(env.QODER_HOME.trim());
150
+ const homeKey = `${envPrefix}_HOME`;
151
+ if (typeof env[homeKey] === "string" && env[homeKey].trim()) {
152
+ return path.resolve(env[homeKey].trim());
148
153
  }
149
154
  if (platform === "darwin") {
150
- return path.join(home, "Library", "Application Support", "Qoder");
155
+ return path.join(home, "Library", "Application Support", appDir);
151
156
  }
152
157
  if (platform === "win32") {
153
- return path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "Qoder");
158
+ return path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), appDir);
154
159
  }
155
- return path.join(home, ".config", "Qoder");
160
+ return path.join(home, ".config", appDir);
156
161
  }
157
162
 
158
163
  function qoderRpcRequest(method, params = {}, {
159
164
  home = os.homedir(),
160
165
  env = process.env,
161
166
  platform = process.platform,
167
+ appDir = "Qoder",
168
+ envPrefix = "QODER",
162
169
  timeoutMs = 1500,
163
170
  netModule = net,
164
171
  } = {}) {
165
172
  return new Promise((resolve, reject) => {
166
- const infoPath = path.join(qoderDataRoot({ home, env, platform }), "SharedClientCache", ".info.json");
173
+ const infoPath = path.join(qoderDataRoot({ home, env, platform, appDir, envPrefix }), "SharedClientCache", ".info.json");
167
174
  let info;
168
175
  try {
169
176
  info = JSON.parse(fs.readFileSync(infoPath, "utf8"));
@@ -302,17 +309,21 @@ function normalizeQoderActivityResponse(response, { nowMs = Date.now() } = {}) {
302
309
  };
303
310
  }
304
311
 
305
- function qoderActivityCachePath({ home = os.homedir() } = {}) {
306
- return path.join(home, ".tokentracker", "tracker", QODER_ACTIVITY_CACHE_FILE);
312
+ function qoderActivityCachePath({ home = os.homedir(), namespace } = {}) {
313
+ const file = namespace
314
+ ? `qoder-${namespace}-activity-cache.json`
315
+ : QODER_ACTIVITY_CACHE_FILE;
316
+ return path.join(home, ".tokentracker", "tracker", file);
307
317
  }
308
318
 
309
319
  function readQoderActivityCache({
310
320
  home = os.homedir(),
311
321
  nowMs = Date.now(),
322
+ namespace,
312
323
  maxAgeMs = QODER_ACTIVITY_CACHE_MAX_AGE_MS,
313
324
  } = {}) {
314
325
  try {
315
- const parsed = JSON.parse(fs.readFileSync(qoderActivityCachePath({ home }), "utf8"));
326
+ const parsed = JSON.parse(fs.readFileSync(qoderActivityCachePath({ home, namespace }), "utf8"));
316
327
  const window = parsed?.activity_window;
317
328
  const cachedAtMs = Date.parse(parsed?.cached_at || "");
318
329
  if (!window || !Number.isFinite(cachedAtMs) || cachedAtMs > nowMs + 60_000) return null;
@@ -336,9 +347,10 @@ function readQoderActivityCache({
336
347
  function writeQoderActivityCache(window, {
337
348
  home = os.homedir(),
338
349
  nowMs = Date.now(),
350
+ namespace,
339
351
  } = {}) {
340
352
  if (!window) return;
341
- const cachePath = qoderActivityCachePath({ home });
353
+ const cachePath = qoderActivityCachePath({ home, namespace });
342
354
  const payload = {
343
355
  activity_window: window,
344
356
  cached_at: new Date(nowMs).toISOString(),
@@ -351,8 +363,11 @@ function writeQoderActivityCache(window, {
351
363
  } catch (_error) {}
352
364
  }
353
365
 
354
- function qoderLimitsCachePath({ home = os.homedir() } = {}) {
355
- return path.join(home, ".tokentracker", "tracker", QODER_LIMITS_CACHE_FILE);
366
+ function qoderLimitsCachePath({ home = os.homedir(), namespace } = {}) {
367
+ const file = namespace
368
+ ? `qoder-${namespace}-usage-limits-cache.json`
369
+ : QODER_LIMITS_CACHE_FILE;
370
+ return path.join(home, ".tokentracker", "tracker", file);
356
371
  }
357
372
 
358
373
  function qoderCachedWindow(window, { cachedAtMs, nowMs } = {}) {
@@ -365,9 +380,10 @@ function qoderCachedWindow(window, { cachedAtMs, nowMs } = {}) {
365
380
  function readQoderLimitsCache({
366
381
  home = os.homedir(),
367
382
  nowMs = Date.now(),
383
+ namespace,
368
384
  } = {}) {
369
385
  try {
370
- const raw = JSON.parse(fs.readFileSync(qoderLimitsCachePath({ home }), "utf8"))?.qoder;
386
+ const raw = JSON.parse(fs.readFileSync(qoderLimitsCachePath({ home, namespace }), "utf8"))?.qoder;
371
387
  const cachedAtMs = Date.parse(raw?.cached_at || "");
372
388
  if (!Number.isFinite(cachedAtMs) || cachedAtMs > nowMs + 60_000) return null;
373
389
  const primaryWindow = qoderCachedWindow(raw.primary_window, { cachedAtMs, nowMs });
@@ -398,9 +414,10 @@ function readQoderLimitsCache({
398
414
  function writeQoderLimitsCache(limits, {
399
415
  home = os.homedir(),
400
416
  nowMs = Date.now(),
417
+ namespace,
401
418
  } = {}) {
402
419
  if (!limits?.configured || limits.error || (!limits.primary_window && !limits.secondary_window)) return;
403
- const cachePath = qoderLimitsCachePath({ home });
420
+ const cachePath = qoderLimitsCachePath({ home, namespace });
404
421
  const payload = {
405
422
  qoder: {
406
423
  plan_label: limits.plan_label || null,
@@ -484,7 +501,8 @@ function buildQoderActivityHeaders(authStatus, {
484
501
  }
485
502
 
486
503
  async function fetchQoderActivity(authStatus, fetchImpl = fetch, options = {}) {
487
- const response = await fetchImpl(QODER_ACTIVITY_URL, {
504
+ const activityUrl = options.activityUrl || QODER_ACTIVITY_URL;
505
+ const response = await fetchImpl(activityUrl, {
488
506
  method: "GET",
489
507
  headers: buildQoderActivityHeaders(authStatus, options),
490
508
  });
@@ -595,22 +613,26 @@ function readQoderLocalQuota({
595
613
  home = os.homedir(),
596
614
  platform = process.platform,
597
615
  env = process.env,
616
+ appDir = "Qoder",
617
+ envPrefix = "QODER",
598
618
  } = {}) {
619
+ const logRootKey = `${envPrefix}_LOG_ROOT`;
620
+ const homeKey = `${envPrefix}_HOME`;
599
621
  const explicitLogRoot =
600
- typeof env.QODER_LOG_ROOT === "string" && env.QODER_LOG_ROOT.trim()
601
- ? path.resolve(env.QODER_LOG_ROOT.trim())
622
+ typeof env[logRootKey] === "string" && env[logRootKey].trim()
623
+ ? path.resolve(env[logRootKey].trim())
602
624
  : null;
603
625
  const configuredRoot =
604
- typeof env.QODER_HOME === "string" && env.QODER_HOME.trim()
605
- ? path.resolve(env.QODER_HOME.trim())
626
+ typeof env[homeKey] === "string" && env[homeKey].trim()
627
+ ? path.resolve(env[homeKey].trim())
606
628
  : null;
607
629
  const logRoot = explicitLogRoot || (configuredRoot
608
630
  ? path.join(configuredRoot, "logs")
609
631
  : platform === "darwin"
610
- ? path.join(home, "Library", "Application Support", "Qoder", "logs")
632
+ ? path.join(home, "Library", "Application Support", appDir, "logs")
611
633
  : platform === "win32"
612
- ? path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), "Qoder", "logs")
613
- : path.join(home, ".config", "Qoder", "logs"));
634
+ ? path.join(env.APPDATA || path.join(home, "AppData", "Roaming"), appDir, "logs")
635
+ : path.join(home, ".config", appDir, "logs"));
614
636
  for (const logPath of listRecentQoderRendererLogs(logRoot)) {
615
637
  try {
616
638
  const fd = fs.openSync(logPath, "r");
@@ -640,6 +662,11 @@ async function fetchQoderLimits({
640
662
  home = os.homedir(),
641
663
  env = process.env,
642
664
  platform = process.platform,
665
+ appDir = "Qoder",
666
+ envPrefix = "QODER",
667
+ site = QODER_SITES.international,
668
+ sitePinned = false,
669
+ cacheNamespace,
643
670
  fetchImpl = fetch,
644
671
  rpcRequest = qoderRpcRequest,
645
672
  nowMs = Date.now(),
@@ -648,7 +675,7 @@ async function fetchQoderLimits({
648
675
  let rpcAuth = null;
649
676
  let activityWindow = null;
650
677
  let activitySource = null;
651
- const rpcOptions = { home, env, platform };
678
+ const rpcOptions = { home, env, platform, appDir, envPrefix };
652
679
  // Qoder's shared client accepts only one reliable request at a time. Parallel
653
680
  // connections intermittently time out even while the desktop app is running.
654
681
  try {
@@ -660,10 +687,10 @@ async function fetchQoderLimits({
660
687
  } catch (_error) {}
661
688
  if (rpcAuth) {
662
689
  try {
663
- activityWindow = await fetchQoderActivity(rpcAuth, fetchImpl, { nowMs });
690
+ activityWindow = await fetchQoderActivity(rpcAuth, fetchImpl, { nowMs, activityUrl: site.activityUrl });
664
691
  if (activityWindow) {
665
692
  activitySource = "provider-api";
666
- writeQoderActivityCache(activityWindow, { home, nowMs });
693
+ writeQoderActivityCache(activityWindow, { home, nowMs, namespace: cacheNamespace });
667
694
  }
668
695
  } catch (_error) {}
669
696
  }
@@ -672,7 +699,7 @@ async function fetchQoderLimits({
672
699
  // omission, not an authoritative deletion: an activity carries its own
673
700
  // expiry, so the last observed window remains valid until reset_at.
674
701
  if (!activityWindow) {
675
- activityWindow = readQoderActivityCache({ home, nowMs });
702
+ activityWindow = readQoderActivityCache({ home, nowMs, namespace: cacheNamespace });
676
703
  if (activityWindow) activitySource = "disk-cache";
677
704
  }
678
705
  if (rpcUsage) {
@@ -683,21 +710,32 @@ async function fetchQoderLimits({
683
710
  rpcUsage.source = activityWindow
684
711
  ? `local-ipc+${activitySource === "disk-cache" ? "cached-activity" : "provider-api"}`
685
712
  : "local-ipc";
686
- writeQoderLimitsCache(rpcUsage, { home, nowMs });
713
+ writeQoderLimitsCache(rpcUsage, { home, nowMs, namespace: cacheNamespace });
687
714
  return rpcUsage;
688
715
  }
689
716
 
690
717
  // Match the Antigravity last-good behavior: once the local service is
691
718
  // unavailable, prefer a bounded disk snapshot before any provider fallback.
692
- const cachedLimits = readQoderLimitsCache({ home, nowMs });
719
+ const cachedLimits = readQoderLimitsCache({ home, nowMs, namespace: cacheNamespace });
693
720
  if (cachedLimits) {
694
721
  if (activityWindow) cachedLimits.secondary_window = activityWindow;
695
722
  return cachedLimits;
696
723
  }
697
724
 
698
- const manualCookie = typeof env.QODER_COOKIE === "string" ? env.QODER_COOKIE.trim() : "";
725
+ // Manual-cookie fallback reads an envPrefix-specific key: QODER_COOKIE for
726
+ // the international flow, QODER_CN_COOKIE for the CN flow — the two installs
727
+ // hold different sessions and must never share a cookie.
728
+ const cookieKey = `${envPrefix}_COOKIE`;
729
+ const manualCookie = typeof env[cookieKey] === "string" ? env[cookieKey].trim() : "";
730
+ // QODER_SITE is an international-flow knob. A pinned site (the CN fetch)
731
+ // must never be overridden by it — the CN flow always targets china.
732
+ const manualSite = sitePinned ? site : env.QODER_SITE ? siteFromEnv(env.QODER_SITE) : site;
699
733
  const sessions = manualCookie
700
- ? [{ cookieHeader: manualCookie, site: siteFromEnv(env.QODER_SITE), sourceLabel: "QODER_COOKIE" }]
734
+ ? [{
735
+ cookieHeader: manualCookie,
736
+ site: manualSite,
737
+ sourceLabel: cookieKey,
738
+ }]
701
739
  : [];
702
740
 
703
741
  let lastError = null;
@@ -709,7 +747,7 @@ async function fetchQoderLimits({
709
747
  // Preserve that status as metadata, but match Qoder's Credits UI by
710
748
  // rendering an empty 0/0 bucket as 0%, not a full red 100% bar.
711
749
  const local = result.primary_window?.limit_credits === 0
712
- ? readQoderLocalQuota({ home, platform, env })
750
+ ? readQoderLocalQuota({ home, platform, env, appDir, envPrefix })
713
751
  : null;
714
752
  if (local) {
715
753
  result.quota_exceeded = local.quota_exceeded === true;
@@ -726,20 +764,20 @@ async function fetchQoderLimits({
726
764
  cookie_source: session.sourceLabel,
727
765
  site: session.site.id,
728
766
  };
729
- writeQoderLimitsCache(liveResult, { home, nowMs });
767
+ writeQoderLimitsCache(liveResult, { home, nowMs, namespace: cacheNamespace });
730
768
  return liveResult;
731
769
  } catch (error) {
732
770
  lastError = error;
733
771
  }
734
772
  }
735
773
 
736
- const local = readQoderLocalQuota({ home, platform, env });
774
+ const local = readQoderLocalQuota({ home, platform, env, appDir, envPrefix });
737
775
  if (local) {
738
776
  if (activityWindow) {
739
777
  local.secondary_window = activityWindow;
740
778
  local.source += activitySource === "disk-cache" ? "+cached-activity" : "+activity";
741
779
  }
742
- writeQoderLimitsCache(local, { home, nowMs });
780
+ writeQoderLimitsCache(local, { home, nowMs, namespace: cacheNamespace });
743
781
  return local;
744
782
  }
745
783
  if (activityWindow) {
@@ -762,6 +800,22 @@ async function fetchQoderLimits({
762
800
  return { configured: false };
763
801
  }
764
802
 
803
+ // Qoder CN (国内版) — quota comes from qoder.com.cn and credentials live in the
804
+ // separate QoderCN data directory (its own app install). Same mechanics as the
805
+ // international fetch, but with the china site, the QoderCN data root (own
806
+ // QODER_CN_HOME / QODER_CN_DB_PATH / QODER_CN_LOG_ROOT overrides), and a
807
+ // dedicated cache namespace so the two editions never clobber each other.
808
+ function fetchQoderCnLimits(opts = {}) {
809
+ return fetchQoderLimits({
810
+ ...opts,
811
+ appDir: "QoderCN",
812
+ envPrefix: "QODER_CN",
813
+ site: QODER_SITES.china,
814
+ sitePinned: true,
815
+ cacheNamespace: "cn",
816
+ });
817
+ }
818
+
765
819
  module.exports = {
766
820
  QODER_SITES,
767
821
  normalizeQoderUsageResponse,
@@ -779,4 +833,5 @@ module.exports = {
779
833
  parseQoderQuotaLog,
780
834
  readQoderLocalQuota,
781
835
  fetchQoderLimits,
836
+ fetchQoderCnLimits,
782
837
  };