tokentracker-cli 0.96.1 → 0.96.3

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 (145) hide show
  1. package/README.de.md +34 -25
  2. package/README.ja.md +38 -27
  3. package/README.ko.md +39 -28
  4. package/README.md +36 -28
  5. package/README.zh-CN.md +36 -25
  6. package/dashboard/dist/assets/{AchievementBadge-DKyElm0T.js → AchievementBadge-BX4iBBYM.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-BvCx3wND.js → AchievementsPage-DRYRuCbh.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-DN69LzXX.js → AchievementsSection-BM_JWWhJ.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-DGiKdZfz.js → BadgeDetailModal-DxoxXst2.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-zN8rH7_K.js → BotAnimated-BVpFxG8u.js} +1 -1
  11. package/dashboard/dist/assets/{Card-COWo-ClV.js → Card-BJJs701t.js} +1 -1
  12. package/dashboard/dist/assets/{ClawdAnimated-BWsB7Eat.js → ClawdAnimated-46wPZ_Tc.js} +2 -2
  13. package/dashboard/dist/assets/{CommandPalette-BfvIwuoJ.js → CommandPalette-B_zkc7IU.js} +1 -1
  14. package/dashboard/dist/assets/{CommunityStatsModal-BQWg7R_y.js → CommunityStatsModal-M2k5rIf1.js} +1 -1
  15. package/dashboard/dist/assets/{ConfirmModal-C7ABSI-O.js → ConfirmModal-DBvOTcAQ.js} +1 -1
  16. package/dashboard/dist/assets/{Controls-vMzEZzej.js → Controls-BCkjPMSH.js} +1 -1
  17. package/dashboard/dist/assets/DashboardPage-3bHhou09.js +1 -0
  18. package/dashboard/dist/assets/{DevicePage-3ejUBLWM.js → DevicePage-DFBUce8C.js} +1 -1
  19. package/dashboard/dist/assets/DialogClose-kvDd8h3V.js +1 -0
  20. package/dashboard/dist/assets/DialogDescription-BMyOicfz.js +1 -0
  21. package/dashboard/dist/assets/DialogTitle-DL5nvD31.js +1 -0
  22. package/dashboard/dist/assets/{FadeIn-D7Jcfz1N.js → FadeIn-B-Bg6SEy.js} +1 -1
  23. package/dashboard/dist/assets/{HeaderGithubStar-D--Rx5BS.js → HeaderGithubStar-BExz0hh4.js} +1 -1
  24. package/dashboard/dist/assets/{HoverTooltip-n1W0z3Wr.js → HoverTooltip-Dj5zemYT.js} +1 -1
  25. package/dashboard/dist/assets/{IpCheckPage-D202tl9B.js → IpCheckPage-DN7FuJ_v.js} +1 -1
  26. package/dashboard/dist/assets/{LandingPage-D6yL2EZo.js → LandingPage-9dKgSBRy.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardAvatar-ChFg2mtA.js → LeaderboardAvatar-00V95XIg.js} +1 -1
  28. package/dashboard/dist/assets/{LeaderboardPage-BXHwGs8r.js → LeaderboardPage-DkKfUt0Z.js} +2 -2
  29. package/dashboard/dist/assets/{LeaderboardProfileModal-B5LoOpCk.js → LeaderboardProfileModal-C9sIHBgN.js} +4 -4
  30. package/dashboard/dist/assets/{LeaderboardProfilePage-jsukhRz8.js → LeaderboardProfilePage-BAiLMsEb.js} +1 -1
  31. package/dashboard/dist/assets/LimitsPage-BuenTaHa.js +8 -0
  32. package/dashboard/dist/assets/{LocalOnlyNotice-Du0lz657.js → LocalOnlyNotice-CKhY27Lr.js} +1 -1
  33. package/dashboard/dist/assets/{LoginCard-BFEX9x7-.js → LoginCard-BbvoBx5N.js} +1 -1
  34. package/dashboard/dist/assets/{LoginModal-DqC7_d35.js → LoginModal-P7zIkBhO.js} +1 -1
  35. package/dashboard/dist/assets/{LoginPage-Dzx8N7HE.js → LoginPage-Bo0bR26x.js} +1 -1
  36. package/dashboard/dist/assets/PetPage-C6i9bSSl.js +1 -0
  37. package/dashboard/dist/assets/PopoverPopup-h0gf36vg.js +1 -0
  38. package/dashboard/dist/assets/ProviderIcon-Dm8fwPdj.js +1 -0
  39. package/dashboard/dist/assets/{ResetPasswordPage-x_GR79sR.js → ResetPasswordPage-DvSJ8kg0.js} +1 -1
  40. package/dashboard/dist/assets/{SegmentedControl-BxDiDV4R.js → SegmentedControl-CP7oiGZE.js} +1 -1
  41. package/dashboard/dist/assets/Select-iwP92x9C.js +1 -0
  42. package/dashboard/dist/assets/SelectItemText-DzljA15_.js +1 -0
  43. package/dashboard/dist/assets/{ServiceStatusPage-BVQuVdjg.js → ServiceStatusPage-uD4D0Tid.js} +1 -1
  44. package/dashboard/dist/assets/{SessionsPage-DE39wNIO.js → SessionsPage-Cmb1ssNP.js} +2 -2
  45. package/dashboard/dist/assets/SettingsPage-D8ryg1Pv.js +2 -0
  46. package/dashboard/dist/assets/SkillsPage-eXI09q03.js +1 -0
  47. package/dashboard/dist/assets/{TokenGalaxy-BPM779Wb.js → TokenGalaxy-f2djrlUv.js} +1 -1
  48. package/dashboard/dist/assets/ToolbarRootContext-CDxCPYLv.js +1 -0
  49. package/dashboard/dist/assets/{TrendMonitor-BkNy-o1D.js → TrendMonitor-C419X22G.js} +1 -1
  50. package/dashboard/dist/assets/{WidgetsPage-Bpg6H-ZB.js → WidgetsPage-AYq__pOO.js} +1 -1
  51. package/dashboard/dist/assets/{WrappedPage-C6-NM-RO.js → WrappedPage-Bsm9k5XI.js} +1 -1
  52. package/dashboard/dist/assets/{__vite-browser-external-CvMMIjhi.css → __vite-browser-external-5P7me0Qq.css} +1 -1
  53. package/dashboard/dist/assets/__vite-browser-external-BDkeh90N.js +45 -0
  54. package/dashboard/dist/assets/{agent-logos-DpbkRqXT.js → agent-logos-B6Vg-4_1.js} +1 -1
  55. package/dashboard/dist/assets/{arrow-up-right-BjOtWUVs.js → arrow-up-right-DIQXwaOD.js} +1 -1
  56. package/dashboard/dist/assets/{check-BlxnX5jZ.js → check-CgD_bV39.js} +1 -1
  57. package/dashboard/dist/assets/composite-BIgEqWZD.js +1 -0
  58. package/dashboard/dist/assets/{copy-CIsWjLY5.js → copy-DlM1BHdp.js} +1 -1
  59. package/dashboard/dist/assets/{download-QDaQbQdd.js → download-CQFG-VIY.js} +1 -1
  60. package/dashboard/dist/assets/{external-link-DPV4VU3m.js → external-link-BQyDqGxz.js} +1 -1
  61. package/dashboard/dist/assets/{flame-B_X-7VNp.js → flame-BamzyXzF.js} +1 -1
  62. package/dashboard/dist/assets/{format-tokens-DEx2TJj8.js → format-tokens-C4G-_xYt.js} +1 -1
  63. package/dashboard/dist/assets/{icons-983RyGSl.js → icons-Ntscvl1a.js} +1 -1
  64. package/dashboard/dist/assets/{index-qtonw_x4.js → index-CUrcPqgp.js} +1 -1
  65. package/dashboard/dist/assets/{index-BONYA8HZ.js → index-DGxgsNoQ.js} +1 -1
  66. package/dashboard/dist/assets/{index-Cjs5stC0.js → index-FRIt5mlv.js} +1 -1
  67. package/dashboard/dist/assets/{info-DpVOhXZI.js → info-CXytWcZi.js} +1 -1
  68. package/dashboard/dist/assets/{link-2-CBro-upz.js → link-2-Dexoo_jO.js} +1 -1
  69. package/dashboard/dist/assets/{loader-circle-wYFdSyOd.js → loader-circle-Cn0JHc8t.js} +1 -1
  70. package/dashboard/dist/assets/{maximize-2-DhTyQ4ly.js → maximize-2-DLh7gYuP.js} +1 -1
  71. package/dashboard/dist/assets/{modifiers.esm-CPljwVhN.js → modifiers.esm-DOh_xtyt.js} +1 -1
  72. package/dashboard/dist/assets/module-DUSXqJtA.js +3 -0
  73. package/dashboard/dist/assets/{monitor-smartphone-D_wr-HOR.js → monitor-smartphone-CpqS9Ior.js} +1 -1
  74. package/dashboard/dist/assets/{plus-BNjfkBEF.js → plus-bfQV7nyv.js} +1 -1
  75. package/dashboard/dist/assets/provider-display-Bf9pW06U.js +42 -0
  76. package/dashboard/dist/assets/{react-yHv9MTUA.js → react-BmFZKA_b.js} +1 -1
  77. package/dashboard/dist/assets/{search-Dfak92ZA.js → search-l3jJ2fzC.js} +1 -1
  78. package/dashboard/dist/assets/{skills-api-By2PdNa1.js → skills-api-CWmLnaxn.js} +1 -1
  79. package/dashboard/dist/assets/{sortable.esm-B9xy66_v.js → sortable.esm-DLgyXLHM.js} +1 -1
  80. package/dashboard/dist/assets/store-JM-a8ElZ.js +1 -0
  81. package/dashboard/dist/assets/{terminal-pbXWBmZM.js → terminal-6aY2Sn8P.js} +1 -1
  82. package/dashboard/dist/assets/{trash-2-CboDPwbW.js → trash-D5oon5pT.js} +1 -1
  83. package/dashboard/dist/assets/{use-community-stats-D6HTdnHq.js → use-community-stats-ZWUMFwzc.js} +1 -1
  84. package/dashboard/dist/assets/{use-latest-request-guard-hwSr9Cam.js → use-latest-request-guard-BDaXr4gK.js} +1 -1
  85. package/dashboard/dist/assets/use-limits-display-prefs-yXXToNIc.js +1 -0
  86. package/dashboard/dist/assets/{use-ordered-list-OtfOqrNb.js → use-ordered-list-C6RG8N6c.js} +1 -1
  87. package/dashboard/dist/assets/use-reduced-motion-BqDKim_3.js +1 -0
  88. package/dashboard/dist/assets/{use-session-efficiency-pref-BScvtS8F.js → use-session-efficiency-pref-DpgZO_Lx.js} +1 -1
  89. package/dashboard/dist/assets/{use-transform-Cy1ykjEs.js → use-transform-D42P7jkd.js} +1 -1
  90. package/dashboard/dist/assets/use-usage-limits-CV6puFwq.js +1 -0
  91. package/dashboard/dist/assets/{useCurrency-VqF3cy1C.js → useCurrency-GWBmOrCR.js} +1 -1
  92. package/dashboard/dist/assets/{useTokenFormat-Cgub4MXu.js → useTokenFormat-Beybkb0v.js} +1 -1
  93. package/dashboard/dist/assets/{zap-DStzvQJI.js → zap-CEq2616W.js} +1 -1
  94. package/dashboard/dist/brand-logos/acode-dark.png +0 -0
  95. package/dashboard/dist/brand-logos/acode.png +0 -0
  96. package/dashboard/dist/brand-logos/commandcode.svg +1 -0
  97. package/dashboard/dist/brand-logos/devin.svg +6 -0
  98. package/dashboard/dist/index.html +20 -16
  99. package/dashboard/dist/ip-check.html +2 -2
  100. package/dashboard/dist/leaderboard.html +2 -2
  101. package/dashboard/dist/llms.txt +4 -2
  102. package/dashboard/dist/share.html +2 -2
  103. package/package.json +2 -2
  104. package/src/commands/init.js +114 -4
  105. package/src/commands/status.js +37 -0
  106. package/src/commands/sync.js +92 -10
  107. package/src/commands/uninstall.js +23 -0
  108. package/src/lib/codex-config.js +42 -0
  109. package/src/lib/codex-rollout-parser.js +2 -2
  110. package/src/lib/commandcode-limits.js +317 -0
  111. package/src/lib/devin-limits.js +249 -0
  112. package/src/lib/devin-usage.js +212 -0
  113. package/src/lib/local-api.js +14 -0
  114. package/src/lib/passive-mode.js +13 -1
  115. package/src/lib/pricing/curated-overrides.json +78 -1
  116. package/src/lib/pricing/index.js +11 -9
  117. package/src/lib/pricing/matcher.js +28 -0
  118. package/src/lib/pricing/seed-snapshot.json +1 -1
  119. package/src/lib/rollout.js +1473 -147
  120. package/src/lib/session-analytics.js +2 -2
  121. package/src/lib/skills-manager.js +5 -0
  122. package/src/lib/subscriptions.js +70 -16
  123. package/src/lib/usage-limits.js +240 -49
  124. package/dashboard/dist/assets/DashboardPage-QBhbCMZF.js +0 -1
  125. package/dashboard/dist/assets/DialogClose-D_dvtfGV.js +0 -1
  126. package/dashboard/dist/assets/DialogDescription-ryGYdQjX.js +0 -1
  127. package/dashboard/dist/assets/DialogTitle-_ahf0z8b.js +0 -1
  128. package/dashboard/dist/assets/LimitsPage-PvqkOOwp.js +0 -6
  129. package/dashboard/dist/assets/PetPage-CNlWHF4z.js +0 -1
  130. package/dashboard/dist/assets/PopoverPopup-D9jadD3j.js +0 -1
  131. package/dashboard/dist/assets/ProviderIcon-Caz5PuAm.js +0 -1
  132. package/dashboard/dist/assets/Select-B5o-1sEl.js +0 -1
  133. package/dashboard/dist/assets/SelectItemText-CiA6L7Wk.js +0 -1
  134. package/dashboard/dist/assets/SettingsPage-DJrb_fsK.js +0 -2
  135. package/dashboard/dist/assets/SkillsPage-COk-YQQ5.js +0 -1
  136. package/dashboard/dist/assets/ToolbarRootContext-CozJT6Vs.js +0 -1
  137. package/dashboard/dist/assets/__vite-browser-external-BGnq8Ebm.js +0 -45
  138. package/dashboard/dist/assets/composite-Dxmivlk4.js +0 -1
  139. package/dashboard/dist/assets/limits-providers-BUoquSA-.js +0 -1
  140. package/dashboard/dist/assets/module-xpSFyNBK.js +0 -3
  141. package/dashboard/dist/assets/provider-display-BOBtUyFi.js +0 -42
  142. package/dashboard/dist/assets/store-oQZR4d9M.js +0 -1
  143. package/dashboard/dist/assets/use-limits-display-prefs-B-pbwUS8.js +0 -1
  144. package/dashboard/dist/assets/use-reduced-motion-DGXcZBX3.js +0 -1
  145. package/dashboard/dist/assets/use-usage-limits-CSMb2zYW.js +0 -1
