tokentracker-cli 1.5.14 → 1.5.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 (119) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-DqJWve1h.js → AchievementBadge-CnnJGct8.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-CnuBayj3.js → AchievementsPage-CFkUJVxy.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-WRU5TxAB.js → AchievementsSection-CoLQX04M.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-B5ikEQik.js → BadgeDetailModal-s8rObEHY.js} +1 -1
  5. package/dashboard/dist/assets/{BotAnimated-CaMG6j-6.js → BotAnimated-BmBnL-i8.js} +1 -1
  6. package/dashboard/dist/assets/{Card-BL3vxENk.js → Card-BIqie5mN.js} +1 -1
  7. package/dashboard/dist/assets/{CloudCheckoutPage-CsUb8CJ7.js → CloudCheckoutPage-B_c8Q58A.js} +1 -1
  8. package/dashboard/dist/assets/{CloudPage-5ZiPFzeC.js → CloudPage-CfW_pDf2.js} +2 -2
  9. package/dashboard/dist/assets/{CommandPalette-CYd9nXW-.js → CommandPalette-BRryGPVJ.js} +1 -1
  10. package/dashboard/dist/assets/{CommunityStatsModal-BItox7Sz.js → CommunityStatsModal-Cyh1Q9Fw.js} +1 -1
  11. package/dashboard/dist/assets/{ConfirmModal-BE1ABR4z.js → ConfirmModal-DWBYqpLJ.js} +1 -1
  12. package/dashboard/dist/assets/{Controls-BRqIsaHT.js → Controls-CPSiCzVW.js} +1 -1
  13. package/dashboard/dist/assets/{DashboardPage-Cm0YuBlx.js → DashboardPage-JcS1xwUm.js} +1 -1
  14. package/dashboard/dist/assets/{DateRangePopover-CRwEu66e.js → DateRangePopover-DjuS6888.js} +1 -1
  15. package/dashboard/dist/assets/{DevicePage-DFUPsGb_.js → DevicePage-DZ3qiDbT.js} +1 -1
  16. package/dashboard/dist/assets/{DialogClose-DALl4GmP.js → DialogClose-CfORRnoT.js} +1 -1
  17. package/dashboard/dist/assets/DialogDescription-ChihKLnm.js +1 -0
  18. package/dashboard/dist/assets/{DialogTitle-BSR8ozyn.js → DialogTitle-DZO6-sTI.js} +1 -1
  19. package/dashboard/dist/assets/{ElectricLogo-BtrycBUW.js → ElectricLogo-BzR7mRin.js} +1 -1
  20. package/dashboard/dist/assets/{FadeIn-CfhgvLVd.js → FadeIn-BxHaUvz_.js} +1 -1
  21. package/dashboard/dist/assets/{HeaderGithubStar-N8wqAdOf.js → HeaderGithubStar-KGzKAmuQ.js} +1 -1
  22. package/dashboard/dist/assets/{HoverTooltip-CwdMYIzZ.js → HoverTooltip-C6pASVDN.js} +1 -1
  23. package/dashboard/dist/assets/{IpCheckPage-DCRor9cb.js → IpCheckPage-BJ8TROZC.js} +1 -1
  24. package/dashboard/dist/assets/{LandingPage-s9xvTHSp.js → LandingPage-B15GRFpI.js} +2 -2
  25. package/dashboard/dist/assets/{LeaderboardPage-CUj3WLMo.js → LeaderboardPage-C3hJ2Gh3.js} +2 -2
  26. package/dashboard/dist/assets/{LeaderboardProBadge-N4NOKZWj.js → LeaderboardProBadge-CRfBTxpt.js} +1 -1
  27. package/dashboard/dist/assets/{LeaderboardProfileModal-09HiUyxm.js → LeaderboardProfileModal-DTGmcjWF.js} +3 -3
  28. package/dashboard/dist/assets/{LeaderboardProfilePage-Cw9_rodG.js → LeaderboardProfilePage-BD04D7ML.js} +1 -1
  29. package/dashboard/dist/assets/{LimitsPage-F8b6kZa0.js → LimitsPage-LsDCIUKd.js} +1 -1
  30. package/dashboard/dist/assets/{LocalOnlyNotice-D1eEzDEv.js → LocalOnlyNotice-B3XQKD8k.js} +1 -1
  31. package/dashboard/dist/assets/{LoginCard-0sIovG_J.js → LoginCard-DF1XcskU.js} +1 -1
  32. package/dashboard/dist/assets/{LoginModal-BT3oMVak.js → LoginModal-DuyVT0Tt.js} +1 -1
  33. package/dashboard/dist/assets/{LoginPage-CDsXdaE5.js → LoginPage-kge4kJKa.js} +1 -1
  34. package/dashboard/dist/assets/{PetPage-BEnC1azp.js → PetPage-DuYtsKza.js} +2 -2
  35. package/dashboard/dist/assets/{PopoverPopup-Dwzp-hyf.js → PopoverPopup-BkNNS3VG.js} +1 -1
  36. package/dashboard/dist/assets/{ProviderIcon-Cu_lcuuu.js → ProviderIcon-z2TKXPlS.js} +1 -1
  37. package/dashboard/dist/assets/{ResetPasswordPage-DrpEscm1.js → ResetPasswordPage-CXM9E6d1.js} +1 -1
  38. package/dashboard/dist/assets/{Select-wIR4qvEI.js → Select-CeoAayzi.js} +1 -1
  39. package/dashboard/dist/assets/{SelectItemText-CNuJgYps.js → SelectItemText-CRxvAgUV.js} +1 -1
  40. package/dashboard/dist/assets/{SelfHostPage-CWNLygpL.js → SelfHostPage-Cavstn-d.js} +1 -1
  41. package/dashboard/dist/assets/{ServiceStatusPage-BId9t16Q.js → ServiceStatusPage-CdIeM_DX.js} +1 -1
  42. package/dashboard/dist/assets/{SessionsPage-C_klx7d8.js → SessionsPage-DPihAS26.js} +1 -1
  43. package/dashboard/dist/assets/{SettingsPage-nC73JaZ4.js → SettingsPage-zxOsLeE5.js} +3 -3
  44. package/dashboard/dist/assets/{SkillsPage-B2agou8Y.js → SkillsPage-BRGonzsC.js} +1 -1
  45. package/dashboard/dist/assets/{TokenGalaxy-BNXUWRwa.js → TokenGalaxy-D9-huQJ_.js} +1 -1
  46. package/dashboard/dist/assets/{TooltipPopup--X2kExQ0.js → TooltipPopup-BS99Tf44.js} +1 -1
  47. package/dashboard/dist/assets/{TrendMonitor-BELHsTnz.js → TrendMonitor-DlNT4ObY.js} +1 -1
  48. package/dashboard/dist/assets/{WidgetsPage-DWp0SMXA.js → WidgetsPage-DTFnmTwq.js} +1 -1
  49. package/dashboard/dist/assets/{WrappedPage-DgwczKHe.js → WrappedPage-DwMdnusQ.js} +1 -1
  50. package/dashboard/dist/assets/{__vite-browser-external-BHphZRSC.js → __vite-browser-external-DR_a-OTS.js} +2 -2
  51. package/dashboard/dist/assets/{agent-logos-Cfx5WzU5.js → agent-logos-D6kTooyz.js} +1 -1
  52. package/dashboard/dist/assets/{arrow-left-D83SdANw.js → arrow-left-jpuJhYL4.js} +1 -1
  53. package/dashboard/dist/assets/{arrow-right-D7ye9bmJ.js → arrow-right-n1hLkwiK.js} +1 -1
  54. package/dashboard/dist/assets/{arrow-up-right-CLmwEwwC.js → arrow-up-right-fZmEfh7R.js} +1 -1
  55. package/dashboard/dist/assets/{calendar-days-D1qLlvh4.js → calendar-days-B0LdECvD.js} +1 -1
  56. package/dashboard/dist/assets/{check-BHJvi4ey.js → check-CcnNd8bi.js} +1 -1
  57. package/dashboard/dist/assets/{chevron-down-6RBPKNpt.js → chevron-down-DhbHBjVC.js} +1 -1
  58. package/dashboard/dist/assets/{cloud-checkout-DgWcF-at.js → cloud-checkout-9XOoux-p.js} +1 -1
  59. package/dashboard/dist/assets/{composite-D-0pfyMY.js → composite-DIDAkYhy.js} +1 -1
  60. package/dashboard/dist/assets/{copy-B6mJSQsD.js → copy-BNDBunFG.js} +1 -1
  61. package/dashboard/dist/assets/{download-D31Nn-bI.js → download-wlqdqJmq.js} +1 -1
  62. package/dashboard/dist/assets/{external-link-C_lK9S3p.js → external-link-BvssZ_45.js} +1 -1
  63. package/dashboard/dist/assets/{flame-1ZxhHshT.js → flame-DP8IUT3E.js} +1 -1
  64. package/dashboard/dist/assets/{format-tokens-DJk25SkD.js → format-tokens-DMsx_fWk.js} +1 -1
  65. package/dashboard/dist/assets/{gift-BUZzFh-1.js → gift-zF4D6O4a.js} +1 -1
  66. package/dashboard/dist/assets/{icons-BVPThCa4.js → icons-Batw5aOg.js} +1 -1
  67. package/dashboard/dist/assets/{index-B-mxrdIz.js → index-3qKpTOzz.js} +1 -1
  68. package/dashboard/dist/assets/{index-CjPsyTl7.js → index-Cqla2Zq5.js} +1 -1
  69. package/dashboard/dist/assets/{index-C0zFsOWh.js → index-Dc8PQ8ZR.js} +1 -1
  70. package/dashboard/dist/assets/{index-CzstoU6d.js → index-dCfimErX.js} +1 -1
  71. package/dashboard/dist/assets/{info-DmU-vlJ6.js → info-DcUyOPKG.js} +1 -1
  72. package/dashboard/dist/assets/{link-2-BlBM7uw7.js → link-2-CS-cWLmx.js} +1 -1
  73. package/dashboard/dist/assets/{loader-circle-Dhrb_Ocy.js → loader-circle-DKc-gwgL.js} +1 -1
  74. package/dashboard/dist/assets/{local-usage-transfer-BudmMtkd.js → local-usage-transfer-bHATORmq.js} +1 -1
  75. package/dashboard/dist/assets/{modifiers.esm-kmw1VM8W.js → modifiers.esm-B4QifJzx.js} +1 -1
  76. package/dashboard/dist/assets/{monitor-smartphone-DtjXNC_x.js → monitor-smartphone-D8vpN9Jv.js} +1 -1
  77. package/dashboard/dist/assets/{play-9w46lbjy.js → play-D70P7DVT.js} +1 -1
  78. package/dashboard/dist/assets/{plus-DAf_WGKT.js → plus-YomnSdqY.js} +1 -1
  79. package/dashboard/dist/assets/{provider-display-BZ0XlB2q.js → provider-display-VXu7Hzwq.js} +1 -1
  80. package/dashboard/dist/assets/{react-B01ZJuZY.js → react-CUO9Gs2G.js} +1 -1
  81. package/dashboard/dist/assets/{safePolygon-B6_x12Gb.js → safePolygon-CbVKM3Lv.js} +1 -1
  82. package/dashboard/dist/assets/{search-D3dW9GbV.js → search-BUi1hAmV.js} +1 -1
  83. package/dashboard/dist/assets/{server-C2XZBar7.js → server-B-agbJYI.js} +1 -1
  84. package/dashboard/dist/assets/{shield-check-4FX5vbS7.js → shield-check-yHx3fQDo.js} +1 -1
  85. package/dashboard/dist/assets/{skills-api-BvW_MUyg.js → skills-api-DojEO2dS.js} +1 -1
  86. package/dashboard/dist/assets/{sortable.esm-CAa_BJgh.js → sortable.esm-r4Lz4ugj.js} +1 -1
  87. package/dashboard/dist/assets/{store-2AX2MNkz.js → store-GzQbKyT-.js} +1 -1
  88. package/dashboard/dist/assets/{terminal-CON7n_ir.js → terminal-BLCZgZTm.js} +1 -1
  89. package/dashboard/dist/assets/{trash-C2f88jSi.js → trash-BFGtCBJS.js} +1 -1
  90. package/dashboard/dist/assets/{use-community-stats-CgQu43xV.js → use-community-stats-DGdLAa8L.js} +1 -1
  91. package/dashboard/dist/assets/{use-latest-request-guard-lShM46-3.js → use-latest-request-guard-l39bM4s0.js} +1 -1
  92. package/dashboard/dist/assets/{use-limits-display-prefs-8o7pbEg0.js → use-limits-display-prefs-JcWgMNA0.js} +1 -1
  93. package/dashboard/dist/assets/{use-native-settings-dN8shG3M.js → use-native-settings-0sXFw6mq.js} +1 -1
  94. package/dashboard/dist/assets/{use-ordered-list-beJy2IqQ.js → use-ordered-list-mRdyZ18r.js} +1 -1
  95. package/dashboard/dist/assets/use-reduced-motion-D2g3iGbi.js +1 -0
  96. package/dashboard/dist/assets/{use-transform-cJpAQr2d.js → use-transform-CbJqMa_Y.js} +1 -1
  97. package/dashboard/dist/assets/{use-usage-limits-CuusZA6b.js → use-usage-limits-qQKS4sHR.js} +1 -1
  98. package/dashboard/dist/assets/{useCurrency-Cx9UsKJK.js → useCurrency-BJyZCVtd.js} +1 -1
  99. package/dashboard/dist/assets/{usePositioner-TEClK6mF.js → usePositioner-zsiJqVGU.js} +1 -1
  100. package/dashboard/dist/assets/{useTokenFormat-BNwTLiac.js → useTokenFormat-DxEKSp5H.js} +1 -1
  101. package/dashboard/dist/assets/{zap-CFgbVEhC.js → zap-D40-8VPk.js} +1 -1
  102. package/dashboard/dist/index.html +1 -1
  103. package/dashboard/dist/ip-check.html +1 -1
  104. package/dashboard/dist/leaderboard.html +1 -1
  105. package/dashboard/dist/share.html +1 -1
  106. package/package.json +1 -1
  107. package/src/lib/ark-agent-plan-limits.js +26 -55
  108. package/src/lib/ark-coding-plan-limits.js +31 -62
  109. package/src/lib/cloud-account.js +29 -3
  110. package/src/lib/limits-cache.js +475 -0
  111. package/src/lib/local-api.js +92 -5
  112. package/src/lib/pricing/curated-overrides.json +32 -0
  113. package/src/lib/pricing/matcher.js +4 -1
  114. package/src/lib/pricing/seed-snapshot.json +1 -1
  115. package/src/lib/qoder-limits.js +35 -69
  116. package/src/lib/rollout.js +43 -47
  117. package/src/lib/usage-limits.js +370 -1063
  118. package/dashboard/dist/assets/DialogDescription-CO0hykis.js +0 -1
  119. package/dashboard/dist/assets/use-reduced-motion-S0bR_G1B.js +0 -1
