tokentracker-cli 0.96.0 → 0.96.2

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 (121) 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 +4 -2
  5. package/README.zh-CN.md +3 -3
  6. package/dashboard/dist/assets/{AchievementBadge-Bb2ljquW.js → AchievementBadge-CgEitoX1.js} +1 -1
  7. package/dashboard/dist/assets/{AchievementsPage-ByUP2x8a.js → AchievementsPage-dnnea0JH.js} +2 -2
  8. package/dashboard/dist/assets/{AchievementsSection-_L6sCLTw.js → AchievementsSection-CJqG0UP7.js} +1 -1
  9. package/dashboard/dist/assets/{BadgeDetailModal-B9n_-45B.js → BadgeDetailModal-BLV6SJoI.js} +1 -1
  10. package/dashboard/dist/assets/{BotAnimated-lWmg-aZg.js → BotAnimated-Cm78yEbz.js} +1 -1
  11. package/dashboard/dist/assets/{Card-aSp259T0.js → Card-IPGSYT6O.js} +1 -1
  12. package/dashboard/dist/assets/{ClawdAnimated-Cy-XaTXW.js → ClawdAnimated-04w1U1sm.js} +2 -2
  13. package/dashboard/dist/assets/{CommandPalette-CNwtKCUm.js → CommandPalette-gSSa1V_s.js} +1 -1
  14. package/dashboard/dist/assets/{CommunityStatsModal-DJSezaPV.js → CommunityStatsModal-CICwUz14.js} +1 -1
  15. package/dashboard/dist/assets/{ConfirmModal-CVjmhZd6.js → ConfirmModal-DaWC0cY7.js} +1 -1
  16. package/dashboard/dist/assets/{Controls-BIj6yVxE.js → Controls-BDihA03F.js} +1 -1
  17. package/dashboard/dist/assets/DashboardPage-hV7wncKF.js +1 -0
  18. package/dashboard/dist/assets/{DevicePage-ptsI2fZc.js → DevicePage-BPpo_eiY.js} +1 -1
  19. package/dashboard/dist/assets/{DialogClose-D1pLoZ56.js → DialogClose-C_dF-r3s.js} +1 -1
  20. package/dashboard/dist/assets/DialogDescription-GClOSCaE.js +1 -0
  21. package/dashboard/dist/assets/{DialogTitle-DfSBUjWf.js → DialogTitle-Buh_5688.js} +1 -1
  22. package/dashboard/dist/assets/{FadeIn-DBn4L_BV.js → FadeIn-DmI9PRLa.js} +1 -1
  23. package/dashboard/dist/assets/{HeaderGithubStar-EJLBwEqp.js → HeaderGithubStar-CBO4cY7i.js} +1 -1
  24. package/dashboard/dist/assets/{HoverTooltip-DaSrZhYd.js → HoverTooltip-CPFGhi9K.js} +1 -1
  25. package/dashboard/dist/assets/{IpCheckPage-diQTVaYt.js → IpCheckPage-Do7w_6z6.js} +1 -1
  26. package/dashboard/dist/assets/{LandingPage-BqCxjwar.js → LandingPage-at_Ti1ZE.js} +2 -2
  27. package/dashboard/dist/assets/{LeaderboardAvatar-CQImCsPN.js → LeaderboardAvatar-BH1cnKPp.js} +1 -1
  28. package/dashboard/dist/assets/{LeaderboardPage-D2ZDUi_O.js → LeaderboardPage-Cd30aXwL.js} +2 -2
  29. package/dashboard/dist/assets/{LeaderboardProfileModal-DHvADX1d.js → LeaderboardProfileModal-CVsti_HN.js} +3 -3
  30. package/dashboard/dist/assets/{LeaderboardProfilePage-CJsedB28.js → LeaderboardProfilePage-BfSpDM9e.js} +1 -1
  31. package/dashboard/dist/assets/LimitsPage-_-BadDAj.js +8 -0
  32. package/dashboard/dist/assets/{LocalOnlyNotice-Cmnvu9tI.js → LocalOnlyNotice-9-vRgsdn.js} +1 -1
  33. package/dashboard/dist/assets/{LoginCard-BQmKkD7X.js → LoginCard-DM_ZdFNl.js} +1 -1
  34. package/dashboard/dist/assets/{LoginModal-2oPROePZ.js → LoginModal-BaNMznwh.js} +1 -1
  35. package/dashboard/dist/assets/{LoginPage-aqdUJoQK.js → LoginPage-BMMthUf4.js} +1 -1
  36. package/dashboard/dist/assets/{PetPage-Bn3p_o5r.js → PetPage-Iq8Hswai.js} +1 -1
  37. package/dashboard/dist/assets/{PopoverPopup-DXD_YdiB.js → PopoverPopup-DXr3JxVV.js} +1 -1
  38. package/dashboard/dist/assets/{ProviderIcon-BZZ45-gY.js → ProviderIcon-sb2keSY2.js} +1 -1
  39. package/dashboard/dist/assets/{ResetPasswordPage-Bn5ITiHQ.js → ResetPasswordPage-Ca4SULSV.js} +1 -1
  40. package/dashboard/dist/assets/{SegmentedControl-hGXboQZy.js → SegmentedControl-Bbsn-qvZ.js} +1 -1
  41. package/dashboard/dist/assets/{Select-BP8aVRZ1.js → Select-vD-yj-qa.js} +1 -1
  42. package/dashboard/dist/assets/{SelectItemText-Odx47kND.js → SelectItemText-CRtFEuvg.js} +1 -1
  43. package/dashboard/dist/assets/{ServiceStatusPage-DUueam0N.js → ServiceStatusPage-BPS8KJ3a.js} +1 -1
  44. package/dashboard/dist/assets/{SessionsPage-BA9IFqQ1.js → SessionsPage-C1yt3TMZ.js} +1 -1
  45. package/dashboard/dist/assets/SettingsPage-CWVHrMer.js +2 -0
  46. package/dashboard/dist/assets/{SkillsPage-Ds2vH_PG.js → SkillsPage-DGWEH1xm.js} +1 -1
  47. package/dashboard/dist/assets/{TokenGalaxy-CWBR4jnB.js → TokenGalaxy-DKvbJbnv.js} +1 -1
  48. package/dashboard/dist/assets/{ToolbarRootContext-Bmg56vYz.js → ToolbarRootContext-D8yov1dW.js} +1 -1
  49. package/dashboard/dist/assets/{TrendMonitor-BQzcllvg.js → TrendMonitor-YF5C_-L1.js} +1 -1
  50. package/dashboard/dist/assets/{WidgetsPage-Bu3tHDjO.js → WidgetsPage-BKV3DgXg.js} +1 -1
  51. package/dashboard/dist/assets/{WrappedPage-BrAMJhgy.js → WrappedPage-s4IRoLf7.js} +1 -1
  52. package/dashboard/dist/assets/__vite-browser-external-CvMMIjhi.css +1 -0
  53. package/dashboard/dist/assets/__vite-browser-external-_LUatzwU.js +45 -0
  54. package/dashboard/dist/assets/{agent-logos-B8lT-HcI.js → agent-logos-Hbt-wyl2.js} +1 -1
  55. package/dashboard/dist/assets/{arrow-up-right-rpBCOTg2.js → arrow-up-right-KeIpKlw3.js} +1 -1
  56. package/dashboard/dist/assets/{check-DIKw7TYH.js → check-_QKyY1v_.js} +1 -1
  57. package/dashboard/dist/assets/{composite-_NeXO0lv.js → composite-BidfcMeb.js} +1 -1
  58. package/dashboard/dist/assets/{copy-CsfAtL7P.js → copy-DpLM3QMD.js} +1 -1
  59. package/dashboard/dist/assets/{download-DM1W9pIW.js → download-C4NX-JnW.js} +1 -1
  60. package/dashboard/dist/assets/{external-link-CwbuMbeQ.js → external-link-CG-7FcOF.js} +1 -1
  61. package/dashboard/dist/assets/{flame-DcYbLdhE.js → flame-CkWid7Ji.js} +1 -1
  62. package/dashboard/dist/assets/{format-tokens-CEvr3dNy.js → format-tokens-CbwWhioZ.js} +1 -1
  63. package/dashboard/dist/assets/{icons-DaGMVsJ5.js → icons-BZq17rU3.js} +1 -1
  64. package/dashboard/dist/assets/{index-ChZkJjpk.js → index-CR9tDiiC.js} +1 -1
  65. package/dashboard/dist/assets/{index-BVVLPj3N.js → index-CSwEiUn4.js} +1 -1
  66. package/dashboard/dist/assets/{index-T7OjmEuX.js → index-DDGLE0dz.js} +1 -1
  67. package/dashboard/dist/assets/{info-CAjN3dX4.js → info-mkeRjzqc.js} +1 -1
  68. package/dashboard/dist/assets/limits-providers-DRkcWQup.js +1 -0
  69. package/dashboard/dist/assets/{link-2-DaWH2Tyc.js → link-2-BjMw8csC.js} +1 -1
  70. package/dashboard/dist/assets/{loader-circle-CnF8qPi1.js → loader-circle-cGWOJnWn.js} +1 -1
  71. package/dashboard/dist/assets/{maximize-2-B-2Pnj6g.js → maximize-2-Cg_S73tD.js} +1 -1
  72. package/dashboard/dist/assets/{modifiers.esm-BoDOvUKl.js → modifiers.esm-DNxTWxId.js} +1 -1
  73. package/dashboard/dist/assets/{monitor-smartphone-DDgwxG_E.js → monitor-smartphone-BvE9Tt2U.js} +1 -1
  74. package/dashboard/dist/assets/{plus-DIc1YUWB.js → plus-BC2wdPRr.js} +1 -1
  75. package/dashboard/dist/assets/{provider-display-yeNsVCO_.js → provider-display-B7GggHQy.js} +1 -1
  76. package/dashboard/dist/assets/{react-DZqS6YTX.js → react-D0kcslTz.js} +1 -1
  77. package/dashboard/dist/assets/{search-D6tEppYJ.js → search-BuBNF3E3.js} +1 -1
  78. package/dashboard/dist/assets/{skills-api-vsA1rc1Z.js → skills-api-DxAN5pNx.js} +1 -1
  79. package/dashboard/dist/assets/{sortable.esm-DDCnS1HI.js → sortable.esm-DoqMcEX-.js} +1 -1
  80. package/dashboard/dist/assets/{store-Crne4DqF.js → store-Cg1HeUFM.js} +1 -1
  81. package/dashboard/dist/assets/{terminal-Ca7dkLMF.js → terminal-COWFUJdU.js} +1 -1
  82. package/dashboard/dist/assets/{trash-2-CjEAS5ic.js → trash-2-Cqjki3RI.js} +1 -1
  83. package/dashboard/dist/assets/{use-community-stats-BHx2YxyK.js → use-community-stats-DsrlOPDM.js} +1 -1
  84. package/dashboard/dist/assets/{use-latest-request-guard-BLGbXQdf.js → use-latest-request-guard-ryRFL2gx.js} +1 -1
  85. package/dashboard/dist/assets/{use-limits-display-prefs-DBkAp6ob.js → use-limits-display-prefs-DkSuu12m.js} +1 -1
  86. package/dashboard/dist/assets/{use-ordered-list-BTfW5M9u.js → use-ordered-list-D-Q86R1b.js} +1 -1
  87. package/dashboard/dist/assets/use-reduced-motion-D7NNqavo.js +1 -0
  88. package/dashboard/dist/assets/{use-session-efficiency-pref-DBmd-MqH.js → use-session-efficiency-pref-CrNz45y7.js} +1 -1
  89. package/dashboard/dist/assets/{use-transform-Bp5XrwaQ.js → use-transform-CoGM21px.js} +1 -1
  90. package/dashboard/dist/assets/{use-usage-limits-BmqRcyhH.js → use-usage-limits-C_6L1sO5.js} +1 -1
  91. package/dashboard/dist/assets/{useCurrency-B0YsRDj-.js → useCurrency-CgCfDktb.js} +1 -1
  92. package/dashboard/dist/assets/{useTokenFormat-PP3D0Ket.js → useTokenFormat-QKtzCKTL.js} +1 -1
  93. package/dashboard/dist/assets/{zap-BVOm1aBH.js → zap-CTIobzgU.js} +1 -1
  94. package/dashboard/dist/brand-logos/commandcode.svg +1 -0
  95. package/dashboard/dist/index.html +5 -5
  96. package/dashboard/dist/ip-check.html +2 -2
  97. package/dashboard/dist/leaderboard.html +2 -2
  98. package/dashboard/dist/share.html +2 -2
  99. package/package.json +1 -1
  100. package/src/commands/status.js +4 -4
  101. package/src/commands/sync.js +5 -3
  102. package/src/lib/ark-agent-plan-limits.js +4 -3
  103. package/src/lib/ark-coding-plan-limits.js +6 -8
  104. package/src/lib/codex-rollout-parser.js +2 -2
  105. package/src/lib/command-runner.js +13 -0
  106. package/src/lib/commandcode-limits.js +317 -0
  107. package/src/lib/local-api.js +3 -0
  108. package/src/lib/pricing/curated-overrides.json +11 -0
  109. package/src/lib/pricing/index.js +6 -7
  110. package/src/lib/pricing/seed-snapshot.json +1 -1
  111. package/src/lib/rollout.js +183 -11
  112. package/src/lib/session-analytics.js +2 -2
  113. package/src/lib/usage-limits.js +153 -14
  114. package/dashboard/dist/assets/DashboardPage-BjYZLmqQ.js +0 -1
  115. package/dashboard/dist/assets/DialogDescription-KY7X94CG.js +0 -1
  116. package/dashboard/dist/assets/LimitsPage-CAwff24b.js +0 -6
  117. package/dashboard/dist/assets/SettingsPage-DLOHHB4f.js +0 -2
  118. package/dashboard/dist/assets/__vite-browser-external-4IGm4Djn.js +0 -45
  119. package/dashboard/dist/assets/__vite-browser-external-CCMdjCX2.css +0 -1
  120. package/dashboard/dist/assets/limits-providers-jn7Yln_V.js +0 -1
  121. package/dashboard/dist/assets/use-reduced-motion-D_kUnDs4.js +0 -1
