tokentracker-cli 0.87.9 → 0.87.11

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 (96) hide show
  1. package/dashboard/dist/assets/{AchievementBadge-D3_hERkQ.js → AchievementBadge-C-w9f93M.js} +1 -1
  2. package/dashboard/dist/assets/{AchievementsPage-DZGjT92g.js → AchievementsPage-BAtc6581.js} +2 -2
  3. package/dashboard/dist/assets/{AchievementsSection-CEzq3U3A.js → AchievementsSection-Bok48qmR.js} +1 -1
  4. package/dashboard/dist/assets/{BadgeDetailModal-D3_B8E--.js → BadgeDetailModal-CnR1S5Fk.js} +1 -1
  5. package/dashboard/dist/assets/{Card-CMXM3-lp.js → Card-A4JQKxIZ.js} +1 -1
  6. package/dashboard/dist/assets/{ClawdAnimated-lQxUKRzw.js → ClawdAnimated-CNRbH_ri.js} +1 -1
  7. package/dashboard/dist/assets/{CommandPalette-BCGdAeFB.js → CommandPalette-CAkZSh_I.js} +1 -1
  8. package/dashboard/dist/assets/{CommunityStatsModal-BwE5h2y5.js → CommunityStatsModal-Dh1PvEU5.js} +1 -1
  9. package/dashboard/dist/assets/{ConfirmModal-8uhS2ccj.js → ConfirmModal-DtUn4PvI.js} +1 -1
  10. package/dashboard/dist/assets/{Controls-CBEQTl2X.js → Controls-Bju6_Qgc.js} +1 -1
  11. package/dashboard/dist/assets/{DashboardPage-UE_3tGVy.js → DashboardPage-BFXwPC6L.js} +1 -1
  12. package/dashboard/dist/assets/DevicePage-BbiVkFkJ.js +1 -0
  13. package/dashboard/dist/assets/{DialogClose-L_t-nrYm.js → DialogClose-OelNPG0Y.js} +1 -1
  14. package/dashboard/dist/assets/DialogDescription-DtcxB2Zm.js +1 -0
  15. package/dashboard/dist/assets/{DialogTitle-Bsft4VvL.js → DialogTitle-C_CTTQtO.js} +1 -1
  16. package/dashboard/dist/assets/{FadeIn-BNDdLqTu.js → FadeIn-CkRSoCb2.js} +1 -1
  17. package/dashboard/dist/assets/{HeaderGithubStar-CvNaKDbl.js → HeaderGithubStar-CTX7kP5L.js} +1 -1
  18. package/dashboard/dist/assets/{HoverTooltip-D5j5IpNx.js → HoverTooltip-CPlF8ZwO.js} +1 -1
  19. package/dashboard/dist/assets/{IpCheckPage-Csf8gMJW.js → IpCheckPage-BJsKVMC-.js} +1 -1
  20. package/dashboard/dist/assets/{LandingPage-BVSSwM0D.js → LandingPage-9jAUDqx0.js} +2 -2
  21. package/dashboard/dist/assets/{LeaderboardAvatar-DXxSHqmP.js → LeaderboardAvatar-8dN340iC.js} +1 -1
  22. package/dashboard/dist/assets/{LeaderboardPage-DXj21BBR.js → LeaderboardPage-B1MBedOd.js} +2 -2
  23. package/dashboard/dist/assets/{LeaderboardProfileModal-DbEzZGg4.js → LeaderboardProfileModal-CxrQFLfo.js} +3 -3
  24. package/dashboard/dist/assets/{LeaderboardProfilePage-DoIm3jB_.js → LeaderboardProfilePage-C7LkCn7j.js} +1 -1
  25. package/dashboard/dist/assets/{LimitsPage-D_tpQrUM.js → LimitsPage-CX2ohexs.js} +1 -1
  26. package/dashboard/dist/assets/{LocalOnlyNotice-DBi5iuPr.js → LocalOnlyNotice-Bx3Y6V6z.js} +1 -1
  27. package/dashboard/dist/assets/{LoginCard-qDIJgUX1.js → LoginCard-C6nKjmVd.js} +1 -1
  28. package/dashboard/dist/assets/{LoginModal-D4b5p4u1.js → LoginModal-Cy3pcURe.js} +1 -1
  29. package/dashboard/dist/assets/{LoginPage-CsSgItFA.js → LoginPage-DwA2WE_J.js} +1 -1
  30. package/dashboard/dist/assets/{PetPage-B-wElg5q.js → PetPage-CeMz69em.js} +1 -1
  31. package/dashboard/dist/assets/{PopoverPopup-9C_VarUQ.js → PopoverPopup-Dr_89ZsN.js} +1 -1
  32. package/dashboard/dist/assets/{ProviderIcon-CciEdEeR.js → ProviderIcon-yBPK67if.js} +1 -1
  33. package/dashboard/dist/assets/{ResetPasswordPage-lsI8qlzE.js → ResetPasswordPage-5foVX-ji.js} +1 -1
  34. package/dashboard/dist/assets/{SegmentedControl-BGonMV45.js → SegmentedControl-ByMBjrQu.js} +1 -1
  35. package/dashboard/dist/assets/{Select-DhRrGrDT.js → Select-BmiLelJH.js} +1 -1
  36. package/dashboard/dist/assets/{SelectItemText-ClMYgZsa.js → SelectItemText-ra9fOxTH.js} +1 -1
  37. package/dashboard/dist/assets/{ServiceStatusPage-DuzttgL7.js → ServiceStatusPage-DN15AhqV.js} +1 -1
  38. package/dashboard/dist/assets/{SessionsPage-gZbxx0f_.js → SessionsPage-DcNtVUlG.js} +1 -1
  39. package/dashboard/dist/assets/{SettingsPage-NhxTrVNW.js → SettingsPage-B62dIBq2.js} +1 -1
  40. package/dashboard/dist/assets/{SkillsPage-BeXojJqQ.js → SkillsPage-BH9Y-Jr3.js} +1 -1
  41. package/dashboard/dist/assets/{TokenGalaxy-DM49zKzo.js → TokenGalaxy-Dop8r6gR.js} +1 -1
  42. package/dashboard/dist/assets/{ToolbarRootContext-BfvoVNMW.js → ToolbarRootContext--p8cQmQC.js} +1 -1
  43. package/dashboard/dist/assets/{TrendMonitor-Bu-dXcrV.js → TrendMonitor-O_3Y-SZE.js} +1 -1
  44. package/dashboard/dist/assets/{WidgetsPage-D2Wo7seE.js → WidgetsPage-fmeuoBRn.js} +1 -1
  45. package/dashboard/dist/assets/{WrappedPage-BXAwic4D.js → WrappedPage-D31IOcLs.js} +1 -1
  46. package/dashboard/dist/assets/{__vite-browser-external-BDEQk--l.js → __vite-browser-external-B8xHeUWG.js} +2 -2
  47. package/dashboard/dist/assets/{agent-logos-DMEEHjG7.js → agent-logos-qv-6cVem.js} +1 -1
  48. package/dashboard/dist/assets/{arrow-up-right-gNviKtY8.js → arrow-up-right-D2jDALUn.js} +1 -1
  49. package/dashboard/dist/assets/{check-Cvg5pmYP.js → check-Ct6KAwPF.js} +1 -1
  50. package/dashboard/dist/assets/{chevron-right-DxdaNTo8.js → chevron-right-C6_7i5IV.js} +1 -1
  51. package/dashboard/dist/assets/{copy-BNIJL7bf.js → copy-CuN61rn4.js} +1 -1
  52. package/dashboard/dist/assets/{download-C9McJlPa.js → download-CGQHQ_O1.js} +1 -1
  53. package/dashboard/dist/assets/{external-link-C_IU6zDK.js → external-link-DMK8oGV1.js} +1 -1
  54. package/dashboard/dist/assets/{flame-CII7X8Je.js → flame-DYDfZm4m.js} +1 -1
  55. package/dashboard/dist/assets/{format-tokens-DuQbGpPt.js → format-tokens-Cr3tGM0M.js} +1 -1
  56. package/dashboard/dist/assets/{icons-B1YTqnxG.js → icons-C5uWoSyt.js} +1 -1
  57. package/dashboard/dist/assets/{index-Dc9xsIu2.js → index-BnRjukrI.js} +1 -1
  58. package/dashboard/dist/assets/{index-BRMxraNK.js → index-BxQbRCxm.js} +1 -1
  59. package/dashboard/dist/assets/{index-QtcYir6O.js → index-CvXbHj4v.js} +1 -1
  60. package/dashboard/dist/assets/{info-CIgVqJoj.js → info-DrAzGhOW.js} +1 -1
  61. package/dashboard/dist/assets/{limits-providers-B31NsGar.js → limits-providers-DxW9ioQB.js} +1 -1
  62. package/dashboard/dist/assets/{link-2-DTPXzHuB.js → link-2-BI38rNr_.js} +1 -1
  63. package/dashboard/dist/assets/{loader-circle-CgQSyCz8.js → loader-circle-Bo5D6bhR.js} +1 -1
  64. package/dashboard/dist/assets/{maximize-2-BL5J4aHZ.js → maximize-2-CYdScbRg.js} +1 -1
  65. package/dashboard/dist/assets/{modifiers.esm-s_rzcEf2.js → modifiers.esm-quleLIKM.js} +1 -1
  66. package/dashboard/dist/assets/{monitor-smartphone-C9ei_xde.js → monitor-smartphone-BIX1vqmO.js} +1 -1
  67. package/dashboard/dist/assets/{provider-display-ZgvoJi05.js → provider-display-BIgyIwfN.js} +1 -1
  68. package/dashboard/dist/assets/{react-DhfPVGOm.js → react-D3GtDc7w.js} +1 -1
  69. package/dashboard/dist/assets/{search-Ba_MWO7q.js → search-BkIdCRLX.js} +1 -1
  70. package/dashboard/dist/assets/{skills-api-BkmJmyg1.js → skills-api-CderQz5B.js} +1 -1
  71. package/dashboard/dist/assets/{sortable.esm-Bmd5t12E.js → sortable.esm-2gcAB4zs.js} +1 -1
  72. package/dashboard/dist/assets/{store-BxRQs9yc.js → store-C1IKZc33.js} +1 -1
  73. package/dashboard/dist/assets/{terminal-BzUwAs4g.js → terminal-p1ztJj4p.js} +1 -1
  74. package/dashboard/dist/assets/{trash-2-CCprY7cu.js → trash-2-CJWF-VNC.js} +1 -1
  75. package/dashboard/dist/assets/{use-community-stats-TCdPJ_ir.js → use-community-stats-Dn06TrFv.js} +1 -1
  76. package/dashboard/dist/assets/{use-limits-display-prefs-CNF9luO6.js → use-limits-display-prefs-BgaBwlW4.js} +1 -1
  77. package/dashboard/dist/assets/{use-ordered-list-BlTuSYJy.js → use-ordered-list-CLAfnQJp.js} +1 -1
  78. package/dashboard/dist/assets/use-reduced-motion-DAOu1VYi.js +1 -0
  79. package/dashboard/dist/assets/{use-session-efficiency-pref-iHS0O_Fo.js → use-session-efficiency-pref-Bjt6Wz-F.js} +1 -1
  80. package/dashboard/dist/assets/{use-transform-CcVfL3mi.js → use-transform-Bacfpv38.js} +1 -1
  81. package/dashboard/dist/assets/{use-usage-limits-Xdy-Y_1Z.js → use-usage-limits-BF6HF4CU.js} +1 -1
  82. package/dashboard/dist/assets/{useCurrency-IXccXbmx.js → useCurrency-BResSm4n.js} +1 -1
  83. package/dashboard/dist/assets/{useScrollLock-BEVewfZm.js → useScrollLock-C7qX5L-D.js} +1 -1
  84. package/dashboard/dist/assets/{useTokenFormat-Cihv3Ghl.js → useTokenFormat-COB2oujy.js} +1 -1
  85. package/dashboard/dist/assets/{zap-zG2Fbb5_.js → zap-8w1maR-b.js} +1 -1
  86. package/dashboard/dist/index.html +1 -1
  87. package/dashboard/dist/ip-check.html +1 -1
  88. package/dashboard/dist/leaderboard.html +1 -1
  89. package/dashboard/dist/share.html +1 -1
  90. package/package.json +1 -1
  91. package/src/commands/sync.js +186 -1
  92. package/src/lib/pricing/seed-snapshot.json +1 -1
  93. package/src/lib/rollout.js +232 -44
  94. package/dashboard/dist/assets/DevicePage-B06ybHJI.js +0 -1
  95. package/dashboard/dist/assets/DialogDescription-B7FFtR7A.js +0 -1
  96. package/dashboard/dist/assets/use-reduced-motion-CfiR91Eg.js +0 -1