@@ -26,6 +26,7 @@ const os = require("node:os");
26
26
  const path = require("node:path");
27
27
 
28
28
  const { runCommand, resolveBinaryPath, statBinaryInDirs, commonGlobalBinDirectories, resolvedCliEnvironment } = require("./command-runner");
29
+ const { createLimitsCache } = require("./limits-cache");
29
30
 
30
31
  const ARK_LIMITS_CACHE_FILE = "ark-agent-plan-limits-cache.json";
31
32
  const ARK_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
@@ -188,68 +189,38 @@ function normalizeArkAgentPlanResponse(body) {
188
189
  // Alias for shared shape
189
190
  const normalizeArkCodingPlanResponse = normalizeArkAgentPlanResponse;
190
191
 
192
+ // Deliberately fail-open on identity: the guard only applies when `profile
193
+ // show` could establish the current identity — when it also failed, serving
194
+ // the stale cache beats erroring out (availability over strictness). There is
195
+ // no global max-age: dated windows are bounded by their own reset stamp and
196
+ // undated ones by the 12h unknown-reset TTL.
197
+ const arkLimitsCache = createLimitsCache({
198
+ file: ARK_LIMITS_CACHE_FILE,
199
+ key: null,
200
+ maxAgeMs: null,
201
+ unknownResetTtlMs: ARK_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS,
202
+ fields: { plan_label: "string", product: "string", profile_identity: "string" },
203
+ windowKeys: ["primary_window", "secondary_window", "tertiary_window"],
204
+ identityField: "profile_identity",
205
+ identityRequired: false,
206
+ // The normalizer throws when no periods survive, so the write guard skips
207
+ // the window check the other providers need.
208
+ writeGuard: (record) => Boolean(record?.configured) && !record.error,
209
+ cachedFlag: true,
210
+ stale: true,
211
+ source: { const: "disk-cache" },
212
+ });
213
+
191
214
  function arkAgentPlanCachePath({ home = os.homedir() } = {}) {
192
- return path.join(home, ".tokentracker", "tracker", ARK_LIMITS_CACHE_FILE);
215
+ return arkLimitsCache.path({ home });
193
216
  }
194
217
 
195
218
  function readArkAgentPlanLimitsCache({ home = os.homedir(), nowMs = Date.now(), profileIdentity } = {}) {
196
- try {
197
- const parsed = JSON.parse(fs.readFileSync(arkAgentPlanCachePath({ home }), "utf8"));
198
- if (profileIdentity && parsed?.profile_identity !== profileIdentity) return null;
199
- const cachedAtMs = Date.parse(parsed?.cached_at || "");
200
- if (!Number.isFinite(cachedAtMs) || cachedAtMs > nowMs + 60_000) return null;
201
- const windows = [parsed?.primary_window, parsed?.secondary_window, parsed?.tertiary_window];
202
- const surviving = windows.map((window) => {
203
- if (!window) return null;
204
- const resetAtMs = Date.parse(window.reset_at || "");
205
- if (Number.isFinite(resetAtMs) && resetAtMs <= nowMs) return null;
206
- return window;
207
- });
208
- if (surviving.every((window) => !window)) return null;
209
- const bounded = surviving.map((window) => {
210
- if (!window) return null;
211
- return Number.isFinite(Date.parse(window.reset_at || ""))
212
- || nowMs - cachedAtMs <= ARK_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS
213
- ? window
214
- : null;
215
- });
216
- if (bounded.every((window) => !window)) return null;
217
- return {
218
- configured: true,
219
- error: null,
220
- plan_label: typeof parsed?.plan_label === "string" ? parsed.plan_label : null,
221
- product: typeof parsed?.product === "string" ? parsed.product : null,
222
- primary_window: bounded[0],
223
- secondary_window: bounded[1],
224
- tertiary_window: bounded[2],
225
- cached: true,
226
- stale: true,
227
- cached_at: parsed.cached_at,
228
- source: "disk-cache",
229
- };
230
- } catch (_error) {
231
- return null;
232
- }
219
+ return arkLimitsCache.read({ home, nowMs, identity: profileIdentity });
233
220
  }
234
221
 
235
222
  function writeArkAgentPlanLimitsCache(limits, { home = os.homedir(), nowMs = Date.now() } = {}) {
236
- if (!limits?.configured || limits.error) return;
237
- const cachePath = arkAgentPlanCachePath({ home });
238
- const payload = {
239
- plan_label: limits.plan_label || null,
240
- product: limits.product || null,
241
- profile_identity: limits.profile_identity || null,
242
- primary_window: limits.primary_window || null,
243
- secondary_window: limits.secondary_window || null,
244
- tertiary_window: limits.tertiary_window || null,
245
- cached_at: new Date(nowMs).toISOString(),
246
- };
247
- try {
248
- fs.mkdirSync(path.dirname(cachePath), { recursive: true });
249
- const tmpPath = `${cachePath}.${process.pid}.tmp`;
250
- fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
251
- fs.renameSync(tmpPath, cachePath);
252
- } catch (_error) {}
223
+ arkLimitsCache.write(limits, { home, nowMs });
253
224
  }
254
225
 
255
226
  function hasArkCliInstallEvidence({ home = os.homedir(), platform = process.platform } = {}) {
@@ -26,6 +26,7 @@ const os = require("node:os");
26
26
  const path = require("node:path");
27
27
 
28
28
  const { runCommand, resolveBinaryPath, statBinaryInDirs, commonGlobalBinDirectories, resolvedCliEnvironment } = require("./command-runner");
29
+ const { createLimitsCache } = require("./limits-cache");
29
30
 
30
31
  const ARK_LIMITS_CACHE_FILE = "ark-coding-plan-limits-cache.json";
31
32
  const ARK_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS = 12 * 60 * 60 * 1000;
@@ -212,75 +213,43 @@ function normalizeArkCodingPlanResponse(body) {
212
213
  };
213
214
  }
214
215
 
216
+ // Deliberately fail-open on identity: the guard only applies when `profile
217
+ // show` could establish the current identity — when it also failed, serving
218
+ // the stale cache beats erroring out (availability over strictness). There is
219
+ // no global max-age: dated windows are bounded by their own reset stamp and
220
+ // undated ones by the 12h unknown-reset TTL.
221
+
222
+ // A window whose reset_at has passed is stale — the quota has already rolled
223
+ // over, so serving its old used_percent would mislead. Undated windows can't be
224
+ // checked against a reset, so each one is dropped when the snapshot is too old;
225
+ // a future-dated sibling must not keep it alive.
226
+ const arkLimitsCache = createLimitsCache({
227
+ file: ARK_LIMITS_CACHE_FILE,
228
+ key: null,
229
+ maxAgeMs: null,
230
+ unknownResetTtlMs: ARK_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS,
231
+ fields: { plan_label: "string", product: "string", profile_identity: "string" },
232
+ windowKeys: ["primary_window", "secondary_window", "tertiary_window"],
233
+ identityField: "profile_identity",
234
+ identityRequired: false,
235
+ // The normalizer throws when no periods survive, so the write guard skips
236
+ // the window check the other providers need.
237
+ writeGuard: (record) => Boolean(record?.configured) && !record.error,
238
+ cachedFlag: true,
239
+ stale: true,
240
+ source: { const: "disk-cache" },
241
+ });
242
+
215
243
  function arkCodingPlanCachePath({ home = os.homedir() } = {}) {
216
- return path.join(home, ".tokentracker", "tracker", ARK_LIMITS_CACHE_FILE);
244
+ return arkLimitsCache.path({ home });
217
245
  }
218
246
 
219
247
  function readArkCodingPlanLimitsCache({ home = os.homedir(), nowMs = Date.now(), profileIdentity } = {}) {
220
- try {
221
- const parsed = JSON.parse(fs.readFileSync(arkCodingPlanCachePath({ home }), "utf8"));
222
- // Deliberately fail-open: the guard only applies when `profile show`
223
- // could establish the current identity. When it also failed, serving
224
- // the stale cache beats erroring out — availability over strictness.
225
- if (profileIdentity && parsed?.profile_identity !== profileIdentity) return null;
226
- const cachedAtMs = Date.parse(parsed?.cached_at || "");
227
- if (!Number.isFinite(cachedAtMs) || cachedAtMs > nowMs + 60_000) return null;
228
- // A window whose reset_at has passed is stale — the quota has already
229
- // rolled over, so serving its old used_percent would mislead. Drop it.
230
- const windows = [parsed?.primary_window, parsed?.secondary_window, parsed?.tertiary_window];
231
- const surviving = windows.map((window) => {
232
- if (!window) return null;
233
- const resetAtMs = Date.parse(window.reset_at || "");
234
- if (Number.isFinite(resetAtMs) && resetAtMs <= nowMs) return null;
235
- return window;
236
- });
237
- if (surviving.every((window) => !window)) return null;
238
- // Undated windows can't be checked against a reset, so drop each one when
239
- // the snapshot is too old. A future-dated sibling must not keep it alive.
240
- const bounded = surviving.map((window) => {
241
- if (!window) return null;
242
- return Number.isFinite(Date.parse(window.reset_at || ""))
243
- || nowMs - cachedAtMs <= ARK_LIMITS_CACHE_UNKNOWN_RESET_TTL_MS
244
- ? window
245
- : null;
246
- });
247
- if (bounded.every((window) => !window)) return null;
248
- return {
249
- configured: true,
250
- error: null,
251
- plan_label: typeof parsed?.plan_label === "string" ? parsed.plan_label : null,
252
- product: typeof parsed?.product === "string" ? parsed.product : null,
253
- primary_window: bounded[0],
254
- secondary_window: bounded[1],
255
- tertiary_window: bounded[2],
256
- cached: true,
257
- stale: true,
258
- cached_at: parsed.cached_at,
259
- source: "disk-cache",
260
- };
261
- } catch (_error) {
262
- return null;
263
- }
248
+ return arkLimitsCache.read({ home, nowMs, identity: profileIdentity });
264
249
  }
265
250
 
266
251
  function writeArkCodingPlanLimitsCache(limits, { home = os.homedir(), nowMs = Date.now() } = {}) {
267
- if (!limits?.configured || limits.error) return;
268
- const cachePath = arkCodingPlanCachePath({ home });
269
- const payload = {
270
- plan_label: limits.plan_label || null,
271
- product: limits.product || null,
272
- profile_identity: limits.profile_identity || null,
273
- primary_window: limits.primary_window || null,
274
- secondary_window: limits.secondary_window || null,
275
- tertiary_window: limits.tertiary_window || null,
276
- cached_at: new Date(nowMs).toISOString(),
277
- };
278
- try {
279
- fs.mkdirSync(path.dirname(cachePath), { recursive: true });
280
- const tmpPath = `${cachePath}.${process.pid}.tmp`;
281
- fs.writeFileSync(tmpPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
282
- fs.renameSync(tmpPath, cachePath);
283
- } catch (_error) {}
252
+ arkLimitsCache.write(limits, { home, nowMs });
284
253
  }
285
254
 
286
255
  // runCommand / whichBinary / resolveBinaryPath now live in ./command-runner,
@@ -216,6 +216,28 @@ function csrfTokenFromRefreshPayload(data) {
216
216
  return raw && raw.length > 0 ? raw : null;
217
217
  }
218
218
 
219
+ function userFromRefreshPayload(data) {
220
+ if (!data || typeof data !== "object") return null;
221
+ const raw = data.user && typeof data.user === "object" && !Array.isArray(data.user)
222
+ ? data.user
223
+ : data.session && typeof data.session === "object" && data.session.user && typeof data.session.user === "object" && !Array.isArray(data.session.user)
224
+ ? data.session.user
225
+ : null;
226
+ return raw;
227
+ }
228
+
229
+ function userFromAccessToken(accessToken) {
230
+ try {
231
+ const payload = JSON.parse(Buffer.from(String(accessToken).split(".")[1], "base64url").toString("utf8"));
232
+ if (typeof payload?.sub !== "string" || !payload.sub) return null;
233
+ const user = { id: payload.sub };
234
+ if (typeof payload.email === "string" && payload.email) user.email = payload.email;
235
+ return user;
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+
219
241
  /**
220
242
  * Perform the actual refresh POST. Throws a classified `AccountAuthError` on
221
243
  * every failure so callers can distinguish a transient outage (timeout,
@@ -283,7 +305,8 @@ async function performMint({ root, anonKey, refreshToken, fetchImpl, now, timeou
283
305
  const rotated = refreshTokenFromRefreshPayload(data);
284
306
  // The caller persists a rotation before its next account read. Key the
285
307
  // still-valid access token by that current refresh token, not the consumed one.
286
- tokenCache = { cacheKey: `${root}\0${rotated || refreshToken}`, accessToken, expMs };
308
+ const user = userFromRefreshPayload(data) || userFromAccessToken(accessToken);
309
+ tokenCache = { cacheKey: `${root}\0${rotated || refreshToken}`, accessToken, expMs, user };
287
310
  return {
288
311
  accessToken,
289
312
  refreshToken: rotated && rotated !== refreshToken ? rotated : null,
@@ -292,6 +315,9 @@ async function performMint({ root, anonKey, refreshToken, fetchImpl, now, timeou
292
315
  // relayed (refresh, csrf) pair in sync — a stale relayed csrf breaks the
293
316
  // dashboard's cookie-path refresh with 403 Invalid CSRF.
294
317
  csrfToken: csrfTokenFromRefreshPayload(data),
318
+ // The InsForge SDK's refreshSession persists the session only when the
319
+ // response includes a user object alongside the access token.
320
+ user,
295
321
  };
296
322
  }
297
323
 
@@ -303,7 +329,7 @@ async function performMint({ root, anonKey, refreshToken, fetchImpl, now, timeou
303
329
  *
304
330
  * @param {boolean} [throwOnFailure] when true, reject with a classified
305
331
  * `AccountAuthError` instead of resolving to `null`.
306
- * @returns {Promise<{accessToken: string, refreshToken: string|null, csrfToken: string|null}|null>}
332
+ * @returns {Promise<{accessToken: string, refreshToken: string|null, csrfToken: string|null, user: object|null}|null>}
307
333
  * null when no refresh token is available or the refresh failed.
308
334
  */
309
335
  async function mintAccessToken({
@@ -327,7 +353,7 @@ async function mintAccessToken({
327
353
  tokenCache.accessToken &&
328
354
  tokenCache.expMs - skewMs > now()
329
355
  ) {
330
- return { accessToken: tokenCache.accessToken, refreshToken: null, csrfToken: null };
356
+ return { accessToken: tokenCache.accessToken, refreshToken: null, csrfToken: null, user: tokenCache.user || null };
331
357
  }
332
358
 
333
359
  let inflight = mintInflight.get(cacheKey);