tokentracker-cli 0.5.71 → 0.5.72
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.
- package/dashboard/dist/assets/{Card-5ebScQbM.js → Card-D_q1XGfK.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-B32RhUB1.js → DashboardPage-CuBSoNgI.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-CHjFUAEe.js → FadeIn-ClpHby-T.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-CrbKufId.js → IpCheckPage-B5TpHE6G.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-CVKBhGzY.js → LeaderboardPage-BOrtGeIf.js} +1 -1
- package/dashboard/dist/assets/LeaderboardProfilePage-C8xexZ08.js +1 -0
- package/dashboard/dist/assets/{LimitsPage-BwQ4isbM.js → LimitsPage-CERWQyIU.js} +1 -1
- package/dashboard/dist/assets/{SettingsPage-Y3Znzs1i.js → SettingsPage-DUwt4x-H.js} +1 -1
- package/dashboard/dist/assets/{WidgetsPage-tNojCBaj.js → WidgetsPage-Cmr2tbD7.js} +1 -1
- package/dashboard/dist/assets/{download-CQASvwEL.js → download-M8e1PJC-.js} +1 -1
- package/dashboard/dist/assets/{leaderboard-columns-DFG1TWe9.js → leaderboard-columns-Dcg9r7R2.js} +1 -1
- package/dashboard/dist/assets/{main-Cv6YpYdZ.js → main-D1VdJk4V.js} +2 -2
- package/dashboard/dist/assets/{use-limits-display-prefs-BUrHqTkA.js → use-limits-display-prefs-CSj55sfK.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-D-gv2i1R.js → use-usage-limits-BuWINUAm.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/commands/sync.js +26 -1
- package/src/lib/local-api.js +159 -56
- package/src/lib/rollout.js +45 -4
- package/dashboard/dist/assets/LeaderboardProfilePage-CbpzxvLA.js +0 -1
package/src/lib/local-api.js
CHANGED
|
@@ -116,30 +116,90 @@ function resolveQueuePath() {
|
|
|
116
116
|
return path.join(home, ".tokentracker", "tracker", "queue.jsonl");
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
function
|
|
119
|
+
function readProjectQueueData(projectQueuePath) {
|
|
120
|
+
let raw;
|
|
120
121
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
raw = fs.readFileSync(projectQueuePath, "utf8");
|
|
123
|
+
} catch (e) {
|
|
124
|
+
if (e?.code !== "ENOENT") {
|
|
125
|
+
console.error("[LocalAPI] readProjectQueueData: failed to read:", e?.message || e);
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
130
|
+
const seen = new Map();
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
try {
|
|
133
|
+
const row = JSON.parse(line);
|
|
134
|
+
const key = `${row.project_key || ""}|${row.source || ""}|${row.hour_start || ""}`;
|
|
129
135
|
seen.set(key, row);
|
|
136
|
+
} catch {
|
|
137
|
+
// skip malformed
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return Array.from(seen.values());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function readQueueData(queuePath) {
|
|
144
|
+
let raw;
|
|
145
|
+
try {
|
|
146
|
+
raw = fs.readFileSync(queuePath, "utf8");
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// ENOENT is legitimate (never synced yet); anything else is a signal we
|
|
149
|
+
// don't want to hide behind an empty array forever — the dashboard would
|
|
150
|
+
// otherwise render "0 tokens" with no clue the queue was unreadable.
|
|
151
|
+
if (e?.code !== "ENOENT") {
|
|
152
|
+
console.error("[LocalAPI] readQueueData: failed to read queue:", e?.message || e);
|
|
130
153
|
}
|
|
131
|
-
return Array.from(seen.values());
|
|
132
|
-
} catch (_e) {
|
|
133
154
|
return [];
|
|
134
155
|
}
|
|
156
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
157
|
+
// Parse row-by-row so a single corrupted line (partial write, disk-full
|
|
158
|
+
// truncation, …) does not wipe out every other row with it.
|
|
159
|
+
const parsed = [];
|
|
160
|
+
let malformed = 0;
|
|
161
|
+
for (const line of lines) {
|
|
162
|
+
try {
|
|
163
|
+
parsed.push(JSON.parse(line));
|
|
164
|
+
} catch {
|
|
165
|
+
malformed += 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (malformed > 0) {
|
|
169
|
+
console.error(
|
|
170
|
+
`[LocalAPI] readQueueData: skipped ${malformed}/${lines.length} malformed line(s) in ${queuePath}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
// Deduplicate: each sync appends cumulative totals per bucket, so for
|
|
174
|
+
// each (source, model, hour_start) keep only the latest (last) entry.
|
|
175
|
+
const seen = new Map();
|
|
176
|
+
for (const row of parsed) {
|
|
177
|
+
const key = `${row.source || ""}|${row.model || ""}|${row.hour_start || ""}`;
|
|
178
|
+
seen.set(key, row);
|
|
179
|
+
}
|
|
180
|
+
return Array.from(seen.values());
|
|
135
181
|
}
|
|
136
182
|
|
|
137
|
-
function
|
|
183
|
+
function rowDayKey(row, timeZoneContext) {
|
|
184
|
+
const hs = row.hour_start;
|
|
185
|
+
if (!hs) return "";
|
|
186
|
+
if (
|
|
187
|
+
timeZoneContext &&
|
|
188
|
+
(timeZoneContext.timeZone || Number.isFinite(timeZoneContext.offsetMinutes))
|
|
189
|
+
) {
|
|
190
|
+
const parts = getZonedParts(new Date(hs), timeZoneContext);
|
|
191
|
+
const key = formatPartsDayKey(parts);
|
|
192
|
+
if (key) return key;
|
|
193
|
+
}
|
|
194
|
+
return hs.slice(0, 10);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function aggregateByDay(rows, timeZoneContext = null) {
|
|
138
198
|
const byDay = new Map();
|
|
139
199
|
for (const row of rows) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
200
|
+
if (!row.hour_start) continue;
|
|
201
|
+
const day = rowDayKey(row, timeZoneContext);
|
|
202
|
+
if (!day) continue;
|
|
143
203
|
if (!byDay.has(day)) {
|
|
144
204
|
byDay.set(day, {
|
|
145
205
|
day,
|
|
@@ -674,8 +734,9 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
674
734
|
if (p === "/functions/tokentracker-usage-summary") {
|
|
675
735
|
const from = url.searchParams.get("from") || "";
|
|
676
736
|
const to = url.searchParams.get("to") || "";
|
|
737
|
+
const timeZoneContext = getTimeZoneContext(url);
|
|
677
738
|
const rows = readQueueData(qp);
|
|
678
|
-
const daily = aggregateByDay(rows).filter((d) => d.day >= from && d.day <= to);
|
|
739
|
+
const daily = aggregateByDay(rows, timeZoneContext).filter((d) => d.day >= from && d.day <= to);
|
|
679
740
|
const totals = daily.reduce(
|
|
680
741
|
(acc, r) => {
|
|
681
742
|
acc.total_tokens += r.total_tokens;
|
|
@@ -693,16 +754,19 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
693
754
|
);
|
|
694
755
|
const totalCost = totals.total_cost_usd;
|
|
695
756
|
|
|
696
|
-
const
|
|
697
|
-
const todayStr =
|
|
698
|
-
const allDaily = aggregateByDay(rows);
|
|
757
|
+
const todayParts = getZonedParts(new Date(), timeZoneContext);
|
|
758
|
+
const todayStr = formatPartsDayKey(todayParts) || new Date().toISOString().slice(0, 10);
|
|
759
|
+
const allDaily = aggregateByDay(rows, timeZoneContext);
|
|
699
760
|
|
|
761
|
+
const shiftDay = (dayStr, delta) => {
|
|
762
|
+
const d = new Date(`${dayStr}T00:00:00Z`);
|
|
763
|
+
d.setUTCDate(d.getUTCDate() + delta);
|
|
764
|
+
return d.toISOString().slice(0, 10);
|
|
765
|
+
};
|
|
700
766
|
const collectDays = (n) => {
|
|
701
767
|
const out = [];
|
|
702
768
|
for (let i = n - 1; i >= 0; i--) {
|
|
703
|
-
const
|
|
704
|
-
d.setUTCDate(d.getUTCDate() - i);
|
|
705
|
-
const ds = d.toISOString().slice(0, 10);
|
|
769
|
+
const ds = shiftDay(todayStr, -i);
|
|
706
770
|
const dd = allDaily.find((x) => x.day === ds);
|
|
707
771
|
if (dd) out.push(dd);
|
|
708
772
|
}
|
|
@@ -719,17 +783,15 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
719
783
|
const l30 = collectDays(30);
|
|
720
784
|
const l7t = sumDays(l7);
|
|
721
785
|
const l30t = sumDays(l30);
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
const l30from = new Date(today);
|
|
725
|
-
l30from.setUTCDate(l30from.getUTCDate() - 29);
|
|
786
|
+
const l7fromStr = shiftDay(todayStr, -6);
|
|
787
|
+
const l30fromStr = shiftDay(todayStr, -29);
|
|
726
788
|
|
|
727
789
|
json(res, {
|
|
728
790
|
from, to, days: daily.length,
|
|
729
791
|
totals: { ...totals, total_cost_usd: totalCost.toFixed(6) },
|
|
730
792
|
rolling: {
|
|
731
|
-
last_7d: { from:
|
|
732
|
-
last_30d: { from:
|
|
793
|
+
last_7d: { from: l7fromStr, to: todayStr, active_days: l7.length, totals: l7t },
|
|
794
|
+
last_30d: { from: l30fromStr, to: todayStr, active_days: l30.length, totals: l30t, avg_per_active_day: l30.length > 0 ? Math.round(l30t.billable_total_tokens / l30.length) : 0 },
|
|
733
795
|
},
|
|
734
796
|
});
|
|
735
797
|
return true;
|
|
@@ -739,8 +801,9 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
739
801
|
if (p === "/functions/tokentracker-usage-daily") {
|
|
740
802
|
const from = url.searchParams.get("from") || "";
|
|
741
803
|
const to = url.searchParams.get("to") || "";
|
|
804
|
+
const timeZoneContext = getTimeZoneContext(url);
|
|
742
805
|
const rows = readQueueData(qp);
|
|
743
|
-
const daily = aggregateByDay(rows).filter((d) => d.day >= from && d.day <= to);
|
|
806
|
+
const daily = aggregateByDay(rows, timeZoneContext).filter((d) => d.day >= from && d.day <= to);
|
|
744
807
|
json(res, { from, to, data: daily });
|
|
745
808
|
return true;
|
|
746
809
|
}
|
|
@@ -748,10 +811,12 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
748
811
|
// --- usage-heatmap ---
|
|
749
812
|
if (p === "/functions/tokentracker-usage-heatmap") {
|
|
750
813
|
const weeks = parseInt(url.searchParams.get("weeks") || "52", 10);
|
|
814
|
+
const timeZoneContext = getTimeZoneContext(url);
|
|
751
815
|
const rows = readQueueData(qp);
|
|
752
|
-
const daily = aggregateByDay(rows);
|
|
753
|
-
const
|
|
754
|
-
const
|
|
816
|
+
const daily = aggregateByDay(rows, timeZoneContext);
|
|
817
|
+
const todayParts = getZonedParts(new Date(), timeZoneContext);
|
|
818
|
+
const todayStr = formatPartsDayKey(todayParts) || new Date().toISOString().slice(0, 10);
|
|
819
|
+
const end = new Date(`${todayStr}T00:00:00Z`);
|
|
755
820
|
const start = new Date(end);
|
|
756
821
|
start.setUTCDate(start.getUTCDate() - weeks * 7 + 1);
|
|
757
822
|
const from = start.toISOString().slice(0, 10);
|
|
@@ -792,9 +857,10 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
792
857
|
if (p === "/functions/tokentracker-usage-model-breakdown") {
|
|
793
858
|
const from = url.searchParams.get("from") || "";
|
|
794
859
|
const to = url.searchParams.get("to") || "";
|
|
860
|
+
const timeZoneContext = getTimeZoneContext(url);
|
|
795
861
|
const rows = readQueueData(qp).filter((r) => {
|
|
796
862
|
if (!r.hour_start) return false;
|
|
797
|
-
const d = r
|
|
863
|
+
const d = rowDayKey(r, timeZoneContext);
|
|
798
864
|
return d >= from && d <= to;
|
|
799
865
|
});
|
|
800
866
|
|
|
@@ -852,35 +918,71 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
852
918
|
|
|
853
919
|
// --- project-usage-summary ---
|
|
854
920
|
if (p === "/functions/tokentracker-project-usage-summary") {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
const
|
|
861
|
-
|
|
921
|
+
// Use the per-project bucket log that rollout.js emits — it already
|
|
922
|
+
// carries the actual tokens attributed to each (project_key, source,
|
|
923
|
+
// hour_start). Falling back to "session-file count × total tokens"
|
|
924
|
+
// (the old behavior) produced pure fiction: every short-and-hot
|
|
925
|
+
// project got the same weight as every long-and-cold one.
|
|
926
|
+
const projectQueuePath = path.join(
|
|
927
|
+
path.dirname(qp),
|
|
928
|
+
"project.queue.jsonl",
|
|
929
|
+
);
|
|
930
|
+
const projectRows = readProjectQueueData(projectQueuePath);
|
|
931
|
+
|
|
932
|
+
const byProject = new Map();
|
|
933
|
+
for (const row of projectRows) {
|
|
934
|
+
const key = row.project_key || "unknown";
|
|
935
|
+
if (!byProject.has(key)) {
|
|
936
|
+
byProject.set(key, {
|
|
937
|
+
project_key: key,
|
|
938
|
+
project_ref: row.project_ref || key,
|
|
939
|
+
total_tokens: 0,
|
|
940
|
+
billable_total_tokens: 0,
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
const agg = byProject.get(key);
|
|
944
|
+
agg.total_tokens += Number(row.total_tokens || 0);
|
|
945
|
+
agg.billable_total_tokens += Number(row.total_tokens || 0);
|
|
946
|
+
if (!agg.project_ref && row.project_ref) agg.project_ref = row.project_ref;
|
|
947
|
+
}
|
|
862
948
|
|
|
863
|
-
|
|
949
|
+
// If no project-attributed rows exist yet (user hasn't synced project
|
|
950
|
+
// attribution, or never used a project-capable CLI), fall back to
|
|
951
|
+
// per-source aggregation over the main queue so the panel isn't
|
|
952
|
+
// totally empty. This path used to also exist for the non-empty case
|
|
953
|
+
// and produce wrong numbers; keep it only as the empty fallback.
|
|
954
|
+
let entries;
|
|
955
|
+
if (byProject.size === 0) {
|
|
956
|
+
const rows = readQueueData(qp);
|
|
864
957
|
const bySrc = new Map();
|
|
865
958
|
for (const row of rows) {
|
|
866
959
|
const src = row.source || "unknown";
|
|
867
|
-
if (!bySrc.has(src))
|
|
960
|
+
if (!bySrc.has(src)) {
|
|
961
|
+
bySrc.set(src, {
|
|
962
|
+
project_key: src,
|
|
963
|
+
project_ref: `https://${src}.ai`,
|
|
964
|
+
total_tokens: 0,
|
|
965
|
+
billable_total_tokens: 0,
|
|
966
|
+
});
|
|
967
|
+
}
|
|
868
968
|
bySrc.get(src).total_tokens += row.total_tokens || 0;
|
|
869
969
|
bySrc.get(src).billable_total_tokens += row.total_tokens || 0;
|
|
870
970
|
}
|
|
871
|
-
entries.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
971
|
+
entries = Array.from(bySrc.values())
|
|
972
|
+
.sort((a, b) => b.billable_total_tokens - a.billable_total_tokens)
|
|
973
|
+
.map((e) => ({
|
|
974
|
+
...e,
|
|
975
|
+
total_tokens: String(e.total_tokens),
|
|
976
|
+
billable_total_tokens: String(e.billable_total_tokens),
|
|
977
|
+
}));
|
|
876
978
|
} else {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
979
|
+
entries = Array.from(byProject.values())
|
|
980
|
+
.sort((a, b) => b.billable_total_tokens - a.billable_total_tokens)
|
|
981
|
+
.map((e) => ({
|
|
982
|
+
...e,
|
|
983
|
+
total_tokens: String(e.total_tokens),
|
|
984
|
+
billable_total_tokens: String(e.billable_total_tokens),
|
|
985
|
+
}));
|
|
884
986
|
}
|
|
885
987
|
|
|
886
988
|
json(res, { generated_at: new Date().toISOString(), entries });
|
|
@@ -911,12 +1013,13 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
911
1013
|
if (p === "/functions/tokentracker-usage-monthly") {
|
|
912
1014
|
const from = url.searchParams.get("from") || "";
|
|
913
1015
|
const to = url.searchParams.get("to") || "";
|
|
1016
|
+
const timeZoneContext = getTimeZoneContext(url);
|
|
914
1017
|
const rows = readQueueData(qp);
|
|
915
1018
|
const byMonth = new Map();
|
|
916
1019
|
for (const row of rows) {
|
|
917
1020
|
if (!row.hour_start) continue;
|
|
918
|
-
const day = row
|
|
919
|
-
if (day < from || day > to) continue;
|
|
1021
|
+
const day = rowDayKey(row, timeZoneContext);
|
|
1022
|
+
if (!day || day < from || day > to) continue;
|
|
920
1023
|
const month = day.slice(0, 7);
|
|
921
1024
|
if (!byMonth.has(month))
|
|
922
1025
|
byMonth.set(month, { month, total_tokens: 0, billable_total_tokens: 0, input_tokens: 0, output_tokens: 0, cached_input_tokens: 0, cache_creation_input_tokens: 0, reasoning_output_tokens: 0, conversation_count: 0 });
|
package/src/lib/rollout.js
CHANGED
|
@@ -676,12 +676,21 @@ async function parseOpenclawSessionFile({
|
|
|
676
676
|
|
|
677
677
|
const model = normalizeModelInput(msg.model) || DEFAULT_MODEL;
|
|
678
678
|
|
|
679
|
+
// Per CLAUDE.md: cached_input_tokens = cache reads,
|
|
680
|
+
// cache_creation_input_tokens = cache writes. Also re-derive total_tokens
|
|
681
|
+
// as input + output + cache_creation + cache_read so cost math works
|
|
682
|
+
// even when the source's own totalTokens is stale or rounded.
|
|
683
|
+
const inputTok = Number(usage.input || 0);
|
|
684
|
+
const cacheReadTok = Number(usage.cacheRead || 0);
|
|
685
|
+
const cacheWriteTok = Number(usage.cacheWrite || 0);
|
|
686
|
+
const outputTok = Number(usage.output || 0);
|
|
679
687
|
const delta = {
|
|
680
|
-
input_tokens:
|
|
681
|
-
cached_input_tokens:
|
|
682
|
-
|
|
688
|
+
input_tokens: inputTok,
|
|
689
|
+
cached_input_tokens: cacheReadTok,
|
|
690
|
+
cache_creation_input_tokens: cacheWriteTok,
|
|
691
|
+
output_tokens: outputTok,
|
|
683
692
|
reasoning_output_tokens: 0,
|
|
684
|
-
total_tokens:
|
|
693
|
+
total_tokens: inputTok + outputTok + cacheReadTok + cacheWriteTok,
|
|
685
694
|
conversation_count: 1,
|
|
686
695
|
};
|
|
687
696
|
|
|
@@ -2083,6 +2092,7 @@ function sameGeminiTotals(a, b) {
|
|
|
2083
2092
|
return (
|
|
2084
2093
|
a.input_tokens === b.input_tokens &&
|
|
2085
2094
|
a.cached_input_tokens === b.cached_input_tokens &&
|
|
2095
|
+
a.cache_creation_input_tokens === b.cache_creation_input_tokens &&
|
|
2086
2096
|
a.output_tokens === b.output_tokens &&
|
|
2087
2097
|
a.reasoning_output_tokens === b.reasoning_output_tokens &&
|
|
2088
2098
|
a.total_tokens === b.total_tokens
|
|
@@ -2097,12 +2107,20 @@ function diffGeminiTotals(current, previous) {
|
|
|
2097
2107
|
const totalReset = (current.total_tokens || 0) < (previous.total_tokens || 0);
|
|
2098
2108
|
if (totalReset) return current;
|
|
2099
2109
|
|
|
2110
|
+
// Must include cache_creation_input_tokens in both the equality check and
|
|
2111
|
+
// the delta — OpenCode routes through this diff and its cache.write number
|
|
2112
|
+
// would otherwise be permanently reported as zero. Gemini itself always
|
|
2113
|
+
// emits cache_creation=0 so the extra field is a no-op for Gemini.
|
|
2100
2114
|
const delta = {
|
|
2101
2115
|
input_tokens: Math.max(0, (current.input_tokens || 0) - (previous.input_tokens || 0)),
|
|
2102
2116
|
cached_input_tokens: Math.max(
|
|
2103
2117
|
0,
|
|
2104
2118
|
(current.cached_input_tokens || 0) - (previous.cached_input_tokens || 0),
|
|
2105
2119
|
),
|
|
2120
|
+
cache_creation_input_tokens: Math.max(
|
|
2121
|
+
0,
|
|
2122
|
+
(current.cache_creation_input_tokens || 0) - (previous.cache_creation_input_tokens || 0),
|
|
2123
|
+
),
|
|
2106
2124
|
output_tokens: Math.max(0, (current.output_tokens || 0) - (previous.output_tokens || 0)),
|
|
2107
2125
|
reasoning_output_tokens: Math.max(
|
|
2108
2126
|
0,
|
|
@@ -2651,6 +2669,16 @@ function readKiroDbTokens(dbPath, sinceId) {
|
|
|
2651
2669
|
// The fallback file does not include per-row timestamps, so newly appended rows are
|
|
2652
2670
|
// bucketed using the file mtime observed during this sync. We track a separate JSONL
|
|
2653
2671
|
// cursor so it never shares state with the SQLite path.
|
|
2672
|
+
function countKiroJsonlLines(jsonlPath) {
|
|
2673
|
+
if (!jsonlPath || !fssync.existsSync(jsonlPath)) return 0;
|
|
2674
|
+
try {
|
|
2675
|
+
const raw = fssync.readFileSync(jsonlPath, "utf8");
|
|
2676
|
+
return raw.split("\n").filter((l) => l.trim()).length;
|
|
2677
|
+
} catch (_e) {
|
|
2678
|
+
return 0;
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2654
2682
|
function readKiroJsonlTokens(jsonlPath, sinceLineIndex) {
|
|
2655
2683
|
if (!jsonlPath || !fssync.existsSync(jsonlPath)) {
|
|
2656
2684
|
return { rows: [], lineCount: 0, reset: false };
|
|
@@ -2795,6 +2823,14 @@ async function parseKiroIncremental({ dbPath, jsonlPath, cursors, queuePath, onP
|
|
|
2795
2823
|
if (fssync.existsSync(resolvedDbPath)) {
|
|
2796
2824
|
rows = readKiroDbTokens(resolvedDbPath, lastDbId);
|
|
2797
2825
|
usingDb = true;
|
|
2826
|
+
// DB and JSONL are siblings for the same usage events. If the DB ever
|
|
2827
|
+
// disappears (corrupted / wiped) and we fall back to JSONL in a later
|
|
2828
|
+
// run, we must not re-read lines that the DB path already consumed.
|
|
2829
|
+
// Advance the JSONL line cursor to the current file tail.
|
|
2830
|
+
if (fssync.existsSync(resolvedJsonlPath)) {
|
|
2831
|
+
const tailLineCount = countKiroJsonlLines(resolvedJsonlPath);
|
|
2832
|
+
if (tailLineCount > nextJsonlLine) nextJsonlLine = tailLineCount;
|
|
2833
|
+
}
|
|
2798
2834
|
} else if (fssync.existsSync(resolvedJsonlPath)) {
|
|
2799
2835
|
const jsonlResult = readKiroJsonlTokens(resolvedJsonlPath, lastJsonlLine);
|
|
2800
2836
|
rows = jsonlResult.rows;
|
|
@@ -3381,4 +3417,9 @@ module.exports = {
|
|
|
3381
3417
|
resolveKimiWireFiles,
|
|
3382
3418
|
resolveKimiDefaultModel,
|
|
3383
3419
|
parseKimiIncremental,
|
|
3420
|
+
// Exposed for regression tests covering cache-token accounting.
|
|
3421
|
+
normalizeGeminiTokens,
|
|
3422
|
+
normalizeOpencodeTokens,
|
|
3423
|
+
sameGeminiTotals,
|
|
3424
|
+
diffGeminiTotals,
|
|
3384
3425
|
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,D as e,aA as D,C as H,aC as W,h as Y,j as Z,aE as G,aI as J,B as a,az as L,ax as R,a9 as E,aJ as q,aK as F}from"./main-Cv6YpYdZ.js";import{b as B,d as Q,l as V,f as X,e as ee,L as re,g as ae}from"./leaderboard-columns-DFG1TWe9.js";const y=18;function O(){return e.jsxs("svg",{"aria-hidden":!0,width:y,height:y,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("circle",{cx:"12",cy:"12",r:"4"}),e.jsx("path",{d:"M12 2v2"}),e.jsx("path",{d:"M12 20v2"}),e.jsx("path",{d:"m4.93 4.93 1.41 1.41"}),e.jsx("path",{d:"m17.66 17.66 1.41 1.41"}),e.jsx("path",{d:"M2 12h2"}),e.jsx("path",{d:"M20 12h2"}),e.jsx("path",{d:"m6.34 17.66-1.41 1.41"}),e.jsx("path",{d:"m19.07 4.93-1.41 1.41"})]})}function P(){return e.jsx("svg",{"aria-hidden":!0,width:y,height:y,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:e.jsx("path",{d:"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"})})}function te(){return e.jsxs("svg",{"aria-hidden":!0,width:y,height:y,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("rect",{x:"2",y:"3",width:"20",height:"14",rx:"2"}),e.jsx("path",{d:"M8 21h8"}),e.jsx("path",{d:"M12 17v4"})]})}const oe=[{value:"light",label:"Light",Icon:O},{value:"dark",label:"Dark",Icon:P},{value:"system",label:"System",Icon:te}];function se(r){return r==="dark"?P:O}function ne({theme:r,resolvedTheme:s,onSetTheme:b,className:d="",direction:p="down",align:T="right"}){const[c,k]=n.useState(!1),h=n.useRef(null),i=n.useCallback(()=>k(!1),[]);n.useEffect(()=>{if(!c)return;const t=l=>{h.current&&!h.current.contains(l.target)&&i()};return document.addEventListener("mousedown",t),()=>document.removeEventListener("mousedown",t)},[c,i]),n.useEffect(()=>{if(!c)return;const t=l=>{l.key==="Escape"&&i()};return document.addEventListener("keydown",t),()=>document.removeEventListener("keydown",t)},[c,i]);const x=se(s);return e.jsxs("div",{ref:h,className:`relative ${d}`,children:[e.jsx("button",{type:"button","aria-label":"Theme","aria-expanded":c,onClick:()=>k(t=>!t),className:"flex items-center justify-center w-9 h-9 rounded-lg text-oai-gray-600 dark:text-oai-gray-400 hover:bg-oai-gray-100 dark:hover:bg-oai-gray-800 hover:text-oai-black dark:hover:text-white transition-colors",children:e.jsx(x,{})}),c&&e.jsx("div",{className:`absolute z-50 min-w-[140px] py-1 rounded-lg border border-oai-gray-200 dark:border-oai-gray-800 bg-white dark:bg-oai-gray-900 shadow-lg ${p==="up"?"bottom-full mb-1":"top-full mt-1"} ${T==="left"?"left-0":"right-0"}`,children:oe.map(({value:t,label:l,Icon:j})=>{const m=r===t;return e.jsxs("button",{type:"button",onClick:()=>{b(t),i()},className:`flex w-full items-center gap-2.5 px-3 py-2 text-sm transition-colors ${m?"text-oai-black dark:text-white bg-oai-gray-100 dark:bg-oai-gray-800":"text-oai-gray-600 dark:text-oai-gray-400 hover:bg-oai-gray-50 dark:hover:bg-oai-gray-800/60 hover:text-oai-black dark:hover:text-white"}`,children:[e.jsx(j,{}),e.jsx("span",{children:l})]},t)})})]})}function ie(r){if(!r)return a("shared.error.prefix",{error:a("leaderboard.error.unknown")});const s=r?.message||String(r),b=String(s||"").trim()||a("leaderboard.error.unknown");return a("shared.error.prefix",{error:b})}function le(r){return typeof r!="string"?"":r.trim()}function de(r){if(typeof r!="string")return null;const s=r.trim().toLowerCase();return s==="week"||s==="month"||s==="total"?s:null}function me({auth:r,signedIn:s,sessionSoftExpired:b,userId:d}){const p=D(),{theme:T,resolvedTheme:c,setTheme:k}=H(),h=n.useMemo(()=>W(),[]),i=Y(),x=s&&!b,t=n.useMemo(()=>x&&(typeof r=="function"||typeof r=="string"||r&&typeof r=="object")?r:null,[r,x]),l=x?t:null,j=x&&Z(l),m=n.useMemo(()=>{const o=new URLSearchParams(p?.search||"");return de(o.get("period"))||"week"},[p?.search]),_=p?.search||"",[v,w]=n.useState(()=>({loading:!1,error:null,data:null}));n.useEffect(()=>{if(!h&&!i||!d||!i&&(!x||!j))return;let o=!0;return w(f=>({...f,loading:!0,error:null})),(async()=>{const f=await G(l);if(!o)return;if(!f){w({loading:!1,error:null,data:null});return}const $=await J({accessToken:f,userId:d,period:m});o&&w({loading:!1,error:null,data:$})})().catch(f=>{o&&w({loading:!1,error:ie(f),data:null})}),()=>{o=!1}},[x,j,h,l,i,m,d]);const N=v.data,C=N?.from||null,A=N?.to||null,M=N?.generated_at||null,g=N?.entry||null,S=le(g?.display_name)||a("leaderboard.anon_label"),z=a("leaderboard.period.week"),K=a("leaderboard.period.month"),U=a("leaderboard.period.total"),I=m==="month"?K:m==="total"?U:z;let u=null;return d?v.loading?u=e.jsx("div",{className:"px-6 py-12 text-center",children:e.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400",children:a("leaderboard.loading")})}):v.error?u=e.jsx("div",{className:"px-6 py-12 text-center",children:e.jsx("p",{className:"text-sm text-red-500 dark:text-red-400",children:v.error})}):g?u=e.jsx("div",{className:"w-full overflow-x-auto",children:e.jsxs("table",{className:"min-w-max w-full text-left text-sm",children:[e.jsx("thead",{className:"border-b border-oai-gray-200 dark:border-oai-gray-800",children:e.jsxs("tr",{children:[e.jsx("th",{className:L(re,"font-medium text-oai-gray-500 dark:text-oai-gray-400"),children:a("leaderboard.column.rank")}),e.jsx("th",{className:L(ae,"font-medium text-oai-gray-500 dark:text-oai-gray-400 whitespace-nowrap"),children:a("leaderboard.column.total")}),B.map(o=>e.jsx("th",{className:"px-4 py-4 font-medium text-oai-gray-500 dark:text-oai-gray-400 whitespace-nowrap",children:e.jsx(Q,{iconSrc:o.icon,label:a(o.copyKey)})},o.key))]})}),e.jsx("tbody",{className:"divide-y divide-oai-gray-100 dark:divide-oai-gray-800/50",children:e.jsxs("tr",{className:"transition-colors hover:bg-oai-gray-50 dark:hover:bg-oai-gray-900/60",children:[e.jsx("td",{className:L(V(!1),"font-medium text-oai-gray-500 dark:text-oai-gray-400"),children:g?.rank??a("shared.placeholder.short")}),e.jsx("td",{className:L(X(),"text-oai-gray-700 dark:text-oai-gray-300"),children:R(g?.total_tokens)}),B.map(o=>e.jsx("td",{className:"px-4 py-4 text-oai-gray-500 dark:text-oai-gray-400 whitespace-nowrap",children:R(g?.[o.key])},o.key))]})})]})}):u=e.jsx("div",{className:"px-6 py-12 text-center",children:e.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400",children:a("leaderboard.empty")})}):u=e.jsx("div",{className:"px-6 py-12 text-center",children:e.jsx("p",{className:"text-sm text-oai-gray-500 dark:text-oai-gray-400",children:a("leaderboard.empty")})}),e.jsxs("div",{className:"flex flex-col min-h-screen bg-oai-white dark:bg-oai-gray-950 text-oai-black dark:text-oai-white font-oai antialiased transition-colors duration-200",children:[e.jsx("header",{className:"sticky top-0 z-50 bg-white/80 dark:bg-oai-gray-950/80 backdrop-blur-md border-b border-oai-gray-200 dark:border-oai-gray-900 transition-colors duration-200",children:e.jsxs("div",{className:"mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6",children:[e.jsxs("div",{className:"flex items-center gap-5",children:[e.jsxs(E,{to:"/",className:"flex items-center gap-3 no-underline outline-none rounded focus-visible:ring-2 focus-visible:ring-oai-brand-500 focus-visible:ring-offset-2 dark:ring-offset-oai-gray-950 transition-opacity hover:opacity-80",children:[e.jsx("img",{src:"/app-icon.png",alt:"",width:24,height:24,className:"rounded-md"}),e.jsx("span",{className:"text-sm font-semibold tracking-wide text-oai-black dark:text-white uppercase",children:"Token Tracker"})]}),e.jsx("div",{className:"hidden sm:block",children:e.jsx(q,{})})]}),e.jsxs("div",{className:"flex items-center gap-2 sm:gap-3",children:[e.jsx(E,{to:`/leaderboard${_}`,className:"no-underline inline-flex items-center justify-center h-9 px-5 text-sm font-medium rounded-full shadow-sm ring-1 ring-oai-gray-200 dark:ring-white/10 bg-oai-gray-900 dark:bg-white text-white dark:text-oai-gray-900 hover:bg-oai-gray-800 dark:hover:bg-oai-gray-100 transition-colors",children:a("leaderboard.profile.nav.back")}),e.jsx(ne,{theme:T,resolvedTheme:c,onSetTheme:k}),e.jsx(F,{})]})]})}),e.jsx("main",{className:"flex-1 py-12 sm:py-16",children:e.jsxs("div",{className:"mx-auto max-w-6xl px-4 sm:px-6",children:[e.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center gap-6 mb-10",children:[e.jsx(ee,{avatarUrl:g?.avatar_url,displayName:S,seed:typeof d=="string"?d:S,size:"lg",className:"shrink-0 ring-2 ring-oai-gray-200 dark:ring-oai-gray-800"}),e.jsxs("div",{className:"min-w-0",children:[e.jsx("h1",{className:"text-3xl sm:text-4xl font-semibold tracking-tight text-oai-black dark:text-white mb-3",children:S}),e.jsxs("p",{className:"text-oai-gray-500 dark:text-oai-gray-400 text-sm sm:text-base",children:[m==="total"?a("leaderboard.range.total"):C&&A?a("leaderboard.range",{period:I,from:C,to:A}):a("leaderboard.range_loading",{period:I}),M&&e.jsx("span",{className:"ml-2 pl-2 border-l border-oai-gray-200 dark:border-oai-gray-800 inline-block text-oai-gray-400 dark:text-oai-gray-500 text-xs",children:a("leaderboard.generated_at",{ts:M})})]})]})]}),e.jsx("div",{className:"rounded-xl border border-oai-gray-200 dark:border-oai-gray-800 overflow-hidden",children:u})]})}),e.jsx("footer",{className:"border-t border-oai-gray-200 dark:border-oai-gray-900 py-8 transition-colors duration-200",children:e.jsxs("div",{className:"mx-auto flex max-w-6xl items-center justify-between px-4 sm:px-6 text-sm text-oai-gray-400 dark:text-oai-gray-500",children:[e.jsx("p",{children:a("landing.v2.footer.line")}),e.jsx(E,{to:`/leaderboard${_}`,className:"text-oai-gray-400 dark:text-oai-gray-500 hover:text-oai-black dark:hover:text-white transition-colors",children:a("leaderboard.profile.nav.back")})]})})]})}export{me as LeaderboardProfilePage};
|