@@ -7842,9 +7842,8 @@ function resolveCodebuddyDefaultModel(env = process.env) {
7842
7842
 
7843
7843
  // Modern CodeBuddy writes token usage into providerData.rawUsage on JSONL
7844
7844
  // records. Older IDE installs may have no usable JSONL usage and rely on the
7845
- // extension log fallback below. Keep the sniff bounded: it is used only to
7846
- // decide whether enabling the legacy log source would create a second copy of
7847
- // the same round trips.
7845
+ // extension log fallback below. Keep the sniff bounded: it is used by the
7846
+ // guarded historical migration to decide whether a JSONL rebuild is safe.
7848
7847
  function codebuddyJsonlHasUsage(filePath) {
7849
7848
  let fd;
7850
7849
  try {
@@ -7875,6 +7874,60 @@ function codebuddyJsonlHasUsage(filePath) {
7875
7874
  return false;
7876
7875
  }
7877
7876
 
7877
+ // Legacy IDE extension logs and modern JSONL records do not share an id. Keep
7878
+ // a bounded, source-neutral fingerprint ledger so a log line can be matched to
7879
+ // the JSONL round-trip it mirrors without collapsing legitimate same-second
7880
+ // requests. The ledger is persisted in the CodeBuddy cursor because the two
7881
+ // sources can arrive on different syncs.
7882
+ function codebuddyUsageFingerprint({ model, timestampMs, inputTokens, cacheRead, cacheCreation, outputTokens, reasoningTokens }) {
7883
+ if (!Number.isFinite(Number(timestampMs)) || Number(timestampMs) <= 0) return null;
7884
+ const second = Math.floor(Number(timestampMs) / 1000);
7885
+ return [
7886
+ second,
7887
+ normalizeModelInput(model) || "unknown",
7888
+ toNonNegativeInt(inputTokens),
7889
+ toNonNegativeInt(cacheRead),
7890
+ toNonNegativeInt(cacheCreation),
7891
+ toNonNegativeInt(outputTokens),
7892
+ toNonNegativeInt(reasoningTokens),
7893
+ ].join(":");
7894
+ }
7895
+
7896
+ function restoreCodebuddyUsageFingerprints(raw) {
7897
+ const state = new Map();
7898
+ if (!raw || typeof raw !== "object") return state;
7899
+ for (const [key, value] of Object.entries(raw)) {
7900
+ const jsonl = toNonNegativeInt(value?.jsonl);
7901
+ const log = toNonNegativeInt(value?.log);
7902
+ if (jsonl > 0 || log > 0) state.set(key, { jsonl, log });
7903
+ }
7904
+ return state;
7905
+ }
7906
+
7907
+ function addCodebuddyJsonlFingerprint(state, fingerprint) {
7908
+ if (!fingerprint) return false;
7909
+ const current = state.get(fingerprint) || { jsonl: 0, log: 0 };
7910
+ const mirrored = current.log > current.jsonl;
7911
+ current.jsonl += 1;
7912
+ state.set(fingerprint, current);
7913
+ return mirrored;
7914
+ }
7915
+
7916
+ function consumeCodebuddyLogFingerprint(state, fingerprint) {
7917
+ if (!fingerprint) return false;
7918
+ const current = state.get(fingerprint) || { jsonl: 0, log: 0 };
7919
+ const mirrored = current.jsonl > current.log;
7920
+ current.log += 1;
7921
+ state.set(fingerprint, current);
7922
+ return mirrored;
7923
+ }
7924
+
7925
+ function capCodebuddyUsageFingerprints(state, maxEntries = 10_000) {
7926
+ const entries = Array.from(state.entries());
7927
+ if (entries.length <= maxEntries) return Object.fromEntries(entries);
7928
+ return Object.fromEntries(entries.slice(entries.length - maxEntries));
7929
+ }
7930
+
7878
7931
  function resolveCodebuddyProjectFiles(env = process.env) {
7879
7932
  const codebuddyHome = resolveCodebuddyHome(env);
7880
7933
  const jsonlFiles = [];
@@ -7965,9 +8018,12 @@ function resolveCodebuddyProjectFiles(env = process.env) {
7965
8018
  }
7966
8019
 
7967
8020
  const fallbackMode = String(env.TOKENTRACKER_CODEBUDDY_LOG_FALLBACK || "").trim();
7968
- const includeLogs =
7969
- fallbackMode === "1" ||
7970
- (fallbackMode !== "0" && !jsonlFiles.some((filePath) => codebuddyJsonlHasUsage(filePath)));
8021
+ // Auto mode keeps both sources available. The parser performs bounded,
8022
+ // count-aware cross-source fingerprint matching, so mixed histories retain
8023
+ // legacy-only log sessions while mirrored rounds are counted once. Env=0 is
8024
+ // still a supported escape hatch for users who want to suppress extension
8025
+ // logs entirely; env=1 remains an explicit force-on value.
8026
+ const includeLogs = fallbackMode !== "0";
7971
8027
  const files = [...jsonlFiles, ...(includeLogs ? logFiles : [])];
7972
8028
  files.sort((a, b) => a.localeCompare(b));
7973
8029
  return files;
@@ -8030,10 +8086,21 @@ async function parseCodebuddyIncremental({
8030
8086
  codebuddyState.logModelsByAgent && typeof codebuddyState.logModelsByAgent === "object"
8031
8087
  ? { ...codebuddyState.logModelsByAgent }
8032
8088
  : {};
8089
+ const usageFingerprints = restoreCodebuddyUsageFingerprints(codebuddyState.usageFingerprints);
8033
8090
 
8034
- const files = Array.isArray(projectFiles)
8091
+ const discoveredFiles = Array.isArray(projectFiles)
8035
8092
  ? projectFiles
8036
8093
  : resolveCodebuddyProjectFiles(env || process.env);
8094
+ // JSONL must be parsed before extension logs so the fingerprint ledger can
8095
+ // match mirrored rounds in the same sync. Keep the legacy bare-string API
8096
+ // accepted for callers and tests.
8097
+ const files = [...discoveredFiles].sort((a, b) => {
8098
+ const aPath = typeof a === "string" ? a : a?.path || "";
8099
+ const bPath = typeof b === "string" ? b : b?.path || "";
8100
+ const aLog = aPath.endsWith(".log") ? 1 : 0;
8101
+ const bLog = bPath.endsWith(".log") ? 1 : 0;
8102
+ return aLog - bLog || aPath.localeCompare(bPath);
8103
+ });
8037
8104
  const fallbackModel = defaultModel || resolveCodebuddyDefaultModel(env || process.env);
8038
8105
 
8039
8106
  if (files.length === 0) {
@@ -8041,6 +8108,7 @@ async function parseCodebuddyIncremental({
8041
8108
  ...codebuddyState,
8042
8109
  seenIds: Array.from(seenIds),
8043
8110
  fileOffsets,
8111
+ usageFingerprints: capCodebuddyUsageFingerprints(usageFingerprints),
8044
8112
  updatedAt: new Date().toISOString(),
8045
8113
  };
8046
8114
  return { recordsProcessed: 0, eventsAggregated: 0, bucketsQueued: 0 };
@@ -8192,6 +8260,20 @@ async function parseCodebuddyIncremental({
8192
8260
  fallbackModel;
8193
8261
  const model = normalizeModelInput(rawModel);
8194
8262
 
8263
+ const fingerprint = codebuddyUsageFingerprint({
8264
+ model,
8265
+ timestampMs: tsMs,
8266
+ inputTokens,
8267
+ cacheRead,
8268
+ cacheCreation,
8269
+ outputTokens: completionTokens,
8270
+ reasoningTokens,
8271
+ });
8272
+ if (consumeCodebuddyLogFingerprint(usageFingerprints, fingerprint)) {
8273
+ seenIds.add(messageId);
8274
+ continue;
8275
+ }
8276
+
8195
8277
  const delta = {
8196
8278
  input_tokens: inputTokens,
8197
8279
  cached_input_tokens: cacheRead,
@@ -8318,6 +8400,20 @@ async function parseCodebuddyIncremental({
8318
8400
  normalizeModelInput(entry.model) ||
8319
8401
  fallbackModel;
8320
8402
 
8403
+ const fingerprint = codebuddyUsageFingerprint({
8404
+ model,
8405
+ timestampMs: tsMs,
8406
+ inputTokens,
8407
+ cacheRead,
8408
+ cacheCreation,
8409
+ outputTokens: completionTokens,
8410
+ reasoningTokens,
8411
+ });
8412
+ if (addCodebuddyJsonlFingerprint(usageFingerprints, fingerprint)) {
8413
+ seenIds.add(messageId);
8414
+ continue;
8415
+ }
8416
+
8321
8417
  const delta = {
8322
8418
  input_tokens: inputTokens,
8323
8419
  cached_input_tokens: cacheRead,
@@ -8333,6 +8429,13 @@ async function parseCodebuddyIncremental({
8333
8429
  addTotals(bucket.totals, delta);
8334
8430
  touchedBuckets.add(bucketKey("codebuddy", model, bucketStart));
8335
8431
  seenIds.add(messageId);
8432
+ // Preserve the pre-fingerprint cursor key as well. Older releases
8433
+ // keyed JSONL rows by entry.id; retaining it prevents the first
8434
+ // post-upgrade sync from replaying an already-counted row when
8435
+ // providerData.messageId is now preferred.
8436
+ if (typeof entry.id === "string" && entry.id && entry.id !== messageId) {
8437
+ seenIds.add(entry.id);
8438
+ }
8336
8439
  eventsAggregated++;
8337
8440
 
8338
8441
  if (cb) {
@@ -8380,6 +8483,7 @@ async function parseCodebuddyIncremental({
8380
8483
  seenIds: cappedSeen,
8381
8484
  fileOffsets,
8382
8485
  logModelsByAgent: cappedLogModelsByAgent,
8486
+ usageFingerprints: capCodebuddyUsageFingerprints(usageFingerprints),
8383
8487
  updatedAt,
8384
8488
  };
8385
8489
 
@@ -8433,9 +8537,11 @@ async function parseCodebuddyIncremental({
8433
8537
  //
8434
8538
  // 4. Completed ~/.workbuddy/traces/**/trace_*.json summaries expose aggregate
8435
8539
  // modelInfo input/output/cache totals. They are a fallback only when the
8436
- // session has no detailed JSONL usage; SQLite's session_usage table has no
8437
- // output/cache/reasoning columns, so SQLite-only rows intentionally remain
8438
- // input/total-only instead of fabricating a token split.
8540
+ // session has no detailed JSONL usage. The current SQLite session_usage
8541
+ // table exposes bounded context-window state (`used`/`size`) plus credits,
8542
+ // not billable token columns, so SQLite-only rows are ignored rather than
8543
+ // mislabelled as input usage. A future schema with explicit token columns
8544
+ // is accepted by workbuddySqliteUsageSnapshot.
8439
8545
  // ─────────────────────────────────────────────────────────────────────────────
8440
8546
 
8441
8547
  function resolveWorkbuddyHome(env = process.env) {
@@ -8534,6 +8640,49 @@ function resolveWorkbuddyProjectFiles(env = process.env) {
8534
8640
  return files;
8535
8641
  }
8536
8642
 
8643
+ // WorkBuddy's current session_usage table stores context-window state in
8644
+ // `used`/`size` and model credit dollars in `credit_json`; neither is a token
8645
+ // accounting breakdown. Only consume a SQLite row when a future schema (or a
8646
+ // newer installation) exposes explicit cumulative token columns. This keeps
8647
+ // the fallback forward-compatible without treating a context snapshot as
8648
+ // billable usage.
8649
+ function workbuddySqliteUsageSnapshot(row) {
8650
+ if (!row || typeof row !== "object") return null;
8651
+ const hasAny = (keys) => keys.some((key) => Object.prototype.hasOwnProperty.call(row, key));
8652
+ const inputKeys = ["input_tokens", "inputTokens", "prompt_tokens", "promptTokens"];
8653
+ const cacheReadKeys = [
8654
+ "cached_input_tokens",
8655
+ "cachedInputTokens",
8656
+ "cache_read_input_tokens",
8657
+ "cacheReadInputTokens",
8658
+ "cache_read_tokens",
8659
+ "cacheReadTokens",
8660
+ ];
8661
+ const cacheCreationKeys = [
8662
+ "cache_creation_input_tokens",
8663
+ "cacheCreationInputTokens",
8664
+ "prompt_cache_write_tokens",
8665
+ "promptCacheWriteTokens",
8666
+ ];
8667
+ const outputKeys = ["output_tokens", "outputTokens", "completion_tokens", "completionTokens"];
8668
+ const reasoningKeys = [
8669
+ "reasoning_output_tokens",
8670
+ "reasoningOutputTokens",
8671
+ "reasoning_tokens",
8672
+ "reasoningTokens",
8673
+ ];
8674
+ if (!hasAny([...inputKeys, ...cacheReadKeys, ...cacheCreationKeys, ...outputKeys, ...reasoningKeys])) {
8675
+ return null;
8676
+ }
8677
+ return {
8678
+ input: firstPresentNonNegativeInt(inputKeys.map((key) => row[key])),
8679
+ cacheRead: firstPresentNonNegativeInt(cacheReadKeys.map((key) => row[key])),
8680
+ cacheCreation: firstPresentNonNegativeInt(cacheCreationKeys.map((key) => row[key])),
8681
+ output: firstPresentNonNegativeInt(outputKeys.map((key) => row[key])),
8682
+ reasoning: firstPresentNonNegativeInt(reasoningKeys.map((key) => row[key])),
8683
+ };
8684
+ }
8685
+
8537
8686
  async function parseWorkbuddyIncremental({
8538
8687
  projectFiles,
8539
8688
  cursors,
@@ -8641,26 +8790,28 @@ async function parseWorkbuddyIncremental({
8641
8790
  typeof entry.sessionId === "string" && entry.sessionId
8642
8791
  ? entry.sessionId
8643
8792
  : path.basename(filePath, ".jsonl");
8644
- if (
8645
- Object.prototype.hasOwnProperty.call(sqliteSessions, sessionId) ||
8646
- tracedSessionIds.has(sessionId)
8647
- ) {
8793
+ const sqliteSession = sqliteSessions[sessionId];
8794
+ if (sqliteSession?.detailed || tracedSessionIds.has(sessionId)) {
8648
8795
  continue;
8649
8796
  }
8650
8797
  const tsMs =
8651
8798
  Number.isFinite(Number(entry.timestamp)) && Number(entry.timestamp) > 0
8652
8799
  ? Number(entry.timestamp)
8653
8800
  : null;
8654
- // One usage record per LLM round-trip; the response id is the most stable
8655
- // dedup key, then providerData.messageId, then session+timestamp.
8801
+ // One usage record per LLM round-trip; providerData.messageId is the
8802
+ // response-level id shared by function_call/message records and must win
8803
+ // over the per-record append id. Fall back to entry.uuid/id only when the
8804
+ // response-level id is absent.
8656
8805
  const messageId =
8657
- typeof entry.id === "string" && entry.id
8658
- ? entry.id
8659
- : typeof provider.messageId === "string" && provider.messageId
8806
+ typeof provider?.messageId === "string" && provider.messageId
8660
8807
  ? provider.messageId
8661
- : tsMs != null
8662
- ? `${sessionId}:${tsMs}`
8663
- : null;
8808
+ : typeof entry.uuid === "string" && entry.uuid
8809
+ ? entry.uuid
8810
+ : typeof entry.id === "string" && entry.id
8811
+ ? entry.id
8812
+ : tsMs != null
8813
+ ? `${sessionId}:${tsMs}`
8814
+ : null;
8664
8815
  if (!messageId) continue;
8665
8816
  if (seenIds.has(messageId)) continue;
8666
8817
 
@@ -8732,6 +8883,11 @@ async function parseWorkbuddyIncremental({
8732
8883
  addTotals(bucket.totals, delta);
8733
8884
  touchedBuckets.add(bucketKey("workbuddy", model, bucketStart));
8734
8885
  seenIds.add(messageId);
8886
+ // Keep legacy entry-id keys alongside the response id so upgrading a
8887
+ // cursor cannot replay a row that an older build already counted.
8888
+ if (typeof entry.id === "string" && entry.id && entry.id !== messageId) {
8889
+ seenIds.add(entry.id);
8890
+ }
8735
8891
  detailedSessions[sessionId] = true;
8736
8892
  detailedSessionsWithUsage.add(sessionId);
8737
8893
  eventsAggregated++;
@@ -8852,11 +9008,9 @@ async function parseWorkbuddyIncremental({
8852
9008
  if (dbExists) {
8853
9009
  const query = `
8854
9010
  SELECT
8855
- su.session_id,
8856
- su.used,
8857
- su.updated_at,
8858
- s.model,
8859
- s.cwd
9011
+ su.*,
9012
+ s.model AS session_model,
9013
+ s.cwd AS session_cwd
8860
9014
  FROM session_usage su
8861
9015
  LEFT JOIN sessions s ON s.id = su.session_id
8862
9016
  WHERE su.used IS NOT NULL
@@ -8887,33 +9041,68 @@ async function parseWorkbuddyIncremental({
8887
9041
  tracedSessionIds.has(sessionId)
8888
9042
  ) continue;
8889
9043
 
8890
- const usedNow = toNonNegativeInt(row.used);
8891
9044
  const updatedAtRaw = toNonNegativeInt(row.updated_at);
8892
- const rawModel = typeof row.model === "string" ? row.model.trim() : "";
9045
+ const rawModel = typeof row.session_model === "string"
9046
+ ? row.session_model.trim()
9047
+ : typeof row.model === "string" ? row.model.trim() : "";
8893
9048
 
8894
- if (usedNow <= 0 || updatedAtRaw <= 0) continue;
9049
+ if (updatedAtRaw <= 0) continue;
9050
+
9051
+ const snapshot = workbuddySqliteUsageSnapshot(row);
9052
+ const usedNow = toNonNegativeInt(row.used);
9053
+ const prev = sqliteSessions[sessionId] || {};
8895
9054
 
8896
- const prev = sqliteSessions[sessionId] || { used: 0 };
8897
- const prevUsed = toNonNegativeInt(prev.used);
8898
- const isReset = usedNow > 0 && prevUsed > 0 && usedNow < prevUsed;
8899
- const inputDelta = isReset ? usedNow : Math.max(0, usedNow - prevUsed);
8900
- if (inputDelta === 0) {
9055
+ // Current WorkBuddy releases expose only context state (`used` versus
9056
+ // `size`) here. Do not turn that bounded snapshot into a fake usage
9057
+ // delta; detailed JSONL/trace sources remain the authoritative path.
9058
+ if (!snapshot) {
9059
+ sqliteSessions[sessionId] = {
9060
+ ...prev,
9061
+ used: usedNow,
9062
+ updatedAt: updatedAtRaw,
9063
+ model: rawModel || prev.model || fallbackModel,
9064
+ detailed: false,
9065
+ };
9066
+ continue;
9067
+ }
9068
+
9069
+ const previousTokens = prev.tokens && typeof prev.tokens === "object"
9070
+ ? prev.tokens
9071
+ : { input: 0, cacheRead: 0, cacheCreation: 0, output: 0, reasoning: 0 };
9072
+ const isReset = ["input", "cacheRead", "cacheCreation", "output", "reasoning"]
9073
+ .some((key) => snapshot[key] < toNonNegativeInt(previousTokens[key]));
9074
+ const deltaTokens = {
9075
+ input: isReset ? snapshot.input : Math.max(0, snapshot.input - toNonNegativeInt(previousTokens.input)),
9076
+ cacheRead: isReset ? snapshot.cacheRead : Math.max(0, snapshot.cacheRead - toNonNegativeInt(previousTokens.cacheRead)),
9077
+ cacheCreation: isReset ? snapshot.cacheCreation : Math.max(0, snapshot.cacheCreation - toNonNegativeInt(previousTokens.cacheCreation)),
9078
+ output: isReset ? snapshot.output : Math.max(0, snapshot.output - toNonNegativeInt(previousTokens.output)),
9079
+ reasoning: isReset ? snapshot.reasoning : Math.max(0, snapshot.reasoning - toNonNegativeInt(previousTokens.reasoning)),
9080
+ };
9081
+ if (
9082
+ deltaTokens.input === 0 &&
9083
+ deltaTokens.cacheRead === 0 &&
9084
+ deltaTokens.cacheCreation === 0 &&
9085
+ deltaTokens.output === 0 &&
9086
+ deltaTokens.reasoning === 0
9087
+ ) {
8901
9088
  sqliteSessions[sessionId] = {
8902
9089
  ...prev,
8903
9090
  used: usedNow,
8904
9091
  updatedAt: updatedAtRaw,
8905
9092
  model: rawModel || prev.model || fallbackModel,
9093
+ tokens: snapshot,
9094
+ detailed: true,
8906
9095
  };
8907
9096
  continue;
8908
9097
  }
8909
9098
 
8910
9099
  recordsProcessed++;
8911
9100
 
8912
- const inputTokens = inputDelta;
8913
- const completionTokens = 0;
8914
- const cacheRead = 0;
8915
- const cacheCreation = 0;
8916
- const reasoningTokens = 0;
9101
+ const inputTokens = deltaTokens.input;
9102
+ const completionTokens = deltaTokens.output;
9103
+ const cacheRead = deltaTokens.cacheRead;
9104
+ const cacheCreation = deltaTokens.cacheCreation;
9105
+ const reasoningTokens = deltaTokens.reasoning;
8917
9106
 
8918
9107
  const tsMs = updatedAtRaw > 10000000000 ? updatedAtRaw : updatedAtRaw * 1000;
8919
9108
  const tsIso = new Date(tsMs).toISOString();
@@ -8922,9 +9111,6 @@ async function parseWorkbuddyIncremental({
8922
9111
 
8923
9112
  const model = normalizeModelInput(rawModel) || fallbackModel;
8924
9113
 
8925
- // session_usage only exposes a cumulative `used` value. It has no
8926
- // output/cache/reasoning columns, so SQLite-only history can safely
8927
- // contribute input/total but must not invent a token split.
8928
9114
  const delta = {
8929
9115
  input_tokens: inputTokens,
8930
9116
  cached_input_tokens: cacheRead,
@@ -8932,7 +9118,7 @@ async function parseWorkbuddyIncremental({
8932
9118
  output_tokens: completionTokens,
8933
9119
  reasoning_output_tokens: reasoningTokens,
8934
9120
  total_tokens: inputTokens + completionTokens + cacheRead + cacheCreation + reasoningTokens,
8935
- conversation_count: prevUsed === 0 || isReset ? 1 : 0,
9121
+ conversation_count: Object.keys(previousTokens).every((key) => toNonNegativeInt(previousTokens[key]) === 0) || isReset ? 1 : 0,
8936
9122
  };
8937
9123
 
8938
9124
  const bucket = getHourlyBucket(hourlyState, "workbuddy", model, bucketStart);
@@ -8942,6 +9128,8 @@ async function parseWorkbuddyIncremental({
8942
9128
  used: usedNow,
8943
9129
  updatedAt: updatedAtRaw,
8944
9130
  model,
9131
+ tokens: snapshot,
9132
+ detailed: true,
8945
9133
  };
8946
9134
  eventsAggregated++;
8947
9135
  }
@@ -1 +0,0 @@
1
- import{a as b,r as l,j as r,c as e,i as v}from"./__vite-browser-external-BDEQk--l.js";const x={BASE_URL:"/",DEV:!1,MODE:"production",PROD:!0,SSR:!1,VITE_APP_VERSION:"0.87.9",VITE_INSFORGE_ANON_KEY:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3OC0xMjM0LTU2NzgtOTBhYi1jZGVmMTIzNDU2NzgiLCJlbWFpbCI6ImFub25AaW5zZm9yZ2UuY29tIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODExNDU5NDd9.T0auta_IrVIh0uXW1bob5QSnzvsnJmN28r5XkSGEuQY",VITE_INSFORGE_BASE_URL:"https://srctyff5.us-east.insforge.app"};function f(){return v(),`${(x&&"https://srctyff5.us-east.insforge.app"||"https://srctyff5.us-east.insforge.app").replace(/\/$/,"")}/functions/tokentracker-device-flow-grant`}function p(s){return(s||"").toUpperCase().replace(/\s+/g,"").replace(/[^A-Z0-9-]/g,"")}function h(){const s=b(),[m,g]=l.useState(""),[i,a]=l.useState({kind:"idle",message:""});l.useEffect(()=>{try{const d=new URLSearchParams(window.location.search).get("user_code");d&&g(p(d))}catch{}},[]);async function u(c){if(c?.preventDefault?.(),!s?.signedIn){a({kind:"error",message:e("device.approval.error.sign_in")});return}const d=p(m);if(!/^[A-HJKMNP-Z2-9]{4}-[A-HJKMNP-Z2-9]{4}$/.test(d)){a({kind:"error",message:e("device.approval.error.invalid_code")});return}a({kind:"working",message:e("device.approval.action.approving")});try{const n=await s.getAccessToken?.();if(!n){a({kind:"error",message:e("device.approval.error.no_token")});return}const o=await fetch(f(),{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({user_code:d})}),t=await o.json().catch(()=>({}));o.ok?a({kind:"success",message:t.status==="already_approved"?t.client_info?e("device.approval.success.already_client",{client:t.client_info}):e("device.approval.success.already"):t.client_info?e("device.approval.success.approved_client",{client:t.client_info}):e("device.approval.success.approved")}):o.status===404?a({kind:"error",message:e("device.approval.error.not_found")}):o.status===410?a({kind:"error",message:e("device.approval.error.expired")}):o.status===401?a({kind:"error",message:e("device.approval.error.session_expired")}):a({kind:"error",message:t?.error?e("device.approval.error.server_detail",{message:t.error}):e("device.approval.error.server",{status:o.status})})}catch(n){a({kind:"error",message:e("device.approval.error.network",{message:n?.message||n})})}}return r.jsx("div",{className:"min-h-screen flex items-center justify-center bg-white dark:bg-oai-gray-950 px-6 py-12",children:r.jsxs("div",{className:"w-full max-w-md rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 p-8",children:[r.jsx("h1",{className:"text-2xl font-semibold text-oai-gray-900 dark:text-white mb-2",children:e("device.approval.title")}),r.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400 mb-6",children:e("device.approval.description")}),s?.signedIn?null:r.jsx("div",{className:"rounded-md border border-amber-200 bg-amber-50 dark:bg-amber-900/20 dark:border-amber-800 p-4 mb-4",children:r.jsx("p",{className:"text-sm text-amber-700 dark:text-amber-300",children:e("device.approval.sign_in_banner")})}),r.jsxs("form",{onSubmit:u,className:"space-y-4",children:[r.jsxs("label",{className:"block",children:[r.jsx("span",{className:"block text-xs font-medium uppercase tracking-wide text-oai-gray-500 dark:text-oai-gray-400 mb-1",children:e("device.approval.code_label")}),r.jsx("input",{type:"text",value:m,onChange:c=>g(p(c.target.value)),placeholder:"XXXX-XXXX",spellCheck:!1,autoCapitalize:"characters",maxLength:9,className:"w-full font-mono text-xl tracking-widest text-center rounded-md border border-oai-gray-300 dark:border-oai-gray-700 bg-white dark:bg-oai-gray-900 text-oai-gray-900 dark:text-white px-4 py-3 focus:outline-none focus:ring-inset focus:ring-2 focus:ring-emerald-600"})]}),r.jsx("button",{type:"submit",disabled:i.kind==="working"||!s?.signedIn,className:"w-full rounded-md bg-emerald-600 hover:bg-emerald-500 disabled:bg-oai-gray-300 disabled:cursor-not-allowed text-white font-medium py-3 transition-colors",children:i.kind==="working"?e("device.approval.action.approving"):e("device.approval.action.approve")})]}),i.kind==="success"&&r.jsx("div",{className:"mt-4 rounded-md border border-emerald-200 bg-emerald-50 dark:bg-emerald-900/20 dark:border-emerald-800 p-4",children:r.jsx("p",{className:"text-sm text-emerald-700 dark:text-emerald-300",children:i.message})}),i.kind==="error"&&r.jsx("div",{className:"mt-4 rounded-md border border-red-200 bg-red-50 dark:bg-red-900/20 dark:border-red-800 p-4",children:r.jsx("p",{className:"text-sm text-red-700 dark:text-red-300",children:i.message})})]})})}export{h as default};
@@ -1 +0,0 @@
1
- import{r as n,z as a}from"./__vite-browser-external-BDEQk--l.js";import{u as p}from"./DialogTitle-Bsft4VvL.js";import{u as c}from"./useScrollLock-BEVewfZm.js";const x=n.forwardRef(function(e,r){const{render:l,className:u,style:m,id:s,...t}=e,{store:i}=p(),o=c(s);return i.useSyncedValueWithCleanup("descriptionElementId",o),a("p",e,{ref:r,props:[{id:o},t]})});export{x as D};
@@ -1 +0,0 @@
1
- import{r as o}from"./__vite-browser-external-BDEQk--l.js";import{h as r,i as t,p as s}from"./react-DhfPVGOm.js";function i(){!r.current&&t();const[e]=o.useState(s.current);return e}export{i as u};