tokentracker-cli 0.79.0 → 0.79.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 (93) hide show
  1. package/README.de.md +6 -2
  2. package/README.ja.md +6 -2
  3. package/README.ko.md +6 -2
  4. package/README.md +6 -2
  5. package/README.zh-CN.md +6 -2
  6. package/dashboard/dist/achievements/README.md +40 -7
  7. package/dashboard/dist/achievements/big-day.png +0 -0
  8. package/dashboard/dist/achievements/marathoner.png +0 -0
  9. package/dashboard/dist/achievements/momentum.png +0 -0
  10. package/dashboard/dist/achievements/multitool.png +0 -0
  11. package/dashboard/dist/achievements/night-owl.png +0 -0
  12. package/dashboard/dist/achievements/podium.png +0 -0
  13. package/dashboard/dist/achievements/polyglot.png +0 -0
  14. package/dashboard/dist/achievements/project-devotion.png +0 -0
  15. package/dashboard/dist/achievements/project-hopper.png +0 -0
  16. package/dashboard/dist/achievements/streak.png +0 -0
  17. package/dashboard/dist/achievements/token-titan.png +0 -0
  18. package/dashboard/dist/achievements/trendsetter.png +0 -0
  19. package/dashboard/dist/achievements/veteran.png +0 -0
  20. package/dashboard/dist/achievements/weekend-warrior.png +0 -0
  21. package/dashboard/dist/achievements/wordsmith.png +0 -0
  22. package/dashboard/dist/assets/AchievementBadge-DcDkcyk4.js +1 -0
  23. package/dashboard/dist/assets/AchievementsPage-CV3Bwdzf.js +2 -0
  24. package/dashboard/dist/assets/AchievementsSection-Cj13lk07.js +1 -0
  25. package/dashboard/dist/assets/{ActivityHeatmap-B2NacPjc.js → ActivityHeatmap-BCBaXKEP.js} +1 -1
  26. package/dashboard/dist/assets/{BadgeDetailModal-BMNUpuro.js → BadgeDetailModal-DCOLG1fl.js} +1 -1
  27. package/dashboard/dist/assets/{Card-Y5og8UqF.js → Card-DT8qXml-.js} +1 -1
  28. package/dashboard/dist/assets/{ClawdAnimated-4EGVuTbr.js → ClawdAnimated-BzsZjNGN.js} +1 -1
  29. package/dashboard/dist/assets/{CommunityStatsModal-DgQ5Cv9h.js → CommunityStatsModal-Byx0RRzc.js} +1 -1
  30. package/dashboard/dist/assets/{ConfirmModal-DNDMMsf7.js → ConfirmModal-oyMOnK7G.js} +1 -1
  31. package/dashboard/dist/assets/{Controls-BzQP6AmV.js → Controls-PMRhKPF-.js} +1 -1
  32. package/dashboard/dist/assets/DashboardPage-1aUdHjJF.js +19 -0
  33. package/dashboard/dist/assets/{DevicePage-Hc411Mif.js → DevicePage-CGLIyRA8.js} +1 -1
  34. package/dashboard/dist/assets/{DialogClose-DG2e5iky.js → DialogClose-zLe-zrd-.js} +1 -1
  35. package/dashboard/dist/assets/DialogDescription-DT5tiXAh.js +1 -0
  36. package/dashboard/dist/assets/{DialogTitle-BlZXQboq.js → DialogTitle-hGgP0eky.js} +1 -1
  37. package/dashboard/dist/assets/{FadeIn-bR4Q4FyK.js → FadeIn-DeoGuCrV.js} +1 -1
  38. package/dashboard/dist/assets/{HeaderGithubStar-iujOMm58.js → HeaderGithubStar-CSZ0XQLV.js} +1 -1
  39. package/dashboard/dist/assets/{IpCheckPage-BVb5O-Nc.js → IpCheckPage-CJlsBtiO.js} +1 -1
  40. package/dashboard/dist/assets/{LandingPage-B5JCSXIZ.js → LandingPage-CK-aWD2Y.js} +1 -1
  41. package/dashboard/dist/assets/{LeaderboardAvatar-C5o48_SY.js → LeaderboardAvatar-hrfyPkYI.js} +1 -1
  42. package/dashboard/dist/assets/LeaderboardPage-CsMfaxrd.js +2 -0
  43. package/dashboard/dist/assets/{LeaderboardProfileModal-Buo6oZZk.js → LeaderboardProfileModal-C7mhM6ih.js} +4 -4
  44. package/dashboard/dist/assets/{LeaderboardProfilePage-DljcZHf7.js → LeaderboardProfilePage-CXyrCZiT.js} +1 -1
  45. package/dashboard/dist/assets/{LimitsPage-BOaJ4wfo.js → LimitsPage-Ck_M1OHJ.js} +1 -1
  46. package/dashboard/dist/assets/{LocalOnlyNotice-BBxetUgv.js → LocalOnlyNotice-BzI7RWAf.js} +1 -1
  47. package/dashboard/dist/assets/{LoginPage-LYriDN1G.js → LoginPage-BUm6w7Np.js} +1 -1
  48. package/dashboard/dist/assets/{PetPage-CPPVItLo.js → PetPage-DtAyFM55.js} +1 -1
  49. package/dashboard/dist/assets/{PopoverPopup-iP8jGLgj.js → PopoverPopup-BlkcABOL.js} +1 -1
  50. package/dashboard/dist/assets/{ResetPasswordPage-BqAwHe1w.js → ResetPasswordPage-CBSVeUPI.js} +1 -1
  51. package/dashboard/dist/assets/{Select-DdpTxWI0.js → Select-BJhSVz1k.js} +1 -1
  52. package/dashboard/dist/assets/{SelectItemText-Cpno_HPs.js → SelectItemText-B9zmI-iv.js} +1 -1
  53. package/dashboard/dist/assets/{SettingsPage-DtKCHKrt.js → SettingsPage-DviHKn3P.js} +1 -1
  54. package/dashboard/dist/assets/{SkillsPage-D5vXnEab.js → SkillsPage-B79FutnV.js} +1 -1
  55. package/dashboard/dist/assets/{WidgetsPage-BIPU747m.js → WidgetsPage-AwJIXxE9.js} +1 -1
  56. package/dashboard/dist/assets/{WrappedPage-VfTcO6cY.js → WrappedPage-CTJB07D4.js} +1 -1
  57. package/dashboard/dist/assets/{agent-logos-sYidWnQM.js → agent-logos-BPUbc3yd.js} +1 -1
  58. package/dashboard/dist/assets/{arrow-up-right-Dt35aRs9.js → arrow-up-right-CZZ-UM4M.js} +1 -1
  59. package/dashboard/dist/assets/check-DZawOdAk.js +1 -0
  60. package/dashboard/dist/assets/{copy-BdlX1Dcz.js → copy-BGaQZ7P5.js} +1 -1
  61. package/dashboard/dist/assets/{download-C4750cH0.js → download-BKHLHuIT.js} +1 -1
  62. package/dashboard/dist/assets/{flame-cYIuY1Z6.js → flame-sZg_y2Ct.js} +1 -1
  63. package/dashboard/dist/assets/{format-tokens-Ci6ia4_b.js → format-tokens-Ck8WH-5G.js} +1 -1
  64. package/dashboard/dist/assets/{info-Hy8GSxzK.js → info-wPFCesi6.js} +1 -1
  65. package/dashboard/dist/assets/{link-2-CHfhV9M_.js → link-2-u9AJ19mI.js} +1 -1
  66. package/dashboard/dist/assets/{loader-circle-OYZpObE-.js → loader-circle-B_RhUksQ.js} +1 -1
  67. package/dashboard/dist/assets/main-KK0yAXww.css +1 -0
  68. package/dashboard/dist/assets/{main--YQ6HWXP.js → main-rco_Tu2D.js} +23 -17
  69. package/dashboard/dist/assets/{trash-2-DFV8X7qB.js → trash-2-BWfZEWVm.js} +1 -1
  70. package/dashboard/dist/assets/{use-community-stats-BzlENlsN.js → use-community-stats-C_l8jtn9.js} +1 -1
  71. package/dashboard/dist/assets/{use-limits-display-prefs-Bgxvo849.js → use-limits-display-prefs-CDMXqVgA.js} +1 -1
  72. package/dashboard/dist/assets/{use-native-settings-Cz5XgWcy.js → use-native-settings-Bgi3a5uc.js} +1 -1
  73. package/dashboard/dist/assets/{use-ordered-list-N591-3J4.js → use-ordered-list-DbLNv_Fs.js} +1 -1
  74. package/dashboard/dist/assets/{use-quality-per-dollar-pref-CW332Qtp.js → use-quality-per-dollar-pref-ByfXALmd.js} +1 -1
  75. package/dashboard/dist/assets/{use-transform-BNrGbsUe.js → use-transform-h2Jds0kA.js} +1 -1
  76. package/dashboard/dist/assets/{use-usage-limits-BL1n1H9j.js → use-usage-limits-Dg7aMrVT.js} +1 -1
  77. package/dashboard/dist/assets/{useCurrency-B5QegAgS.js → useCurrency-x0Nv5rZP.js} +1 -1
  78. package/dashboard/dist/assets/{useScrollLock-CcorrDyq.js → useScrollLock-DGfYuwgo.js} +1 -1
  79. package/dashboard/dist/assets/{zap-C_DbBJ3s.js → zap-BQ6Eilqi.js} +1 -1
  80. package/dashboard/dist/index.html +2 -2
  81. package/dashboard/dist/share.html +2 -2
  82. package/package.json +1 -1
  83. package/src/commands/init.js +9 -4
  84. package/src/lib/pricing/seed-snapshot.json +1 -1
  85. package/src/lib/rollout.js +386 -35
  86. package/dashboard/dist/assets/AchievementBadge-BxETKM1a.js +0 -1
  87. package/dashboard/dist/assets/AchievementsPage-BnsI7sbt.js +0 -2
  88. package/dashboard/dist/assets/AchievementsSection-BjKtfATU.js +0 -1
  89. package/dashboard/dist/assets/DashboardPage-C5nQV_pg.js +0 -19
  90. package/dashboard/dist/assets/DialogDescription-CD7W4GGZ.js +0 -1
  91. package/dashboard/dist/assets/LeaderboardPage-CNOPBahO.js +0 -2
  92. package/dashboard/dist/assets/check-B2kgoEGi.js +0 -1
  93. package/dashboard/dist/assets/main-BY13exWE.css +0 -1