@@ -190,8 +190,8 @@ function normalizeModelUsageRows(rows) {
190
190
  // selected_models re-seeds from `model`, model_attribution is derived from
191
191
  // rerouted_usage_events, and cost_usd is always recomputed by
192
192
  // repriceSessionRecord(). Worth ~19% of the file on a 6.8k-session history,
193
- // most of it the long_context_* counters that stay at zero (see
194
- // OPENAI_LONG_CONTEXT_INPUT_THRESHOLD in src/lib/pricing/index.js).
193
+ // most of it the long_context_* counters for sessions with no requests over
194
+ // OPENAI_LONG_CONTEXT_INPUT_THRESHOLD in src/lib/pricing/index.js.
195
195
  function serializeModelUsageRow(row) {
196
196
  const out = { model: row.model };
197
197
  for (const field of MODEL_USAGE_SUM_FIELDS) {
@@ -15,6 +15,7 @@ const DEFAULT_REPOS = [
15
15
  const TARGETS = {
16
16
  claude: { id: "claude", label: "Claude", dir: () => path.join(os.homedir(), ".claude", "skills") },
17
17
  codex: { id: "codex", label: "Codex", dir: () => path.join(resolveCodexHome(), "skills") },
18
+ acode: { id: "acode", label: "AStudio", dir: () => path.join(resolveAcodeHome(), "skills") },
18
19
  grok: { id: "grok", label: "Grok", dir: () => path.join(resolveGrokHome(process.env), "skills") },
19
20
  antigravity: { id: "antigravity", label: "Antigravity", dirs: () => resolveAntigravitySkillDirs(process.env) },
20
21
  gemini: { id: "gemini", label: "Gemini", dir: () => path.join(os.homedir(), ".gemini", "skills") },
@@ -32,6 +33,10 @@ function resolveCodexHome() {
32
33
  return String(process.env.CODEX_HOME || "").trim() || path.join(os.homedir(), ".codex");
33
34
  }
34
35
 
36
+ function resolveAcodeHome() {
37
+ return String(process.env.TOKENTRACKER_ACODE_HOME || "").trim() || path.join(os.homedir(), ".acode");
38
+ }
39
+
35
40
  function resolveZcodeHome() {
36
41
  return String(process.env.ZCODE_HOME || "").trim() || path.join(os.homedir(), ".zcode");
37
42
  }
@@ -19,6 +19,9 @@ const CLAUDE_CODE_CREDENTIALS_FILE = ".credentials.json";
19
19
  // Platforms where Claude Code stores credentials in the plain JSON file above
20
20
  // rather than the macOS Keychain.
21
21
  const CLAUDE_CODE_CREDENTIALS_FILE_PLATFORMS = new Set(["linux", "win32"]);
22
+ // Refresh slightly before wall-clock expiry so Limits does not spend a request
23
+ // that Anthropic answers with 429 for a real-but-expired Claude Code token.
24
+ const CLAUDE_TOKEN_EXPIRY_SKEW_MS = 60_000;
22
25
 
23
26
  function usesClaudeCodeCredentialsFile(platform) {
24
27
  return CLAUDE_CODE_CREDENTIALS_FILE_PLATFORMS.has(platform);
@@ -164,14 +167,23 @@ async function detectOpencodeChatgptSubscription({ home, env }) {
164
167
  };
165
168
  }
166
169
 
167
- function probeMacosKeychainGenericPassword({ service, securityRunner, timeoutMs } = {}) {
170
+ // Match Claude Code 2.1.267's account derivation. A service-only lookup may
171
+ // select credentials from an older installation before the current account.
172
+ function resolveClaudeKeychainAccount({ env = process.env, userInfo = os.userInfo } = {}) {
173
+ let account;
174
+ try { account = env.USER || userInfo().username; } catch (_error) { return "claude-code-user"; }
175
+ return typeof account === "string" && /^[a-zA-Z0-9._-]+$/.test(account)
176
+ ? account : "claude-code-user";
177
+ }
178
+
179
+ function probeMacosKeychainGenericPassword({ service, securityRunner, timeoutMs, env } = {}) {
168
180
  const svc = normalizeString(service);
169
181
  if (!svc) return false;
170
182
 
171
183
  const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
172
184
  if (runner === cp.spawnSync && !fs.existsSync(MACOS_SECURITY_BIN)) return false;
173
185
 
174
- const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc], {
186
+ const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc, "-a", resolveClaudeKeychainAccount({ env })], {
175
187
  stdio: "ignore",
176
188
  timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
177
189
  });
@@ -180,14 +192,14 @@ function probeMacosKeychainGenericPassword({ service, securityRunner, timeoutMs
180
192
  return result.status === 0;
181
193
  }
182
194
 
183
- function readMacosKeychainPassword({ service, securityRunner, timeoutMs } = {}) {
195
+ function readMacosKeychainPassword({ service, securityRunner, timeoutMs, env } = {}) {
184
196
  const svc = normalizeString(service);
185
197
  if (!svc) return null;
186
198
 
187
199
  const runner = typeof securityRunner === "function" ? securityRunner : cp.spawnSync;
188
200
  if (runner === cp.spawnSync && !fs.existsSync(MACOS_SECURITY_BIN)) return null;
189
201
 
190
- const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc, "-w"], {
202
+ const result = runner(MACOS_SECURITY_BIN, ["find-generic-password", "-s", svc, "-a", resolveClaudeKeychainAccount({ env }), "-w"], {
191
203
  stdio: ["ignore", "pipe", "ignore"],
192
204
  timeout: Number.isFinite(timeoutMs) ? timeoutMs : 2000,
193
205
  encoding: "utf8",
@@ -217,12 +229,13 @@ function readClaudeCodeCredentialsFile({ home, fsReader } = {}) {
217
229
  }
218
230
  }
219
231
 
220
- function detectClaudeCodeCredentialsPresence({ platform = process.platform, securityRunner, home, fsReader } = {}) {
232
+ function detectClaudeCodeCredentialsPresence({ platform = process.platform, securityRunner, home, fsReader, env } = {}) {
221
233
  if (platform === "darwin") {
222
234
  for (const service of CLAUDE_CODE_KEYCHAIN_SERVICES) {
223
235
  const present = probeMacosKeychainGenericPassword({
224
236
  service,
225
237
  securityRunner,
238
+ env,
226
239
  });
227
240
  if (!present) continue;
228
241
 
@@ -260,6 +273,31 @@ function detectClaudeCodeCredentialsPresence({ platform = process.platform, secu
260
273
  return null;
261
274
  }
262
275
 
276
+ function parseClaudeOauthExpiryMs(value) {
277
+ if (typeof value === "number") {
278
+ if (!Number.isFinite(value)) return null;
279
+ if (value <= 0) return 0;
280
+ return value < 1e12 ? value * 1000 : value;
281
+ }
282
+ if (typeof value === "string") {
283
+ const trimmed = value.trim();
284
+ if (!trimmed) return null;
285
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) return parseClaudeOauthExpiryMs(Number(trimmed));
286
+ const ms = Date.parse(trimmed);
287
+ return Number.isFinite(ms) ? ms : null;
288
+ }
289
+ return null;
290
+ }
291
+
292
+ function extractClaudeCodeOauth(payload, nowMs = Date.now()) {
293
+ const oauth = payload?.claudeAiOauth;
294
+ const accessToken = normalizeString(oauth?.accessToken);
295
+ if (!accessToken) return null;
296
+ const expiresAtMs = parseClaudeOauthExpiryMs(oauth?.expiresAt);
297
+ if (expiresAtMs != null && expiresAtMs <= nowMs + CLAUDE_TOKEN_EXPIRY_SKEW_MS) return null;
298
+ return { accessToken, expiresAtMs };
299
+ }
300
+
263
301
  function extractClaudeKeychainSubscription(payload) {
264
302
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
265
303
 
@@ -273,11 +311,11 @@ function extractClaudeKeychainSubscription(payload) {
273
311
  return { subscriptionType, rateLimitTier };
274
312
  }
275
313
 
276
- function detectClaudeCodeSubscriptionDetails({ platform = process.platform, securityRunner, home, fsReader } = {}) {
314
+ function detectClaudeCodeSubscriptionDetails({ platform = process.platform, securityRunner, home, fsReader, env } = {}) {
277
315
  const rawPayloads = [];
278
316
  if (platform === "darwin") {
279
317
  for (const service of CLAUDE_CODE_KEYCHAIN_SERVICES) {
280
- const raw = readMacosKeychainPassword({ service, securityRunner });
318
+ const raw = readMacosKeychainPassword({ service, securityRunner, env });
281
319
  if (raw) rawPayloads.push(raw);
282
320
  }
283
321
  } else if (usesClaudeCodeCredentialsFile(platform)) {
@@ -327,14 +365,14 @@ async function collectLocalSubscriptions({
327
365
  if (opencode) out.push(opencode);
328
366
 
329
367
  if (probeKeychainDetails) {
330
- const claude = detectClaudeCodeSubscriptionDetails({ platform, securityRunner, home });
368
+ const claude = detectClaudeCodeSubscriptionDetails({ platform, securityRunner, home, env });
331
369
  if (claude) out.push(claude);
332
370
  else if (probeKeychain) {
333
- const present = detectClaudeCodeCredentialsPresence({ platform, securityRunner, home });
371
+ const present = detectClaudeCodeCredentialsPresence({ platform, securityRunner, home, env });
334
372
  if (present) out.push(present);
335
373
  }
336
374
  } else if (probeKeychain) {
337
- const claude = detectClaudeCodeCredentialsPresence({ platform, securityRunner, home });
375
+ const claude = detectClaudeCodeCredentialsPresence({ platform, securityRunner, home, env });
338
376
  if (claude) out.push(claude);
339
377
  }
340
378
 
@@ -364,14 +402,25 @@ async function detectOpenclawSessionIntegration({ home, env }) {
364
402
  };
365
403
  }
366
404
 
367
- function readClaudeCodeAccessToken({ platform = process.platform, securityRunner, home, fsReader } = {}) {
405
+ // Returns the live OAuth token plus its expiry stamp. The expiry doubles as a
406
+ // rotation marker for the 429 cool-down: it changes on every refresh and, unlike
407
+ // the token itself, is not a secret, so nothing derived from a credential has to
408
+ // be written to disk.
409
+ function readClaudeCodeOauthToken({
410
+ platform = process.platform,
411
+ securityRunner,
412
+ home,
413
+ fsReader,
414
+ env,
415
+ nowMs = Date.now(),
416
+ } = {}) {
368
417
  if (platform === "darwin") {
369
418
  for (const service of CLAUDE_CODE_KEYCHAIN_SERVICES) {
370
419
  try {
371
- const raw = readMacosKeychainPassword({ service, securityRunner });
420
+ const raw = readMacosKeychainPassword({ service, securityRunner, env });
372
421
  if (!raw) continue;
373
- const payload = JSON.parse(raw);
374
- return normalizeString(payload?.claudeAiOauth?.accessToken);
422
+ const oauth = extractClaudeCodeOauth(JSON.parse(raw), nowMs);
423
+ if (oauth) return oauth;
375
424
  } catch (_e) {
376
425
  continue;
377
426
  }
@@ -386,13 +435,16 @@ function readClaudeCodeAccessToken({ platform = process.platform, securityRunner
386
435
  const raw = readClaudeCodeCredentialsFile({ home, fsReader });
387
436
  if (!raw) return null;
388
437
  try {
389
- const payload = JSON.parse(raw);
390
- return normalizeString(payload?.claudeAiOauth?.accessToken);
438
+ return extractClaudeCodeOauth(JSON.parse(raw), nowMs);
391
439
  } catch (_e) {
392
440
  return null;
393
441
  }
394
442
  }
395
443
 
444
+ function readClaudeCodeAccessToken(options = {}) {
445
+ return readClaudeCodeOauthToken(options)?.accessToken ?? null;
446
+ }
447
+
396
448
  async function readCodexAccessToken({ home, env } = {}) {
397
449
  try {
398
450
  const codexHome = resolveCodexHome({ home, env });
@@ -445,10 +497,12 @@ async function readCodexAuthBundle({ home, env } = {}) {
445
497
  }
446
498
 
447
499
  module.exports = {
500
+ resolveClaudeKeychainAccount,
448
501
  collectLocalSubscriptions,
449
502
  detectClaudeCodeCredentialsPresence,
450
503
  detectClaudeCodeSubscriptionDetails,
451
504
  readClaudeCodeAccessToken,
505
+ readClaudeCodeOauthToken,
452
506
  readCodexAccessToken,
453
507
  readCodexAuthBundle,
454
508
  };