@@ -4458,6 +4458,9 @@ function claudeMessageDedupKey(obj) {
4458
4458
  function normalizeClaudeUsage(u) {
4459
4459
  const inputTokens = toNonNegativeInt(u?.input_tokens);
4460
4460
  const outputTokens = toNonNegativeInt(u?.output_tokens);
4461
+ const reasoningTokens = Math.min(outputTokens, toNonNegativeInt(
4462
+ u?.output_tokens_details?.thinking_tokens ?? u?.output_tokens_details?.reasoning_tokens,
4463
+ ));
4461
4464
  const cacheCreation = toNonNegativeInt(u?.cache_creation_input_tokens);
4462
4465
  const cacheRead = toNonNegativeInt(u?.cache_read_input_tokens);
4463
4466
  const totalTokens = inputTokens + outputTokens + cacheCreation + cacheRead;
@@ -4465,8 +4468,9 @@ function normalizeClaudeUsage(u) {
4465
4468
  input_tokens: inputTokens,
4466
4469
  cached_input_tokens: cacheRead,
4467
4470
  cache_creation_input_tokens: cacheCreation,
4468
- output_tokens: outputTokens,
4469
- reasoning_output_tokens: 0,
4471
+ // Claude rows price reasoning separately; it is already included in usage.output_tokens.
4472
+ output_tokens: outputTokens - reasoningTokens,
4473
+ reasoning_output_tokens: reasoningTokens,
4470
4474
  total_tokens: totalTokens,
4471
4475
  };
4472
4476
  }
@@ -5841,17 +5845,21 @@ function resolveQoderCnProjectsDir({ home = os.homedir(), env = process.env, pla
5841
5845
  ? path.resolve(env.QODER_CN_PROJECTS_DIR.trim())
5842
5846
  : null;
5843
5847
  if (override) return override;
5844
- // CN and international currently share ~/.qoder; keep a distinct override for
5845
- // future split without double-counting by default.
5848
+ // The new CN app (com.qodercn.app.stable, 2026-08+) keeps its sessions in
5849
+ // ~/.qoder-cn/projects a sibling of the international ~/.qoder, not a
5850
+ // shared directory. Pointing CN at ~/.qoder/projects made the "CN dir
5851
+ // diverges from international" guards in sync.js/status.js always false,
5852
+ // so new-version CN JSONL usage was silently never parsed (and on
5853
+ // international-only installs would have double-counted under qoder-cn).
5846
5854
  if (platform === "win32" && !env.QODER_CN_PROJECTS_DIR) {
5847
5855
  const discoverWslHome = deps.discoverWslHome || wsl.discoverWslHome;
5848
- const wslRoot = wsl.shouldProbeWsl(env) ? discoverWslHome(".qoder", { ...deps, env }) : null;
5856
+ const wslRoot = wsl.shouldProbeWsl(env) ? discoverWslHome(".qoder-cn", { ...deps, env }) : null;
5849
5857
  if (wslRoot) {
5850
5858
  const wslProjects = path.join(wslRoot, "projects");
5851
5859
  if ((deps.existsSync || fssync.existsSync)(wslProjects)) return wslProjects;
5852
5860
  }
5853
5861
  }
5854
- return path.join(home, ".qoder", "projects");
5862
+ return path.join(home, ".qoder-cn", "projects");
5855
5863
  }
5856
5864
 
5857
5865
  async function listQoderNewSessionFiles(projectsDir) {
@@ -5876,7 +5884,15 @@ async function listQoderNewSessionFiles(projectsDir) {
5876
5884
  function qoderNewModelFromRecord(record) {
5877
5885
  const msgModel = record?.message?.model;
5878
5886
  const direct = typeof msgModel === "string" ? msgModel.trim() : "";
5879
- return normalizeModelInput(direct) || "qoder-agent";
5887
+ // CN BYOK routes embed an install-local provider UUID in the model id
5888
+ // ("qoder-custom-<uuid>/glm-5.3-flash"). Keep the bare model id so bucket
5889
+ // keys stay stable across reinstalls and don't fragment per user; official
5890
+ // ids (e.g. "qmodel_38max") have no prefix and pass through unchanged.
5891
+ const stripped = direct.replace(
5892
+ /^qoder-custom-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\//i,
5893
+ "",
5894
+ );
5895
+ return normalizeModelInput(stripped) || "qoder-agent";
5880
5896
  }
5881
5897
 
5882
5898
  function qoderNewMessageKey(record, filePath, lineIndex = 0) {
@@ -18654,6 +18670,9 @@ async function parseAntigravityIncremental({
18654
18670
  const initialContextTokens = sameFile ? Number(prev.contextTokens || 0) : 0;
18655
18671
  const initialPrevContext = sameFile ? Number(prev.previousContextTokens || 0) : 0;
18656
18672
  const initialModel = sameFile && typeof prev.currentModel === "string" ? prev.currentModel : null;
18673
+ const initialLastPlannerModel =
18674
+ sameFile && typeof prev.lastPlannerModel === "string" ? prev.lastPlannerModel : null;
18675
+ const initialUsageSource = sameFile && typeof prev.usageSource === "string" ? prev.usageSource : null;
18657
18676
 
18658
18677
  const projectContext = projectEnabled
18659
18678
  ? await resolveProjectContextForFile({
@@ -18673,6 +18692,8 @@ async function parseAntigravityIncremental({
18673
18692
  initialContextTokens,
18674
18693
  initialPrevContext,
18675
18694
  initialModel,
18695
+ initialLastPlannerModel,
18696
+ initialUsageSource,
18676
18697
  hourlyState,
18677
18698
  touchedBuckets,
18678
18699
  source: fileSource,
@@ -18690,6 +18711,8 @@ async function parseAntigravityIncremental({
18690
18711
  contextTokens: result.contextTokens,
18691
18712
  previousContextTokens: result.previousContextTokens,
18692
18713
  currentModel: result.currentModel,
18714
+ lastPlannerModel: result.lastPlannerModel,
18715
+ usageSource: result.usageSource,
18693
18716
  updatedAt: new Date().toISOString(),
18694
18717
  };
18695
18718
 
@@ -18722,12 +18745,121 @@ async function parseAntigravityIncremental({
18722
18745
  return { filesProcessed, eventsAggregated, bucketsQueued, projectBucketsQueued };
18723
18746
  }
18724
18747
 
18748
+ function decodeAntigravityVarint(buf, offset) {
18749
+ let res = 0;
18750
+ let shift = 0;
18751
+ while (offset < buf.length) {
18752
+ const b = buf[offset++];
18753
+ res += (b & 0x7f) * 2 ** shift;
18754
+ if (!(b & 0x80)) break;
18755
+ shift += 7;
18756
+ }
18757
+ return [res, offset];
18758
+ }
18759
+
18760
+ function findAntigravityProtoFields(buf) {
18761
+ const fields = [];
18762
+ let offset = 0;
18763
+ while (offset < buf.length) {
18764
+ const [tag, next] = decodeAntigravityVarint(buf, offset);
18765
+ offset = next;
18766
+ const fieldNum = tag >> 3;
18767
+ const wireType = tag & 7;
18768
+ if (wireType === 0) {
18769
+ const [val, vNext] = decodeAntigravityVarint(buf, offset);
18770
+ offset = vNext;
18771
+ fields.push({ num: fieldNum, val });
18772
+ } else if (wireType === 2) {
18773
+ const [len, lNext] = decodeAntigravityVarint(buf, offset);
18774
+ offset = lNext;
18775
+ fields.push({ num: fieldNum, val: buf.subarray(offset, offset + len) });
18776
+ offset += len;
18777
+ } else if (wireType === 1) {
18778
+ offset += 8;
18779
+ } else if (wireType === 5) {
18780
+ offset += 4;
18781
+ } else {
18782
+ break;
18783
+ }
18784
+ }
18785
+ return fields;
18786
+ }
18787
+
18788
+ function extractAntigravityGenInfo(buf) {
18789
+ const root = findAntigravityProtoFields(buf);
18790
+ const f1 = root.find((f) => f.num === 1)?.val;
18791
+ if (!f1) return null;
18792
+
18793
+ const inner = findAntigravityProtoFields(f1);
18794
+ let model = null;
18795
+ const f19 = inner.find((f) => f.num === 19)?.val;
18796
+ if (f19) model = Buffer.from(f19).toString("utf8").trim();
18797
+
18798
+ let contextTokens = 0;
18799
+ const f9 = inner.find((f) => f.num === 9)?.val;
18800
+ if (f9) {
18801
+ const f10 = findAntigravityProtoFields(f9).find((f) => f.num === 10)?.val;
18802
+ if (f10) {
18803
+ const tok = findAntigravityProtoFields(f10).find((f) => f.num === 1)?.val;
18804
+ if (Number.isFinite(tok)) contextTokens = tok;
18805
+ }
18806
+ }
18807
+
18808
+ let lastStepIndex = null;
18809
+ for (const f of inner) {
18810
+ if (f.num !== 20 || !f.val) continue;
18811
+ const kv = findAntigravityProtoFields(f.val);
18812
+ const k = kv.find((x) => x.num === 1)?.val;
18813
+ const v = kv.find((x) => x.num === 2)?.val;
18814
+ if (k && Buffer.from(k).toString("utf8") === "last_step_index" && v) {
18815
+ const parsed = parseInt(Buffer.from(v).toString("utf8"), 10);
18816
+ if (Number.isFinite(parsed)) lastStepIndex = parsed;
18817
+ }
18818
+ }
18819
+
18820
+ return { model, contextTokens, lastStepIndex };
18821
+ }
18822
+
18823
+ function resolveAntigravityDbPath(transcriptPath) {
18824
+ if (!transcriptPath || typeof transcriptPath !== "string") return null;
18825
+ const m = transcriptPath.match(/^(.*)[/\\]brain[/\\]([^/\\]+)[/\\]\.system_generated[/\\]logs[/\\]transcript.*\.jsonl$/);
18826
+ if (!m) return null;
18827
+ return path.join(m[1], "conversations", `${m[2]}.db`);
18828
+ }
18829
+
18830
+ function readAntigravityConversationDb(dbPath) {
18831
+ if (!dbPath) return null;
18832
+ try {
18833
+ const rows = readSqliteJsonRows(
18834
+ dbPath,
18835
+ "SELECT idx, quote(data) as hex FROM gen_metadata ORDER BY idx",
18836
+ { readOnly: true },
18837
+ );
18838
+ if (!Array.isArray(rows) || rows.length === 0) return null;
18839
+
18840
+ const stepMap = new Map();
18841
+ for (const r of rows) {
18842
+ if (!r || typeof r.hex !== "string" || !r.hex.startsWith("X'")) continue;
18843
+ const buf = Buffer.from(r.hex.slice(2, -1), "hex");
18844
+ const info = extractAntigravityGenInfo(buf);
18845
+ if (info && info.lastStepIndex != null && info.contextTokens > 0) {
18846
+ stepMap.set(info.lastStepIndex + 1, info);
18847
+ }
18848
+ }
18849
+ return stepMap.size > 0 ? stepMap : null;
18850
+ } catch (_) {
18851
+ return null;
18852
+ }
18853
+ }
18854
+
18725
18855
  async function parseAntigravityFile({
18726
18856
  filePath,
18727
18857
  lastLine,
18728
18858
  initialContextTokens,
18729
18859
  initialPrevContext,
18730
18860
  initialModel,
18861
+ initialLastPlannerModel,
18862
+ initialUsageSource,
18731
18863
  hourlyState,
18732
18864
  touchedBuckets,
18733
18865
  source,
@@ -18744,6 +18876,8 @@ async function parseAntigravityFile({
18744
18876
  contextTokens: 0,
18745
18877
  previousContextTokens: 0,
18746
18878
  currentModel: null,
18879
+ lastPlannerModel: null,
18880
+ usageSource: "estimated",
18747
18881
  };
18748
18882
  }
18749
18883
 
@@ -18752,6 +18886,8 @@ async function parseAntigravityFile({
18752
18886
  .map((line) => line.trim())
18753
18887
  .filter(Boolean);
18754
18888
  let eventsAggregated = 0;
18889
+ const dbPath = resolveAntigravityDbPath(filePath);
18890
+ const stepMap = dbPath ? readAntigravityConversationDb(dbPath) : null;
18755
18891
  // Resume cached context-token total + model so historical lines (i < lastLine)
18756
18892
  // don't need to be re-tokenized on every sync. Falls back to a full re-walk
18757
18893
  // when the cached state is missing (legacy cursor) or the file rotated.
@@ -18760,7 +18896,9 @@ async function parseAntigravityFile({
18760
18896
  const cachedTokens = Number.isFinite(initialContextTokens) ? initialContextTokens : 0;
18761
18897
  const cachedPrev = Number.isFinite(initialPrevContext) ? initialPrevContext : 0;
18762
18898
  const cachedModel = typeof initialModel === "string" ? initialModel : null;
18763
- const resumed = canResume && (cachedTokens > 0 || cachedModel !== null);
18899
+ const sqliteCursor = initialUsageSource === "sqlite";
18900
+ const resumed =
18901
+ canResume && (cachedTokens > 0 || cachedModel !== null) && (!stepMap || sqliteCursor);
18764
18902
  const scanStart = resumed ? lastLine : 0;
18765
18903
  let currentModel = resumed ? cachedModel : null;
18766
18904
  if (!currentModel) {
@@ -18771,6 +18909,11 @@ async function parseAntigravityFile({
18771
18909
  // tokens accumulated AFTER that point count as new input on the next planner
18772
18910
  // call — prevents O(N²) double-counting of the full history every turn.
18773
18911
  let previousContextTokens = resumed ? cachedPrev : 0;
18912
+ let lastPlannerModel = null;
18913
+ if (resumed) {
18914
+ lastPlannerModel =
18915
+ typeof initialLastPlannerModel === "string" ? initialLastPlannerModel : cachedModel;
18916
+ }
18774
18917
  let lastCompletedLine = Math.min(Number.isFinite(lastLine) ? lastLine : 0, lines.length);
18775
18918
 
18776
18919
  for (let i = scanStart; i < lines.length; i++) {
@@ -18793,9 +18936,27 @@ async function parseAntigravityFile({
18793
18936
  }
18794
18937
 
18795
18938
  const eventContextTokens = antigravityContextTokens(parsed);
18939
+ const dbTurn =
18940
+ parsed.type === "PLANNER_RESPONSE" && stepMap && Number.isFinite(parsed.step_index)
18941
+ ? stepMap.get(parsed.step_index)
18942
+ : null;
18943
+ const dbContextTokens = dbTurn && dbTurn.contextTokens > 0 ? dbTurn.contextTokens : 0;
18944
+ if (dbTurn && dbTurn.model) {
18945
+ const norm = normalizeAntigravityTranscriptModel(dbTurn.model);
18946
+ if (norm) currentModel = norm;
18947
+ }
18796
18948
 
18797
18949
  if (!isNewEvent) {
18798
- contextTokens += eventContextTokens;
18950
+ if (parsed.type === "PLANNER_RESPONSE") {
18951
+ if (dbContextTokens > 0) {
18952
+ contextTokens = dbContextTokens;
18953
+ }
18954
+ previousContextTokens = contextTokens;
18955
+ lastPlannerModel = currentModel;
18956
+ contextTokens += eventContextTokens;
18957
+ } else {
18958
+ contextTokens += eventContextTokens;
18959
+ }
18799
18960
  lastCompletedLine = i + 1;
18800
18961
  continue;
18801
18962
  }
@@ -18822,7 +18983,14 @@ async function parseAntigravityFile({
18822
18983
  const content = typeof parsed.content === "string" ? parsed.content : "";
18823
18984
  const thinking = typeof parsed.thinking === "string" ? parsed.thinking : "";
18824
18985
 
18986
+ if (dbContextTokens > 0) {
18987
+ contextTokens = dbContextTokens;
18988
+ }
18989
+ if (lastPlannerModel && model !== lastPlannerModel) {
18990
+ previousContextTokens = 0;
18991
+ }
18825
18992
  const inputDelta = Math.max(0, contextTokens - previousContextTokens);
18993
+
18826
18994
  const outputTokens =
18827
18995
  antigravityValueTokens(content) + antigravityValueTokens(parsed.tool_calls);
18828
18996
  const reasoningTokens = antigravityValueTokens(thinking);
@@ -18830,8 +18998,6 @@ async function parseAntigravityFile({
18830
18998
  delta.input_tokens = inputDelta;
18831
18999
  delta.output_tokens = outputTokens;
18832
19000
  delta.reasoning_output_tokens = reasoningTokens;
18833
- // Match the mainstream convention (Codebuddy / Kilocode / OMP / Hermes):
18834
- // total_tokens = sum of every token column. No cache columns here.
18835
19001
  delta.total_tokens = inputDelta + outputTokens + reasoningTokens;
18836
19002
  delta.billable_total_tokens = delta.total_tokens;
18837
19003
  delta.conversation_count = 1;
@@ -18865,6 +19031,7 @@ async function parseAntigravityFile({
18865
19031
  // so they MUST be billed as input on the next planner — don't fold them into
18866
19032
  // previousContextTokens or that history vanishes from the totals.
18867
19033
  previousContextTokens = contextTokens;
19034
+ lastPlannerModel = model;
18868
19035
  contextTokens += eventContextTokens;
18869
19036
  lastCompletedLine = i + 1;
18870
19037
  }
@@ -18875,6 +19042,8 @@ async function parseAntigravityFile({
18875
19042
  contextTokens,
18876
19043
  previousContextTokens,
18877
19044
  currentModel,
19045
+ lastPlannerModel,
19046
+ usageSource: stepMap ? "sqlite" : "estimated",
18878
19047
  };
18879
19048
  }
18880
19049
 
@@ -20457,6 +20626,9 @@ module.exports = {
20457
20626
  parseAntigravityIncremental,
20458
20627
  estimateAntigravityTokens,
20459
20628
  isCjkCodePoint,
20629
+ resolveAntigravityDbPath,
20630
+ extractAntigravityGenInfo,
20631
+ readAntigravityConversationDb,
20460
20632
 
20461
20633
  // Trae SOLO (ByteDance AI IDE)
20462
20634
  resolveTraePath,
@@ -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) {
@@ -30,6 +30,7 @@ const {
30
30
  const { fetchGrokLimits } = require("./grok-limits");
31
31
  const { fetchZcodeLimits } = require("./zcode-limits");
32
32
  const { fetchOpencodeGoLimits } = require("./opencode-go-limits");
33
+ const { fetchCommandcodeLimits } = require("./commandcode-limits");
33
34
  const { fetchQoderLimits, fetchQoderCnLimits } = require("./qoder-limits");
34
35
  const { fetchArkCodingPlanLimits } = require("./ark-coding-plan-limits");
35
36
  const { fetchArkAgentPlanLimits } = require("./ark-agent-plan-limits");
@@ -2167,10 +2168,19 @@ async function detectAntigravityProcess({
2167
2168
  );
2168
2169
  processes = parseWindowsProcesses(result?.stdout);
2169
2170
  } else {
2170
- const result = await runCommand(commandRunner, "/bin/ps", ["-ax", "-o", "pid=,command="], {
2171
+ let result = await runCommand(commandRunner, "/bin/ps", ["-ax", "-o", "pid=,command="], {
2171
2172
  timeout: timeoutMs,
2172
2173
  signal,
2173
2174
  });
2175
+ const isSpawnFailure = result?.error
2176
+ && result.error.code !== "ETIMEDOUT"
2177
+ && result.error.name !== "AbortError";
2178
+ if (isSpawnFailure && !result?.stdout) {
2179
+ result = await runCommand(commandRunner, "ps", ["-ax", "-o", "pid=,command="], {
2180
+ timeout: timeoutMs,
2181
+ signal,
2182
+ });
2183
+ }
2174
2184
  processes = String(result?.stdout || "")
2175
2185
  .split("\n")
2176
2186
  .map(parseProcessLine)
@@ -2633,10 +2643,70 @@ function parseWindowsListeningPorts(output, pid) {
2633
2643
  return Array.from(ports).sort((a, b) => a - b);
2634
2644
  }
2635
2645
 
2646
+ function parseLinuxProcListeningPorts(pid, { procRoot = "/proc" } = {}) {
2647
+ const numPid = Number(pid);
2648
+ if (!Number.isFinite(numPid) || numPid <= 0) return [];
2649
+ const inodes = new Set();
2650
+ try {
2651
+ const fds = fs.readdirSync(path.join(procRoot, String(numPid), "fd"));
2652
+ for (const fd of fds) {
2653
+ try {
2654
+ const link = fs.readlinkSync(path.join(procRoot, String(numPid), "fd", fd));
2655
+ const m = link.match(/^socket:\[(\d+)\]$/);
2656
+ if (m) inodes.add(m[1]);
2657
+ } catch {}
2658
+ }
2659
+ } catch {
2660
+ return [];
2661
+ }
2662
+ if (inodes.size === 0) return [];
2663
+
2664
+ const ports = new Set();
2665
+ for (const table of [path.join(procRoot, "net", "tcp"), path.join(procRoot, "net", "tcp6")]) {
2666
+ try {
2667
+ const content = fs.readFileSync(table, "utf8");
2668
+ for (const line of content.split("\n")) {
2669
+ const parts = line.trim().split(/\s+/);
2670
+ if (parts.length > 9 && parts[3] === "0A") {
2671
+ const inode = parts[9];
2672
+ if (inodes.has(inode)) {
2673
+ const portHex = parts[1]?.split(":")[1];
2674
+ if (portHex) {
2675
+ const port = parseInt(portHex, 16);
2676
+ if (Number.isInteger(port) && port > 0 && port <= 65535) {
2677
+ ports.add(port);
2678
+ }
2679
+ }
2680
+ }
2681
+ }
2682
+ }
2683
+ } catch {}
2684
+ }
2685
+ return Array.from(ports).sort((a, b) => a - b);
2686
+ }
2687
+
2688
+ function parseSsListeningPorts(output, pid) {
2689
+ const ports = new Set();
2690
+ const pidPattern = pid ? new RegExp(`\\bpid=${pid}\\b`) : null;
2691
+ for (const line of String(output || "").split("\n")) {
2692
+ if (!line.includes("LISTEN")) continue;
2693
+ if (pidPattern && !pidPattern.test(line)) continue;
2694
+ const match = line.match(/:(\d+)\s+/);
2695
+ if (match) {
2696
+ const port = Number(match[1]);
2697
+ if (Number.isInteger(port) && port > 0 && port <= 65535) {
2698
+ ports.add(port);
2699
+ }
2700
+ }
2701
+ }
2702
+ return Array.from(ports).sort((a, b) => a - b);
2703
+ }
2704
+
2636
2705
  async function listAntigravityPorts(pid, {
2637
2706
  commandRunner,
2638
2707
  platform = process.platform,
2639
2708
  timeoutMs = ANTIGRAVITY_PROCESS_SCAN_TIMEOUT_MS,
2709
+ procRoot = "/proc",
2640
2710
  signal,
2641
2711
  } = {}) {
2642
2712
  if (platform === "win32") {
@@ -2652,21 +2722,48 @@ async function listAntigravityPorts(pid, {
2652
2722
  }
2653
2723
  return ports;
2654
2724
  }
2725
+
2726
+ // On Linux, try procfs first when no mock command runner is provided (zero-spawn, no dependencies).
2727
+ if (platform === "linux" && !commandRunner) {
2728
+ const procPorts = parseLinuxProcListeningPorts(pid, { procRoot });
2729
+ if (procPorts.length > 0) return procPorts;
2730
+ }
2731
+
2655
2732
  const lsof = await resolveLsofBinary({ commandRunner });
2656
- if (!lsof) {
2657
- throw new Error("Antigravity port detection needs lsof. Install it, then retry.");
2733
+ if (lsof) {
2734
+ const result = await runCommand(
2735
+ commandRunner,
2736
+ lsof,
2737
+ ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", String(pid)],
2738
+ { timeout: timeoutMs, signal },
2739
+ );
2740
+ const ports = parseListeningPorts(result?.stdout);
2741
+ if (ports.length > 0) return ports;
2658
2742
  }
2659
- const result = await runCommand(
2660
- commandRunner,
2661
- lsof,
2662
- ["-nP", "-iTCP", "-sTCP:LISTEN", "-a", "-p", String(pid)],
2663
- { timeout: timeoutMs, signal },
2664
- );
2665
- const ports = parseListeningPorts(result?.stdout);
2666
- if (!ports.length) {
2667
- throw new Error("Antigravity is running but not exposing ports yet. Try again in a few seconds.");
2743
+
2744
+ // On Linux, fall back to procfs (honoring procRoot) or ss if lsof is absent or yielded no ports.
2745
+ if (platform === "linux") {
2746
+ const procPorts = parseLinuxProcListeningPorts(pid, { procRoot });
2747
+ if (procPorts.length > 0) return procPorts;
2748
+
2749
+ const ss = await whichBinary("ss", { commandRunner });
2750
+ if (ss) {
2751
+ const result = await runCommand(
2752
+ commandRunner,
2753
+ ss,
2754
+ ["-H", "-tlpn"],
2755
+ { timeout: timeoutMs, signal },
2756
+ );
2757
+ const ssPorts = parseSsListeningPorts(result?.stdout, pid);
2758
+ if (ssPorts.length > 0) return ssPorts;
2759
+ }
2668
2760
  }
2669
- return ports;
2761
+
2762
+ if (!lsof && platform !== "linux") {
2763
+ throw new Error("Antigravity port detection needs lsof. Install it, then retry.");
2764
+ }
2765
+
2766
+ throw new Error("Antigravity is running but not exposing ports yet. Try again in a few seconds.");
2670
2767
  }
2671
2768
 
2672
2769
  function antigravityDefaultBody() {
@@ -3620,7 +3717,7 @@ async function fetchUsageLimitsUncached({
3620
3717
  : null;
3621
3718
 
3622
3719
  const providerFetch = withFetchTimeout(fetchImpl, providerTimeoutMs);
3623
- const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, agentPlan, claudeServiceStatus] = await Promise.all([
3720
+ const [claudeResult, codexResult, cursor, kimi, gemini, kiro, antigravity, copilot, grok, zcode, opencodeGoRaw, qoder, qoderCn, codingPlan, agentPlan, commandCodeRaw, claudeServiceStatus] = await Promise.all([
3624
3721
  claudeToken && !freshClaudeCache && !claudeRetryAtMs
3625
3722
  ? withProviderTimeout(fetchClaudeUsageLimits(claudeToken, { fetchImpl: providerFetch, maxAttempts: 1 }), "Claude", providerTimeoutMs).then(
3626
3723
  (value) => ({ status: "fulfilled", value }),
@@ -3721,6 +3818,19 @@ async function fetchUsageLimitsUncached({
3721
3818
  "Ark Agent Plan",
3722
3819
  providerTimeoutMs,
3723
3820
  ).catch((reason) => ({ configured: true, error: reason?.message || "Unknown error" })),
3821
+ // CommandCode (commandcode.ai): official subscription windows (5h + weekly)
3822
+ // from the CLI's own alpha endpoints, keyed by the apiKey the CLI stores in
3823
+ // ~/.commandcode/auth.json (or COMMAND_CODE_API_KEY). No local fallback —
3824
+ // window state lives server-side. fetchCommandcodeLimits throws on auth
3825
+ // expiry so the assemble step below can flag auth_action_required. The
3826
+ // outer race bounds the whole slot: without it three sequential per-request
3827
+ // timeouts (whoami, then credits+subscriptions) could hold the aggregate
3828
+ // ~2x longer than any sibling provider.
3829
+ withProviderTimeout(fetchCommandcodeLimits({ home, env, fetchImpl: providerFetch }), "CommandCode", providerTimeoutMs)
3830
+ .then(
3831
+ (value) => ({ status: "fulfilled", value }),
3832
+ (reason) => ({ status: "rejected", reason }),
3833
+ ),
3724
3834
  // Public status-page probe (fail-soft, own 5-min cache in provider-status.js).
3725
3835
  // Only probed for configured accounts — without a token the Claude section
3726
3836
  // never renders, so the reading would have nowhere to go.
@@ -3897,6 +4007,28 @@ async function fetchUsageLimitsUncached({
3897
4007
  }
3898
4008
  }
3899
4009
 
4010
+ // CommandCode: official windows straight from the API — nothing to cache on
4011
+ // disk beyond the shared 2-minute in-memory cache, so keep the assembly
4012
+ // simple. A fulfilled `configured: false` means no apiKey found; a rejected
4013
+ // fetch surfaced an auth-expired error the panel should act on.
4014
+ let commandCodeObj;
4015
+ if (commandCodeRaw?.status === "fulfilled") {
4016
+ const value = commandCodeRaw.value;
4017
+ commandCodeObj = value && value.configured === false
4018
+ ? value
4019
+ : { ...value, stale: false, cached_at: new Date(nowMs).toISOString() };
4020
+ } else {
4021
+ const reason = commandCodeRaw?.reason || null;
4022
+ const authExpired = Boolean(
4023
+ reason &&
4024
+ (reason?.code === "AUTH_EXPIRED" ||
4025
+ /token expired|run `cmd login`|Not authenticated/i.test(reason?.message || "")),
4026
+ );
4027
+ commandCodeObj = authExpired
4028
+ ? { configured: true, error: reason?.message || "Unknown error", auth_action_required: "reauth" }
4029
+ : { configured: true, error: reason?.message || "Unknown error" };
4030
+ }
4031
+
3900
4032
  const data = {
3901
4033
  fetched_at: new Date(nowMs).toISOString(),
3902
4034
  claude: withPlanLabel(claude, claudePlanType, "Claude"),
@@ -3914,6 +4046,10 @@ async function fetchUsageLimitsUncached({
3914
4046
  grok: withPlanLabel(grok, null, "Grok"),
3915
4047
  zcode: withPlanLabel(zcode, zcode.plan_label, "ZCode"),
3916
4048
  opencodeGo: withPlanLabel(opencodeGo, opencodeGo?.plan_label, "OpenCode Go"),
4049
+ // CommandCode tiers are acronym brands (GOAT/Pro/Max) — the fetcher already
4050
+ // maps plan ids to the CLI's exact display strings, so skip the shared
4051
+ // Title-Case normalization ("Goat") and surface them as-is.
4052
+ commandCode: commandCodeObj,
3917
4053
  qoder: withPlanLabel(qoder, qoder?.plan_label, "Qoder"),
3918
4054
  qoderCn: withPlanLabel(qoderCn, qoderCn?.plan_label, "Qoder CN"),
3919
4055
  codingPlan: withPlanLabel(codingPlan, codingPlan?.plan_label, "Ark Coding Plan"),
@@ -3969,6 +4105,8 @@ module.exports = {
3969
4105
  loadAntigravityCredentials,
3970
4106
  parseListeningPorts,
3971
4107
  parseWindowsListeningPorts,
4108
+ parseLinuxProcListeningPorts,
4109
+ parseSsListeningPorts,
3972
4110
  listAntigravityPorts,
3973
4111
  detectAntigravityProcess,
3974
4112
  fetchAntigravityLimits,
@@ -3982,6 +4120,7 @@ module.exports = {
3982
4120
  fetchGrokLimits,
3983
4121
  fetchZcodeLimits,
3984
4122
  fetchOpencodeGoLimits,
4123
+ fetchCommandcodeLimits,
3985
4124
  fetchQoderLimits,
3986
4125
  fetchQoderCnLimits,
3987
4126
  };