@@ -960,15 +960,26 @@ async function parseOpenclawIncremental({
960
960
  const cb = typeof onProgress === "function" ? onProgress : null;
961
961
  const files = Array.isArray(sessionFiles) ? sessionFiles : [];
962
962
  const totalFiles = files.length;
963
- const hourlyState = normalizeHourlyState(cursors?.hourly);
963
+ const hourlyState = normalizeHourlyState(
964
+ cursors?.hourly ? structuredClone(cursors.hourly) : null,
965
+ );
964
966
  const projectEnabled = typeof projectQueuePath === "string" && projectQueuePath.length > 0;
965
- const projectState = projectEnabled ? normalizeProjectState(cursors?.projectHourly) : null;
967
+ const projectState = projectEnabled
968
+ ? normalizeProjectState(
969
+ cursors?.projectHourly ? structuredClone(cursors.projectHourly) : null,
970
+ )
971
+ : null;
966
972
  const projectTouchedBuckets = projectEnabled ? new Set() : null;
967
973
  const touchedBuckets = new Set();
968
974
  const defaultSource = normalizeSourceInput(source) || "openclaw";
969
975
 
970
- if (!cursors.files || typeof cursors.files !== "object") {
971
- cursors.files = {};
976
+ const stagedFiles =
977
+ cursors.files && typeof cursors.files === "object"
978
+ ? { ...cursors.files }
979
+ : {};
980
+ const cursorKeys = new Map();
981
+ for (const existingKey of Object.keys(stagedFiles)) {
982
+ cursorKeys.set(openclawCursorKey(existingKey), existingKey);
972
983
  }
973
984
 
974
985
  for (let idx = 0; idx < files.length; idx++) {
@@ -979,32 +990,129 @@ async function parseOpenclawIncremental({
979
990
  typeof entry === "string"
980
991
  ? defaultSource
981
992
  : normalizeSourceInput(entry?.source) || defaultSource;
982
- const st = await fs.stat(filePath).catch(() => null);
983
- if (!st || !st.isFile()) continue;
984
-
985
- const key = filePath;
986
- const prev = cursors.files[key] || null;
987
- const inode = st.ino || 0;
988
- const startOffset = prev && prev.inode === inode ? prev.offset || 0 : 0;
993
+ const key = openclawCursorKey(filePath);
994
+ const previousKey = cursorKeys.get(key);
995
+ const prev = previousKey ? stagedFiles[previousKey] : null;
996
+ const prevUsageEvents =
997
+ prev?.usageEvents && typeof prev.usageEvents === "object" ? prev.usageEvents : {};
998
+ const hasUsageEventCursor =
999
+ prev &&
1000
+ Object.prototype.hasOwnProperty.call(prev, "usageEvents") &&
1001
+ prev.usageEvents &&
1002
+ typeof prev.usageEvents === "object";
1003
+ const legacyCursor = prev && !hasUsageEventCursor;
1004
+ const previousOffset = Number(prev?.offset || 0);
1005
+ let accepted = null;
1006
+ let openedRegularFile = false;
1007
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1008
+ const fileHandle = await fs.open(filePath, "r").catch(() => null);
1009
+ if (!fileHandle) break;
1010
+ try {
1011
+ const openedStat = await fileHandle.stat();
1012
+ if (!openedStat.isFile()) break;
1013
+ openedRegularFile = true;
1014
+ const fileEndOffset = openedStat.size;
1015
+ let prefixFingerprint = null;
1016
+ let prefixFingerprintState = null;
1017
+ const offsetBoundaryMatches =
1018
+ hasUsageEventCursor &&
1019
+ previousOffset <= fileEndOffset &&
1020
+ await openclawOffsetEndsAtLineBoundary(fileHandle, previousOffset);
1021
+ const parsedLegacyUpdatedAt = Date.parse(String(prev?.updatedAt || ""));
1022
+ const legacyAggregateAfterMs =
1023
+ legacyCursor
1024
+ ? Number.isFinite(parsedLegacyUpdatedAt)
1025
+ ? parsedLegacyUpdatedAt
1026
+ : Number.POSITIVE_INFINITY
1027
+ : null;
1028
+ if (
1029
+ hasUsageEventCursor &&
1030
+ offsetBoundaryMatches &&
1031
+ prev?.usageFingerprint
1032
+ ) {
1033
+ const prefix = await parseOpenclawSessionFile({
1034
+ filePath,
1035
+ fileHandle,
1036
+ startOffset: 0,
1037
+ endOffsetLimit: previousOffset,
1038
+ previousUsageEvents: {},
1039
+ appendOnly: false,
1040
+ collectOnly: true,
1041
+ });
1042
+ prefixFingerprint = prefix.usageFingerprint;
1043
+ prefixFingerprintState = prefix.usageFingerprintState;
1044
+ }
1045
+ const appendOnly =
1046
+ hasUsageEventCursor &&
1047
+ previousOffset <= fileEndOffset &&
1048
+ offsetBoundaryMatches &&
1049
+ sameOpenclawUsageFingerprint(
1050
+ prefixFingerprint,
1051
+ prev.usageFingerprint,
1052
+ );
1053
+ const startOffset = appendOnly ? previousOffset : 0;
1054
+ const attemptHourly = { version: 3, buckets: {}, groupQueued: {} };
1055
+ const attemptTouched = new Set();
1056
+ const result = await parseOpenclawSessionFile({
1057
+ filePath,
1058
+ fileHandle,
1059
+ startOffset,
1060
+ endOffsetLimit: fileEndOffset,
1061
+ previousUsageEvents: prevUsageEvents,
1062
+ appendOnly,
1063
+ aggregateAfterMs: legacyAggregateAfterMs,
1064
+ usageFingerprintState: appendOnly ? prefixFingerprintState : null,
1065
+ hourlyState: attemptHourly,
1066
+ touchedBuckets: attemptTouched,
1067
+ source: fileSource,
1068
+ projectState: null,
1069
+ projectTouchedBuckets: null,
1070
+ });
1071
+ const closedStat = await fileHandle.stat();
1072
+ const pathStat = await fs.stat(filePath).catch(() => null);
1073
+ if (
1074
+ !sameOpenclawFileGeneration(openedStat, closedStat) ||
1075
+ !sameOpenclawFileGeneration(closedStat, pathStat)
1076
+ ) {
1077
+ continue;
1078
+ }
1079
+ accepted = {
1080
+ inode: closedStat.ino || 0,
1081
+ result,
1082
+ hourly: attemptHourly,
1083
+ touched: attemptTouched,
1084
+ usageEvents: result.usageEvents,
1085
+ usageFingerprint: result.usageFingerprint,
1086
+ };
1087
+ break;
1088
+ } finally {
1089
+ await fileHandle.close().catch(() => {});
1090
+ }
1091
+ }
1092
+ if (!accepted) {
1093
+ if (!openedRegularFile) continue;
1094
+ throw new Error(`OpenClaw session changed repeatedly while parsing: ${filePath}`);
1095
+ }
989
1096
 
990
- const result = await parseOpenclawSessionFile({
991
- filePath,
992
- startOffset,
1097
+ mergeOpenclawAttemptBuckets(
993
1098
  hourlyState,
994
1099
  touchedBuckets,
995
- source: fileSource,
996
- projectState,
997
- projectTouchedBuckets,
998
- });
999
-
1000
- cursors.files[key] = {
1001
- inode,
1002
- offset: result.endOffset,
1100
+ accepted.hourly,
1101
+ accepted.touched,
1102
+ );
1103
+ if (previousKey && previousKey !== key) delete stagedFiles[previousKey];
1104
+ stagedFiles[key] = {
1105
+ provider: "openclaw",
1106
+ inode: accepted.inode,
1107
+ offset: accepted.result.endOffset,
1108
+ usageFingerprint: accepted.usageFingerprint,
1003
1109
  updatedAt: new Date().toISOString(),
1110
+ usageEvents: accepted.usageEvents,
1004
1111
  };
1112
+ cursorKeys.set(key, key);
1005
1113
 
1006
1114
  filesProcessed += 1;
1007
- eventsAggregated += result.eventsAggregated;
1115
+ eventsAggregated += accepted.result.eventsAggregated;
1008
1116
 
1009
1117
  if (cb) {
1010
1118
  cb({
@@ -1022,6 +1130,7 @@ async function parseOpenclawIncremental({
1022
1130
  const projectBucketsQueued = projectEnabled
1023
1131
  ? await enqueueTouchedProjectBuckets({ projectQueuePath, projectState, projectTouchedBuckets })
1024
1132
  : 0;
1133
+ cursors.files = stagedFiles;
1025
1134
  hourlyState.updatedAt = new Date().toISOString();
1026
1135
  cursors.hourly = hourlyState;
1027
1136
  if (projectState) {
@@ -1032,23 +1141,131 @@ async function parseOpenclawIncremental({
1032
1141
  return { filesProcessed, eventsAggregated, bucketsQueued, projectBucketsQueued };
1033
1142
  }
1034
1143
 
1144
+ function openclawCursorKey(filePath) {
1145
+ if (typeof filePath !== "string") return "";
1146
+ const normalized = filePath.replace(/\\/g, "/");
1147
+ const isWindowsPath =
1148
+ /^[a-zA-Z]:\//.test(normalized) || normalized.startsWith("//");
1149
+ return isWindowsPath ? normalized.toLowerCase() : filePath;
1150
+ }
1151
+
1152
+ function sameOpenclawFileGeneration(left, right) {
1153
+ if (!left || !right || !left.isFile?.() || !right.isFile?.()) return false;
1154
+ const leftInode = Number(left.ino || 0);
1155
+ const rightInode = Number(right.ino || 0);
1156
+ if (leftInode > 0 && rightInode > 0) {
1157
+ if (leftInode !== rightInode || Number(left.dev || 0) !== Number(right.dev || 0)) {
1158
+ return false;
1159
+ }
1160
+ }
1161
+ return (
1162
+ Number(left.size || 0) === Number(right.size || 0) &&
1163
+ Number(left.mtimeMs || 0) === Number(right.mtimeMs || 0) &&
1164
+ Number(left.ctimeMs || 0) === Number(right.ctimeMs || 0)
1165
+ );
1166
+ }
1167
+
1168
+ async function openclawOffsetEndsAtLineBoundary(fileHandle, offset) {
1169
+ const byteOffset = Math.max(0, Number(offset) || 0);
1170
+ if (byteOffset === 0) return true;
1171
+ const buffer = Buffer.allocUnsafe(1);
1172
+ const { bytesRead } = await fileHandle.read(buffer, 0, 1, byteOffset - 1);
1173
+ return bytesRead === 1 && (buffer[0] === 0x0a || buffer[0] === 0x0d);
1174
+ }
1175
+
1176
+ function sameOpenclawUsageFingerprint(left, right) {
1177
+ return (
1178
+ left?.version === 1 &&
1179
+ right?.version === 1 &&
1180
+ left.eventCount === right.eventCount &&
1181
+ left.digest === right.digest
1182
+ );
1183
+ }
1184
+
1185
+ function mergeOpenclawAttemptBuckets(
1186
+ hourlyState,
1187
+ touchedBuckets,
1188
+ attemptHourly,
1189
+ attemptTouched,
1190
+ ) {
1191
+ for (const key of attemptTouched) {
1192
+ const parsed = parseBucketKey(key);
1193
+ const attemptBucket = attemptHourly.buckets[key];
1194
+ if (!parsed || !attemptBucket) continue;
1195
+ const bucket = getHourlyBucket(
1196
+ hourlyState,
1197
+ parsed.source,
1198
+ parsed.model,
1199
+ parsed.hourStart,
1200
+ );
1201
+ addTotals(bucket.totals, attemptBucket.totals);
1202
+ touchedBuckets.add(key);
1203
+ }
1204
+ }
1205
+
1035
1206
  async function parseOpenclawSessionFile({
1036
1207
  filePath,
1208
+ fileHandle,
1037
1209
  startOffset,
1210
+ endOffsetLimit,
1211
+ previousUsageEvents,
1212
+ appendOnly,
1213
+ aggregateAfterMs,
1214
+ usageFingerprintState,
1215
+ collectOnly = false,
1038
1216
  hourlyState,
1039
1217
  touchedBuckets,
1040
1218
  source,
1041
1219
  projectState,
1042
1220
  projectTouchedBuckets,
1043
1221
  }) {
1044
- const st = await fs.stat(filePath);
1045
- const endOffset = st.size;
1046
- if (startOffset >= endOffset) return { endOffset, eventsAggregated: 0 };
1222
+ const st = fileHandle ? await fileHandle.stat() : await fs.stat(filePath);
1223
+ const endOffset =
1224
+ Number.isFinite(endOffsetLimit) && endOffsetLimit >= 0
1225
+ ? Math.min(st.size, endOffsetLimit)
1226
+ : st.size;
1227
+ const priorCounts =
1228
+ previousUsageEvents && typeof previousUsageEvents === "object"
1229
+ ? previousUsageEvents
1230
+ : {};
1231
+ let fingerprintEventCount =
1232
+ Number.isInteger(usageFingerprintState?.eventCount) &&
1233
+ usageFingerprintState.eventCount >= 0
1234
+ ? usageFingerprintState.eventCount
1235
+ : 0;
1236
+ const fingerprintHash =
1237
+ typeof usageFingerprintState?.hash?.copy === "function"
1238
+ ? usageFingerprintState.hash.copy()
1239
+ : crypto.createHash("sha256");
1240
+ if (startOffset >= endOffset) {
1241
+ return {
1242
+ endOffset,
1243
+ eventsAggregated: 0,
1244
+ usageEvents: { ...priorCounts },
1245
+ usageFingerprint: {
1246
+ version: 1,
1247
+ eventCount: fingerprintEventCount,
1248
+ digest: fingerprintHash.copy().digest("hex"),
1249
+ },
1250
+ usageFingerprintState: {
1251
+ eventCount: fingerprintEventCount,
1252
+ hash: fingerprintHash,
1253
+ },
1254
+ };
1255
+ }
1047
1256
 
1048
- const stream = fssync.createReadStream(filePath, { encoding: "utf8", start: startOffset });
1257
+ const stream = fssync.createReadStream(filePath, {
1258
+ fd: fileHandle?.fd,
1259
+ autoClose: !fileHandle,
1260
+ encoding: "utf8",
1261
+ start: startOffset,
1262
+ end: endOffset - 1,
1263
+ });
1049
1264
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
1050
1265
 
1051
1266
  let eventsAggregated = 0;
1267
+ const scanCounts = appendOnly ? { ...priorCounts } : {};
1268
+ const usageEvents = { ...priorCounts };
1052
1269
  for await (const line of rl) {
1053
1270
  if (!line) continue;
1054
1271
  // Fast-path filter: OpenClaw assistant messages include message.usage.totalTokens.
@@ -1065,13 +1282,26 @@ async function parseOpenclawSessionFile({
1065
1282
  const msg = obj?.message;
1066
1283
  if (!msg || typeof msg !== "object") continue;
1067
1284
 
1068
- const usage = msg.usage;
1069
- if (!usage || typeof usage !== "object") continue;
1285
+ const usage = normalizeOpenclawUsage(msg.usage);
1286
+ if (!usage) continue;
1070
1287
 
1071
1288
  const tokenTimestamp = typeof obj?.timestamp === "string" ? obj.timestamp : null;
1072
1289
  if (!tokenTimestamp) continue;
1073
1290
 
1074
1291
  const model = normalizeModelInput(msg.model) || DEFAULT_MODEL;
1292
+ const eventIdentity = openclawUsageEventIdentity(obj, msg, usage, model);
1293
+ fingerprintHash.update(
1294
+ `${JSON.stringify(
1295
+ openclawUsageFingerprintMetadata(
1296
+ obj,
1297
+ msg,
1298
+ usage,
1299
+ model,
1300
+ eventIdentity.key,
1301
+ ),
1302
+ )}\n`,
1303
+ );
1304
+ fingerprintEventCount += 1;
1075
1305
 
1076
1306
  // OpenClaw wraps Codex, so it follows the same OpenAI convention where
1077
1307
  // `input` INCLUDES cached reads. Normalize by subtracting cached from
@@ -1098,18 +1328,138 @@ async function parseOpenclawSessionFile({
1098
1328
  const bucketStart = toUtcHalfHourStart(tokenTimestamp);
1099
1329
  if (!bucketStart) continue;
1100
1330
 
1101
- const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
1102
- addTotals(bucket.totals, delta);
1103
- touchedBuckets.add(bucketKey(source, model, bucketStart));
1331
+ const priorCount = Number(priorCounts[eventIdentity.key] || 0);
1332
+ const nextCount = Number(scanCounts[eventIdentity.key] || 0) + 1;
1333
+ scanCounts[eventIdentity.key] = nextCount;
1334
+ usageEvents[eventIdentity.key] = eventIdentity.unique
1335
+ ? 1
1336
+ : Math.max(Number(usageEvents[eventIdentity.key] || 0), nextCount);
1337
+ if (
1338
+ eventIdentity.unique
1339
+ ? priorCount > 0 || nextCount > 1
1340
+ : nextCount <= priorCount
1341
+ ) {
1342
+ continue;
1343
+ }
1344
+
1345
+ if (
1346
+ aggregateAfterMs != null &&
1347
+ Date.parse(tokenTimestamp) <= aggregateAfterMs
1348
+ ) {
1349
+ continue;
1350
+ }
1351
+
1352
+ if (!collectOnly) {
1353
+ const bucket = getHourlyBucket(hourlyState, source, model, bucketStart);
1354
+ addTotals(bucket.totals, delta);
1355
+ touchedBuckets.add(bucketKey(source, model, bucketStart));
1356
+ }
1104
1357
 
1105
1358
  // Project-level OpenClaw attribution is not supported yet (no stable cwd info).
1106
1359
  // If OpenClaw later records cwd per event, we can mirror rollout's project logic.
1107
- eventsAggregated += 1;
1360
+ if (!collectOnly) eventsAggregated += 1;
1108
1361
  }
1109
1362
 
1110
1363
  rl.close();
1111
- stream.close?.();
1112
- return { endOffset, eventsAggregated };
1364
+ if (!fileHandle) stream.close?.();
1365
+ return {
1366
+ endOffset,
1367
+ eventsAggregated,
1368
+ usageEvents,
1369
+ usageFingerprint: {
1370
+ version: 1,
1371
+ eventCount: fingerprintEventCount,
1372
+ digest: fingerprintHash.copy().digest("hex"),
1373
+ },
1374
+ usageFingerprintState: {
1375
+ eventCount: fingerprintEventCount,
1376
+ hash: fingerprintHash,
1377
+ },
1378
+ };
1379
+ }
1380
+
1381
+ function normalizeOpenclawUsage(usage) {
1382
+ if (!usage || typeof usage !== "object") return null;
1383
+ const normalized = {};
1384
+ for (const field of [
1385
+ "input",
1386
+ "cacheRead",
1387
+ "cacheWrite",
1388
+ "output",
1389
+ "totalTokens",
1390
+ ]) {
1391
+ const rawValue = usage[field];
1392
+ if (rawValue === undefined) {
1393
+ normalized[field] = 0;
1394
+ continue;
1395
+ }
1396
+ if (
1397
+ typeof rawValue !== "number" ||
1398
+ !Number.isSafeInteger(rawValue) ||
1399
+ rawValue < 0
1400
+ ) {
1401
+ return null;
1402
+ }
1403
+ normalized[field] = rawValue;
1404
+ }
1405
+ return normalized;
1406
+ }
1407
+
1408
+ function openclawUsageFingerprintMetadata(
1409
+ obj,
1410
+ msg,
1411
+ usage,
1412
+ model,
1413
+ identityKey,
1414
+ ) {
1415
+ return {
1416
+ identityKey,
1417
+ timestamp: typeof obj?.timestamp === "string" ? obj.timestamp : null,
1418
+ messageTimestamp:
1419
+ typeof msg?.timestamp === "number" || typeof msg?.timestamp === "string"
1420
+ ? msg.timestamp
1421
+ : null,
1422
+ responseId: typeof msg?.responseId === "string" ? msg.responseId : null,
1423
+ model,
1424
+ provider: typeof msg?.provider === "string" ? msg.provider : null,
1425
+ api: typeof msg?.api === "string" ? msg.api : null,
1426
+ input: Number(usage?.input || 0),
1427
+ cacheRead: Number(usage?.cacheRead || 0),
1428
+ cacheWrite: Number(usage?.cacheWrite || 0),
1429
+ output: Number(usage?.output || 0),
1430
+ totalTokens: Number(usage?.totalTokens || 0),
1431
+ };
1432
+ }
1433
+
1434
+ function openclawUsageEventIdentity(obj, msg, usage, model) {
1435
+ const stableId =
1436
+ typeof obj?.id === "string" && obj.id.trim() ? obj.id.trim() : null;
1437
+ if (stableId) {
1438
+ return {
1439
+ key: `id:${crypto.createHash("sha256").update(stableId).digest("hex")}`,
1440
+ unique: true,
1441
+ };
1442
+ }
1443
+
1444
+ const metadata = {
1445
+ timestamp: typeof obj?.timestamp === "string" ? obj.timestamp : null,
1446
+ messageTimestamp: typeof msg?.timestamp === "number" || typeof msg?.timestamp === "string"
1447
+ ? msg.timestamp
1448
+ : null,
1449
+ responseId: typeof msg?.responseId === "string" ? msg.responseId : null,
1450
+ model,
1451
+ provider: typeof msg?.provider === "string" ? msg.provider : null,
1452
+ api: typeof msg?.api === "string" ? msg.api : null,
1453
+ input: Number(usage?.input || 0),
1454
+ cacheRead: Number(usage?.cacheRead || 0),
1455
+ cacheWrite: Number(usage?.cacheWrite || 0),
1456
+ output: Number(usage?.output || 0),
1457
+ totalTokens: Number(usage?.totalTokens || 0),
1458
+ };
1459
+ return {
1460
+ key: `meta:${crypto.createHash("sha256").update(JSON.stringify(metadata)).digest("hex")}`,
1461
+ unique: false,
1462
+ };
1113
1463
  }
1114
1464
 
1115
1465
  /**
@@ -12151,6 +12501,7 @@ module.exports = {
12151
12501
  parseGeminiIncremental,
12152
12502
  parseOpencodeIncremental,
12153
12503
  parseOpencodeDbIncremental,
12504
+ openclawCursorKey,
12154
12505
  parseOpenclawIncremental,
12155
12506
  parseCursorApiIncremental,
12156
12507
  parseKiroIncremental,
@@ -1 +0,0 @@
1
- import{c as o,R as A,j as i}from"./main--YQ6HWXP.js";import{Z as N}from"./zap-C_DbBJ3s.js";import{F as $}from"./flame-cYIuY1Z6.js";const E=[["path",{d:"M10 22V7a1 1 0 0 0-1-1H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5a1 1 0 0 0-1-1H2",key:"1ah6g2"}],["rect",{x:"14",y:"2",width:"8",height:"8",rx:"1",key:"88lufb"}]],j=o("blocks",E);const L=[["path",{d:"M12 18V5",key:"adv99a"}],["path",{d:"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4",key:"1e3is1"}],["path",{d:"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5",key:"1gqd8o"}],["path",{d:"M17.997 5.125a4 4 0 0 1 2.526 5.77",key:"iwvgf7"}],["path",{d:"M18 18a4 4 0 0 0 2-7.464",key:"efp6ie"}],["path",{d:"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517",key:"1gq6am"}],["path",{d:"M6 18a4 4 0 0 1-2-7.464",key:"k1g0md"}],["path",{d:"M6.003 5.125a4 4 0 0 0-2.526 5.77",key:"q97ue3"}]],T=o("brain",L);const C=[["path",{d:"M11.562 3.266a.5.5 0 0 1 .876 0L15.39 8.87a1 1 0 0 0 1.516.294L21.183 5.5a.5.5 0 0 1 .798.519l-2.834 10.246a1 1 0 0 1-.956.734H5.81a1 1 0 0 1-.957-.734L2.02 6.02a.5.5 0 0 1 .798-.519l4.276 3.664a1 1 0 0 0 1.516-.294z",key:"1vdc57"}],["path",{d:"M5 21h14",key:"11awu3"}]],B=o("crown",C);const R=[["path",{d:"M18 19a5 5 0 0 1-5-5v8",key:"sz5oeg"}],["path",{d:"M9 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v5",key:"1w6njk"}],["circle",{cx:"13",cy:"12",r:"2",key:"1j92g6"}],["circle",{cx:"20",cy:"19",r:"2",key:"1obnsp"}]],q=o("folder-git-2",R);const I=[["path",{d:"M4 16v-2.38C4 11.5 2.97 10.5 3 8c.03-2.72 1.49-6 4.5-6C9.37 2 10 3.8 10 5.5c0 3.11-2 5.66-2 8.68V16a2 2 0 1 1-4 0Z",key:"1dudjm"}],["path",{d:"M20 20v-2.38c0-2.12 1.03-3.12 1-5.62-.03-2.72-1.49-6-4.5-6C14.63 6 14 7.8 14 9.5c0 3.11 2 5.66 2 8.68V20a2 2 0 1 0 4 0Z",key:"l2t8xc"}],["path",{d:"M16 17h4",key:"1dejxt"}],["path",{d:"M4 13h4",key:"1bwh8b"}]],z=o("footprints",I);const D=[["path",{d:"M2 9.5a5.5 5.5 0 0 1 9.591-3.676.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5",key:"mvr1a0"}]],F=o("heart",D);const G=[["path",{d:"M7.21 15 2.66 7.14a2 2 0 0 1 .13-2.2L4.4 2.8A2 2 0 0 1 6 2h12a2 2 0 0 1 1.6.8l1.6 2.14a2 2 0 0 1 .14 2.2L16.79 15",key:"143lza"}],["path",{d:"M11 12 5.12 2.2",key:"qhuxz6"}],["path",{d:"m13 12 5.88-9.8",key:"hbye0f"}],["path",{d:"M8 7h8",key:"i86dvs"}],["circle",{cx:"12",cy:"17",r:"5",key:"qbz8iq"}],["path",{d:"M12 18v-2h-.5",key:"fawc4q"}]],H=o("medal",G);const S=[["path",{d:"M18 5h4",key:"1lhgn2"}],["path",{d:"M20 3v4",key:"1olli1"}],["path",{d:"M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401",key:"kfwtm"}]],V=o("moon-star",S);const Z=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],O=o("shield-check",Z);const P=[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]],X=o("trending-up",P),y=[{id:"token_titan",scope:"cloud",icon:B,format:"tokens",art:"/achievements/galaxy-brain.png"},{id:"big_day",scope:"cloud",icon:N,format:"tokens",art:"/achievements/quickdraw.png"},{id:"marathoner",scope:"cloud",icon:z,format:"days",art:"/achievements/pull-shark.png"},{id:"streak",scope:"cloud",icon:$,format:"days",art:"/achievements/heart-on-your-sleeve.png"},{id:"momentum",scope:"cloud",icon:X,format:"multiplier",art:"/achievements/proxima-staffshipper.png"},{id:"polyglot",scope:"cloud",icon:T,format:"count",art:"/achievements/open-sourcerer.png"},{id:"multitool",scope:"cloud",icon:j,format:"count",art:"/achievements/pair-extraordinaire.png"},{id:"podium",scope:"cloud",icon:H,format:"rank",art:"/achievements/starstruck.png"},{id:"veteran",scope:"cloud",icon:O,format:"days",art:"/achievements/arctic-code-vault-contributor.png"},{id:"project_hopper",scope:"local",icon:q,format:"count",art:"/achievements/proxima-staffuser.png"},{id:"project_devotion",scope:"local",icon:F,format:"tokens",art:"/achievements/public-sponsor.png"},{id:"night_owl",scope:"local",icon:V,format:"count",art:"/achievements/proxima-pioneer.png"}],k=new Map(y.map(e=>[e.id,e])),m=new Map(y.map((e,a)=>[e.id,a]));function ee(e,a){return`achievements.badge.${e}.${a}`}function K(e){return[...e||[]].sort((a,t)=>{const c=(t?.tier||0)-(a?.tier||0);return c!==0?c:(m.get(a?.id)??99)-(m.get(t?.id)??99)})}function ae(e){const a=(e||[]).filter(t=>t&&(t.tier||0)>=1&&k.has(t.id));return a.length===0?null:K(a)[0]}const b={1:"bronze",2:"silver",3:"gold",4:"diamond"},te=["bronze","silver","gold","diamond"],U={bronze:{rim:["#a06a44","#5c3a22"],face:["#d29a6c","#96602f"],glyph:"#3f2817",ring:"#e8c3a0",sheen:"rgba(255, 236, 220, 0.45)",label:"#a06a44"},silver:{rim:["#aab3bb","#67707b"],face:["#dde2e7","#9ba5ae"],glyph:"#39424b",ring:"#f2f5f7",sheen:"rgba(255, 255, 255, 0.55)",label:"#8b959f"},gold:{rim:["#c9971c","#8a6508"],face:["#f2ca52","#cf9c26"],glyph:"#5c430e",ring:"#ffe9a8",sheen:"rgba(255, 248, 214, 0.55)",label:"#b8860b"},diamond:{rim:["#62b1e0","#7c6fd9"],face:["#c8ecf6","#84b4ea"],glyph:"#27496d",ring:"#eafcff",sheen:"rgba(255, 255, 255, 0.65)",label:"#5a9bd8"}},f={rim:["#a8ada9","#7c817d"],face:["#cdd2ce","#a3a8a4"],glyph:"#6a706c",ring:"#dde1de",sheen:"rgba(255, 255, 255, 0.25)",label:"#8b908c"};function W(e,a=!1){return a||!e||e<1?f:U[b[Math.min(e,4)]]||f}function oe(e){return b[e]||null}const g={xs:18,sm:24,md:44,xl:80,lg:96},u={xs:1.5,sm:2,md:3,xl:4,lg:5};function ne({badgeId:e,tier:a=0,size:t="md",locked:c=!1,label:d,className:v=""}){const s=k.get(e),p=c||!a||a<1,n=W(a,p),r=t==="fill",l=r?"100%":g[t]||g.md,_=r?6:u[t]||u.md,[M,x]=A.useState(!1),h=s?.icon||null,w=!!s?.art&&!M;return i.jsx("span",{role:d?"img":void 0,"aria-label":d||void 0,"aria-hidden":d?void 0:!0,className:`inline-flex shrink-0 rounded-full ${v}`,style:{width:l,height:l,padding:_,background:`linear-gradient(135deg, ${n.rim[0]} 0%, ${n.ring} 42%, ${n.rim[1]} 100%)`,boxShadow:r||t==="md"||t==="xl"||t==="lg"?"0 0 0 1px rgba(10, 15, 12, 0.06), 0 1px 2px rgba(10, 15, 12, 0.08)":void 0},children:w?i.jsx("img",{src:s.art,alt:"",draggable:!1,loading:"lazy",onError:()=>x(!0),className:"h-full w-full rounded-full object-cover",style:p?{filter:"grayscale(1)",opacity:.55}:void 0}):i.jsx("span",{className:"flex h-full w-full items-center justify-center rounded-full",style:{background:`linear-gradient(145deg, ${n.face[0]}, ${n.face[1]})`},children:h?i.jsx(h,{size:r?64:Math.round(l*.5),color:n.glyph,strokeWidth:2}):null})})}export{ne as A,y as B,F as H,U as T,k as a,ee as b,te as c,ae as h,W as p,K as s,oe as t};
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/BadgeDetailModal-BMNUpuro.js","assets/main--YQ6HWXP.js","assets/main-BY13exWE.css","assets/AchievementBadge-BxETKM1a.js","assets/zap-C_DbBJ3s.js","assets/flame-cYIuY1Z6.js","assets/use-transform-BNrGbsUe.js","assets/AchievementsSection-BjKtfATU.js","assets/format-tokens-Ci6ia4_b.js","assets/DialogTitle-BlZXQboq.js","assets/useScrollLock-CcorrDyq.js"])))=>i.map(i=>d[i]);
2
- import{r as o,V as x,ak as h,U as f,j as e,E as l,L as p,R as m,_ as v,al as y,a4 as b}from"./main--YQ6HWXP.js";import{A as j}from"./AchievementsSection-BjKtfATU.js";import{B as g}from"./AchievementBadge-BxETKM1a.js";import"./format-tokens-Ci6ia4_b.js";import"./zap-C_DbBJ3s.js";import"./flame-cYIuY1Z6.js";function k({enabled:t=!0}={}){const[i,s]=o.useState({status:"loading",achievements:[],error:null}),n=o.useMemo(()=>x(),[]);return o.useEffect(()=>{if(!t){s({status:"disabled",achievements:[],error:null});return}let r=!1;return s(a=>({...a,status:"loading"})),h({timeZone:n,tzOffsetMinutes:f()}).then(a=>{r||s({status:"ready",achievements:Array.isArray(a?.achievements)?a.achievements:[],error:null})}).catch(a=>{r||s({status:"error",achievements:[],error:a})}),()=>{r=!0}},[t,n]),i}const w=m.lazy(()=>b(()=>import("./BadgeDetailModal-BMNUpuro.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10])));function N(){const t=v(),[i,s]=o.useState({status:"loading",achievements:[]}),n=!!(t?.enabled&&t?.user?.id);return o.useEffect(()=>{if(!n){s({status:"signed-out",achievements:[]});return}let r=!1;return(async()=>{try{const a=await t.getAccessToken?.(),c=await y({accessToken:a,userId:t.user.id,period:"total"});if(r)return;s({status:"ready",achievements:Array.isArray(c?.badges)?c.badges:[]})}catch{r||s({status:"error",achievements:[]})}})(),()=>{r=!0}},[n,t?.user?.id]),{...i,signedIn:n}}function A(){return e.jsx("div",{className:"-mx-1.5 grid animate-pulse grid-cols-[repeat(3,max-content)] justify-between gap-y-7 sm:-mx-3.5 sm:grid-cols-[repeat(4,max-content)] lg:grid-cols-[repeat(5,max-content)]","aria-hidden":!0,children:Array.from({length:g.length}).map((t,i)=>e.jsxs("div",{className:"flex w-[6.75rem] flex-col items-center px-1 pb-3 pt-4 sm:w-[7.75rem]",children:[e.jsx("div",{className:"h-24 w-24 rounded-full bg-oai-gray-200 dark:bg-oai-gray-800"}),e.jsx("div",{className:"mt-3 h-3 w-16 rounded bg-oai-gray-200 dark:bg-oai-gray-800"}),e.jsx("div",{className:"mt-2 h-2 w-10 rounded bg-oai-gray-100 dark:bg-oai-gray-800/60"})]},i))})}function L(){const t=k(),i=N(),[s,n]=o.useState(null),r=t.status==="loading"||i.status==="loading",a=o.useMemo(()=>[...i.achievements||[],...t.achievements||[]],[i.achievements,t.achievements]),c=o.useMemo(()=>a.filter(u=>(u?.tier||0)>=1).length,[a]),d=g.length;return e.jsxs("div",{className:"flex flex-1 flex-col font-oai text-oai-black antialiased dark:text-oai-white",children:[e.jsx("main",{className:"flex-1 pb-12 pt-8 sm:pb-16 sm:pt-10",children:e.jsxs("div",{className:"mx-auto max-w-4xl px-4 sm:px-6",children:[e.jsxs("div",{className:"mb-7 flex items-baseline justify-between gap-4",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("h1",{className:"text-3xl font-semibold tracking-tight text-oai-black dark:text-white sm:text-4xl",children:l("achievements.page.title")}),e.jsx("p",{className:"mt-1.5 text-sm text-oai-gray-500 dark:text-oai-gray-400",children:l("achievements.page.local_note")})]}),!r&&e.jsxs("div",{className:"shrink-0 text-right",children:[e.jsxs("div",{className:"font-mono text-2xl font-semibold leading-none tabular-nums text-oai-black dark:text-white",children:[c,e.jsxs("span",{className:"text-oai-gray-400 dark:text-oai-gray-600",children:["/",d]})]}),e.jsx("div",{className:"mt-2 h-1 w-24 overflow-hidden rounded-full bg-oai-gray-200 dark:bg-oai-gray-800",children:e.jsx("div",{className:"h-full rounded-full bg-oai-brand-600 transition-[width] duration-500",style:{width:`${Math.round(c/d*100)}%`}})})]})]}),!i.signedIn&&!r&&e.jsxs("div",{className:"mb-6 flex flex-wrap items-center justify-between gap-x-4 gap-y-2 rounded-xl border border-oai-gray-200 bg-oai-gray-50 px-4 py-3 dark:border-oai-gray-800 dark:bg-oai-gray-900/60",children:[e.jsx("p",{className:"text-sm text-oai-gray-600 dark:text-oai-gray-300",children:l("achievements.signin.prompt")}),e.jsx(p,{to:"/login",className:"shrink-0 rounded-lg bg-oai-brand-600 px-3 py-1.5 text-sm font-medium text-white transition-colors duration-200 hover:bg-oai-brand-700 active:scale-[0.98]",children:l("achievements.signin.action")})]}),r?e.jsx(A,{}):e.jsxs(e.Fragment,{children:[e.jsx(j,{achievements:a,isOwn:!0,columns:"wide",size:"lg",onSelect:n}),e.jsxs("p",{className:"mt-10 text-center text-xs text-oai-gray-400 dark:text-oai-gray-500",children:[l("achievements.page.suggest")," ",e.jsx("a",{href:"https://github.com/mm7894215/TokenTracker/issues",target:"_blank",rel:"noopener noreferrer",className:"underline decoration-oai-gray-300 underline-offset-2 transition-colors hover:text-oai-gray-700 hover:decoration-oai-gray-500 dark:decoration-oai-gray-600 dark:hover:text-oai-gray-300",children:l("achievements.page.suggest_link")})]})]})]})}),s&&e.jsx(m.Suspense,{fallback:null,children:e.jsx(w,{badge:s,onClose:()=>n(null)})})]})}export{L as default};
@@ -1 +0,0 @@
1
- import{c as k,j as s,E as l}from"./main--YQ6HWXP.js";import{B as f,s as g,b as j,t as w,A,T as M}from"./AchievementBadge-BxETKM1a.js";import{f as _}from"./format-tokens-Ci6ia4_b.js";const $=[["path",{d:"M10 16h.01",key:"1bzywj"}],["path",{d:"M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",key:"18tbho"}],["path",{d:"M21.946 12.013H2.054",key:"zqlbp7"}],["path",{d:"M6 16h.01",key:"1pmjb7"}]],C=k("hard-drive",$);function I(r,i){const t=Number(i)||0;switch(r){case"tokens":return _(t);case"rank":return`#${Math.round(t)}`;case"multiplier":return`${(Math.round(t*10)/10).toString()}×`;default:return Math.round(t).toLocaleString()}}function E(r){if(!r)return 0;const i=Number(r.next_threshold),t=Number(r.metric_value??r.value);if(!Number.isFinite(i)||i<=0)return r.tier>=4?1:0;if(!Number.isFinite(t)||t<=0)return 0;const n=r.lower_is_better?i/t:t/i;return Math.max(0,Math.min(1,n))}function z(r){const i=r?.achieved||{},t=Object.values(i).map(n=>n?Date.parse(n):NaN).filter(n=>Number.isFinite(n));return t.length>0?Math.max(...t):null}function D({achievements:r,isOwn:i=!1,scope:t,onSelect:n,size:p="xl",columns:x="default",className:b=""}){const u=new Map;for(const e of Array.isArray(r)?r:[])e&&e.id&&u.set(e.id,e);const h=t?f.filter(e=>e.scope===t):f;let d;if(i){const e=h.map(a=>({entry:a,record:u.get(a.id)||{id:a.id,tier:0}})),o=g(e.filter(a=>(a.record.tier||0)>=1).map(a=>({...a,id:a.entry.id,tier:a.record.tier}))),c=e.filter(a=>(a.record.tier||0)<1);d=[...o,...c]}else d=g(h.map(e=>({entry:e,record:u.get(e.id)})).filter(e=>e.record&&(e.record.tier||0)>=1).map(e=>({...e,id:e.entry.id,tier:e.record.tier}))).map(({entry:e,record:o})=>({entry:e,record:o}));if(d.length===0)return null;const v=x==="wide"?"grid-cols-[repeat(3,max-content)] sm:grid-cols-[repeat(4,max-content)] lg:grid-cols-[repeat(5,max-content)]":"grid-cols-[repeat(3,max-content)] sm:grid-cols-[repeat(4,max-content)]",y=p==="lg"?"-mx-1.5 sm:-mx-3.5":"-mx-2 sm:-mx-4";return s.jsx("div",{className:`${y} grid justify-between gap-y-7 ${v} ${b}`,children:d.map(({entry:e,record:o})=>{const c=(o.tier||0)>=1,a=l(j(e.id,"name")),m=c?w(o.tier):null,N=E(o);return s.jsxs("button",{type:"button",onClick:n?()=>n({...o,id:e.id}):void 0,className:"group flex w-[6.75rem] flex-col items-center rounded-xl px-1 pb-3 pt-4 text-center transition-[background-color,transform] duration-200 ease-out hover:bg-oai-gray-50 active:scale-[0.97] dark:hover:bg-oai-gray-900/70 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-oai-brand/60 sm:w-[7.75rem]","aria-label":l("achievements.badge.aria",{name:a,tier:m?l(`achievements.tier.${m}`):l("achievements.modal.locked")}),children:[s.jsx(A,{badgeId:e.id,tier:o.tier,locked:!c,size:p,className:"transition-transform duration-200 ease-out group-hover:-translate-y-0.5 group-hover:scale-105"}),s.jsxs("span",{className:"mt-2.5 flex w-full items-center justify-center gap-1",children:[s.jsx("span",{title:a,className:"min-w-0 truncate text-xs font-medium leading-tight text-oai-gray-800 dark:text-oai-gray-200",children:a}),e.scope==="local"&&s.jsx("span",{title:l("achievements.page.local_note"),"aria-label":l("achievements.scope.local"),className:"inline-flex shrink-0 text-oai-gray-400 dark:text-oai-gray-500",children:s.jsx(C,{size:11,strokeWidth:2,"aria-hidden":!0})})]}),s.jsx("span",{className:"mt-1 flex h-4 w-full items-center justify-center",children:c?s.jsx("span",{className:"text-[10px] font-semibold uppercase tracking-[0.08em]",style:{color:M[m]?.label},children:l(`achievements.tier.${m}`)}):i?s.jsx("span",{className:"h-[3px] w-14 overflow-hidden rounded-full bg-oai-gray-200 dark:bg-oai-gray-800",children:s.jsx("span",{className:"block h-full rounded-full bg-oai-brand/70",style:{width:`${Math.round(N*100)}%`}})}):null})]},e.id)})})}export{D as A,E as b,I as f,z as l};