tokentracker-cli 0.5.97 → 0.5.99
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/bin/tracker.js +14 -0
- package/dashboard/dist/assets/{main-h-vFy9lS.js → main-BhjD_pKB.js} +201 -194
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +2 -1
- package/src/commands/init.js +8 -1
- package/src/commands/serve.js +7 -0
- package/src/commands/sync.js +74 -1
- package/src/lib/cursor-config.js +15 -0
- package/src/lib/local-api.js +39 -16
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/proxy-env.js +81 -0
- package/src/lib/rollout.js +40 -20
- package/src/lib/source-metadata.js +46 -0
- package/src/lib/usage-limits.js +312 -40
|
@@ -135,7 +135,7 @@
|
|
|
135
135
|
]
|
|
136
136
|
}
|
|
137
137
|
</script>
|
|
138
|
-
<script type="module" crossorigin src="/assets/main-
|
|
138
|
+
<script type="module" crossorigin src="/assets/main-BhjD_pKB.js"></script>
|
|
139
139
|
<link rel="stylesheet" crossorigin href="/assets/main-HLMqEvtH.css">
|
|
140
140
|
</head>
|
|
141
141
|
<body>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
|
-
<script type="module" crossorigin src="/assets/main-
|
|
54
|
+
<script type="module" crossorigin src="/assets/main-BhjD_pKB.js"></script>
|
|
55
55
|
<link rel="stylesheet" crossorigin href="/assets/main-HLMqEvtH.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.99",
|
|
4
4
|
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Kiro, Gemini, OpenCode, OpenClaw, Hermes, GitHub Copilot)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"dashboard:open": "bash scripts/open-dashboard.sh",
|
|
30
30
|
"dashboard:preview": "npm --prefix dashboard run preview",
|
|
31
31
|
"dev:shim": "node scripts/dev-bin-shim.cjs",
|
|
32
|
+
"audit:tokens": "node scripts/audit-token-correctness.cjs",
|
|
32
33
|
"graph:auto-index": "node scripts/graph/auto-index.cjs",
|
|
33
34
|
"graph:scip": "node scripts/graph/generate-scip.cjs",
|
|
34
35
|
"pricing:build-seed": "node scripts/build-pricing-seed.cjs",
|
package/src/commands/init.js
CHANGED
|
@@ -879,7 +879,7 @@ function isRunnableCommand(command) {
|
|
|
879
879
|
`;
|
|
880
880
|
}
|
|
881
881
|
|
|
882
|
-
module.exports = { cmdInit, buildNotifyHandler };
|
|
882
|
+
module.exports = { cmdInit, buildNotifyHandler, installLocalTrackerApp };
|
|
883
883
|
|
|
884
884
|
async function probeFile(p) {
|
|
885
885
|
try {
|
|
@@ -908,7 +908,9 @@ async function installLocalTrackerApp({ appDir }) {
|
|
|
908
908
|
const packageRoot = path.resolve(__dirname, "../..");
|
|
909
909
|
const srcFrom = path.join(packageRoot, "src");
|
|
910
910
|
const binFrom = path.join(packageRoot, "bin", "tracker.js");
|
|
911
|
+
const packageJsonFrom = path.join(packageRoot, "package.json");
|
|
911
912
|
const nodeModulesFrom = path.join(packageRoot, "node_modules");
|
|
913
|
+
const dashboardDistFrom = path.join(packageRoot, "dashboard", "dist");
|
|
912
914
|
|
|
913
915
|
// When running from the installed local runtime (or when appDir is symlinked to this package),
|
|
914
916
|
// source and destination resolve to the same place. Do not delete appDir in that case.
|
|
@@ -920,6 +922,7 @@ async function installLocalTrackerApp({ appDir }) {
|
|
|
920
922
|
const binToDir = path.join(appDir, "bin");
|
|
921
923
|
const binTo = path.join(binToDir, "tracker.js");
|
|
922
924
|
const nodeModulesTo = path.join(appDir, "node_modules");
|
|
925
|
+
const dashboardDistTo = path.join(appDir, "dashboard", "dist");
|
|
923
926
|
|
|
924
927
|
await fs.rm(appDir, { recursive: true, force: true }).catch(() => {});
|
|
925
928
|
await ensureDir(appDir);
|
|
@@ -927,6 +930,10 @@ async function installLocalTrackerApp({ appDir }) {
|
|
|
927
930
|
await ensureDir(binToDir);
|
|
928
931
|
await fs.copyFile(binFrom, binTo);
|
|
929
932
|
await fs.chmod(binTo, 0o755).catch(() => {});
|
|
933
|
+
await fs.copyFile(packageJsonFrom, path.join(appDir, "package.json")).catch(() => {});
|
|
934
|
+
if (await isDir(dashboardDistFrom)) {
|
|
935
|
+
await fs.cp(dashboardDistFrom, dashboardDistTo, { recursive: true });
|
|
936
|
+
}
|
|
930
937
|
await copyRuntimeDependencies({ from: nodeModulesFrom, to: nodeModulesTo });
|
|
931
938
|
}
|
|
932
939
|
|
package/src/commands/serve.js
CHANGED
|
@@ -37,6 +37,13 @@ async function cmdServe(argv) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
try {
|
|
41
|
+
const { installLocalTrackerApp } = require("./init");
|
|
42
|
+
await installLocalTrackerApp({ appDir: path.join(trackerDir, "app") });
|
|
43
|
+
} catch (e) {
|
|
44
|
+
process.stdout.write(`Runtime refresh warning: ${e?.message || e}\n`);
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
// 0.1 Ensure config.json baseUrl matches the canonical default
|
|
41
48
|
try {
|
|
42
49
|
const { DEFAULT_BASE_URL } = require("../lib/runtime-config");
|
package/src/commands/sync.js
CHANGED
|
@@ -52,6 +52,7 @@ const { resolveTrackerPaths } = require("../lib/tracker-paths");
|
|
|
52
52
|
const { resolveRuntimeConfig } = require("../lib/runtime-config");
|
|
53
53
|
|
|
54
54
|
const CURSOR_UNKNOWN_MIGRATION_KEY = "cursorUnknownPurge_2026_04";
|
|
55
|
+
const ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY = "rolloutCumulativeDeltaReparse_2026_05";
|
|
55
56
|
|
|
56
57
|
async function cmdSync(argv) {
|
|
57
58
|
const opts = parseArgs(argv);
|
|
@@ -114,6 +115,8 @@ async function cmdSync(argv) {
|
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
await migrateRolloutCumulativeDeltaBuckets({ cursors, queuePath, rolloutFiles });
|
|
119
|
+
|
|
117
120
|
const openclawFiles = openclawSignal?.sessionFile
|
|
118
121
|
? [{ path: openclawSignal.sessionFile, source: "openclaw" }]
|
|
119
122
|
: [];
|
|
@@ -621,7 +624,13 @@ function parseArgs(argv) {
|
|
|
621
624
|
return out;
|
|
622
625
|
}
|
|
623
626
|
|
|
624
|
-
module.exports = {
|
|
627
|
+
module.exports = {
|
|
628
|
+
cmdSync,
|
|
629
|
+
migrateCursorUnknownBuckets,
|
|
630
|
+
migrateRolloutCumulativeDeltaBuckets,
|
|
631
|
+
CURSOR_UNKNOWN_MIGRATION_KEY,
|
|
632
|
+
ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY,
|
|
633
|
+
};
|
|
625
634
|
|
|
626
635
|
function normalizeString(value) {
|
|
627
636
|
if (typeof value !== "string") return null;
|
|
@@ -1034,3 +1043,67 @@ async function migrateCursorUnknownBuckets({ cursors, queuePath }) {
|
|
|
1034
1043
|
|
|
1035
1044
|
cursors.migrations[CURSOR_UNKNOWN_MIGRATION_KEY] = new Date().toISOString();
|
|
1036
1045
|
}
|
|
1046
|
+
|
|
1047
|
+
async function migrateRolloutCumulativeDeltaBuckets({ cursors, queuePath, rolloutFiles }) {
|
|
1048
|
+
if (!cursors || typeof cursors !== "object") return;
|
|
1049
|
+
cursors.migrations = cursors.migrations || {};
|
|
1050
|
+
if (cursors.migrations[ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY]) return;
|
|
1051
|
+
|
|
1052
|
+
const rolloutPathSources = new Map();
|
|
1053
|
+
for (const entry of Array.isArray(rolloutFiles) ? rolloutFiles : []) {
|
|
1054
|
+
const filePath = typeof entry === "string" ? entry : entry?.path;
|
|
1055
|
+
const source = typeof entry === "string" ? "codex" : String(entry?.source || "codex");
|
|
1056
|
+
if (!filePath) continue;
|
|
1057
|
+
if (source === "codex" || source === "every-code") {
|
|
1058
|
+
rolloutPathSources.set(filePath, source);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (cursors.files && typeof cursors.files === "object") {
|
|
1063
|
+
for (const filePath of rolloutPathSources.keys()) {
|
|
1064
|
+
delete cursors.files[filePath];
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const buckets = cursors.hourly?.buckets;
|
|
1069
|
+
const retractions = [];
|
|
1070
|
+
if (buckets && typeof buckets === "object") {
|
|
1071
|
+
for (const key of Object.keys(buckets)) {
|
|
1072
|
+
const [source, model, ...hourParts] = key.split("|");
|
|
1073
|
+
if (source !== "codex" && source !== "every-code") continue;
|
|
1074
|
+
const hourStart = hourParts.join("|");
|
|
1075
|
+
retractions.push(
|
|
1076
|
+
JSON.stringify({
|
|
1077
|
+
source,
|
|
1078
|
+
model: model || "unknown",
|
|
1079
|
+
hour_start: hourStart,
|
|
1080
|
+
input_tokens: 0,
|
|
1081
|
+
cached_input_tokens: 0,
|
|
1082
|
+
cache_creation_input_tokens: 0,
|
|
1083
|
+
output_tokens: 0,
|
|
1084
|
+
reasoning_output_tokens: 0,
|
|
1085
|
+
total_tokens: 0,
|
|
1086
|
+
billable_total_tokens: 0,
|
|
1087
|
+
conversation_count: 0,
|
|
1088
|
+
}),
|
|
1089
|
+
);
|
|
1090
|
+
delete buckets[key];
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
const groupQueued = cursors.hourly?.groupQueued;
|
|
1095
|
+
if (groupQueued && typeof groupQueued === "object") {
|
|
1096
|
+
for (const key of Object.keys(groupQueued)) {
|
|
1097
|
+
if (key.startsWith("codex|") || key.startsWith("every-code|")) {
|
|
1098
|
+
delete groupQueued[key];
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
if (retractions.length > 0) {
|
|
1104
|
+
await ensureDir(path.dirname(queuePath));
|
|
1105
|
+
await fs.appendFile(queuePath, retractions.join("\n") + "\n");
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
cursors.migrations[ROLLOUT_CUMULATIVE_DELTA_MIGRATION_KEY] = new Date().toISOString();
|
|
1109
|
+
}
|
package/src/lib/cursor-config.js
CHANGED
|
@@ -107,6 +107,7 @@ function extractUserIdFromJwt(jwt) {
|
|
|
107
107
|
|
|
108
108
|
const CURSOR_CSV_URL = "https://cursor.com/api/dashboard/export-usage-events-csv?strategy=tokens";
|
|
109
109
|
const CURSOR_SUMMARY_URL = "https://cursor.com/api/usage-summary";
|
|
110
|
+
const CURSOR_SOURCE_SCOPE = "account";
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
113
|
* Fetch full usage CSV from Cursor API.
|
|
@@ -280,6 +281,10 @@ function parseCursorCsv(csvText) {
|
|
|
280
281
|
kind: kindIdx !== undefined ? stripQuotes(fields[kindIdx]) : "unknown",
|
|
281
282
|
model: stripQuotes(fields[modelIdx]),
|
|
282
283
|
maxMode: maxModeIdx !== undefined ? stripQuotes(fields[maxModeIdx]) : "No",
|
|
284
|
+
sourceScope: CURSOR_SOURCE_SCOPE,
|
|
285
|
+
billableKind: isCursorBillableKind(kindIdx !== undefined ? fields[kindIdx] : "unknown")
|
|
286
|
+
? "billable"
|
|
287
|
+
: "non_billable",
|
|
283
288
|
inputTokens: inputWithoutCache,
|
|
284
289
|
cacheWriteTokens: Math.max(0, inputWithCache - inputWithoutCache),
|
|
285
290
|
cacheReadTokens: toNum(fields[cacheReadIdx]),
|
|
@@ -312,9 +317,18 @@ function normalizeCursorUsage(record) {
|
|
|
312
317
|
output_tokens: outputTokens,
|
|
313
318
|
reasoning_output_tokens: 0,
|
|
314
319
|
total_tokens: totalTokens,
|
|
320
|
+
billable_total_tokens: isCursorBillableKind(record?.kind) ? totalTokens : 0,
|
|
315
321
|
};
|
|
316
322
|
}
|
|
317
323
|
|
|
324
|
+
function isCursorBillableKind(kind) {
|
|
325
|
+
const normalized = String(kind || "").trim().toLowerCase();
|
|
326
|
+
if (!normalized) return true;
|
|
327
|
+
if (normalized.includes("no charge")) return false;
|
|
328
|
+
if (normalized === "free") return false;
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
318
332
|
// ── CSV helpers ──
|
|
319
333
|
|
|
320
334
|
function parseCsvLine(line) {
|
|
@@ -364,5 +378,6 @@ module.exports = {
|
|
|
364
378
|
fetchCursorUsageCsv,
|
|
365
379
|
fetchCursorUsageSummary,
|
|
366
380
|
parseCursorCsv,
|
|
381
|
+
isCursorBillableKind,
|
|
367
382
|
normalizeCursorUsage,
|
|
368
383
|
};
|
package/src/lib/local-api.js
CHANGED
|
@@ -4,6 +4,12 @@ const path = require("node:path");
|
|
|
4
4
|
const { spawn } = require("node:child_process");
|
|
5
5
|
const crypto = require("node:crypto");
|
|
6
6
|
const { DEFAULT_BASE_URL, resolveRuntimeConfig } = require("./runtime-config");
|
|
7
|
+
const {
|
|
8
|
+
filterRowsByUsageScope,
|
|
9
|
+
getSourceScope,
|
|
10
|
+
listExcludedSources,
|
|
11
|
+
normalizeUsageScope,
|
|
12
|
+
} = require("./source-metadata");
|
|
7
13
|
|
|
8
14
|
const SYNC_TIMEOUT_MS = 120_000;
|
|
9
15
|
const TRACKER_BIN = path.resolve(__dirname, "../../bin/tracker.js");
|
|
@@ -154,7 +160,7 @@ function aggregateByDay(rows, timeZoneContext = null) {
|
|
|
154
160
|
}
|
|
155
161
|
const a = byDay.get(day);
|
|
156
162
|
a.total_tokens += row.total_tokens || 0;
|
|
157
|
-
a.billable_total_tokens += row.total_tokens
|
|
163
|
+
a.billable_total_tokens += row.billable_total_tokens ?? row.total_tokens ?? 0;
|
|
158
164
|
a.total_cost_usd += computeRowCost(row);
|
|
159
165
|
a.input_tokens += row.input_tokens || 0;
|
|
160
166
|
a.output_tokens += row.output_tokens || 0;
|
|
@@ -166,6 +172,22 @@ function aggregateByDay(rows, timeZoneContext = null) {
|
|
|
166
172
|
return Array.from(byDay.values()).sort((a, b) => a.day.localeCompare(b.day));
|
|
167
173
|
}
|
|
168
174
|
|
|
175
|
+
function getRequestedUsageScope(url) {
|
|
176
|
+
if (url.searchParams.get("include_account_level") === "1") return "all";
|
|
177
|
+
return normalizeUsageScope(url.searchParams.get("scope"));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function scopedQueueRows(queuePath, url) {
|
|
181
|
+
const scope = getRequestedUsageScope(url);
|
|
182
|
+
const allRows = readQueueData(queuePath);
|
|
183
|
+
return {
|
|
184
|
+
scope,
|
|
185
|
+
allRows,
|
|
186
|
+
rows: filterRowsByUsageScope(allRows, scope),
|
|
187
|
+
excludedSources: listExcludedSources(allRows, scope),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
169
191
|
function getTimeZoneContext(url) {
|
|
170
192
|
const tz = String(url.searchParams.get("tz") || "").trim();
|
|
171
193
|
const rawOffset = Number(url.searchParams.get("tz_offset_minutes"));
|
|
@@ -796,7 +818,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
796
818
|
const from = url.searchParams.get("from") || "";
|
|
797
819
|
const to = url.searchParams.get("to") || "";
|
|
798
820
|
const timeZoneContext = getTimeZoneContext(url);
|
|
799
|
-
const rows =
|
|
821
|
+
const { rows, scope, excludedSources } = scopedQueueRows(qp, url);
|
|
800
822
|
const daily = aggregateByDay(rows, timeZoneContext).filter((d) => d.day >= from && d.day <= to);
|
|
801
823
|
const totals = daily.reduce(
|
|
802
824
|
(acc, r) => {
|
|
@@ -848,7 +870,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
848
870
|
const l30fromStr = shiftDay(todayStr, -29);
|
|
849
871
|
|
|
850
872
|
json(res, {
|
|
851
|
-
from, to, days: daily.length,
|
|
873
|
+
from, to, days: daily.length, scope, excluded_sources: excludedSources,
|
|
852
874
|
totals: { ...totals, total_cost_usd: totalCost.toFixed(6) },
|
|
853
875
|
rolling: {
|
|
854
876
|
last_7d: { from: l7fromStr, to: todayStr, active_days: l7.length, totals: l7t },
|
|
@@ -863,9 +885,9 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
863
885
|
const from = url.searchParams.get("from") || "";
|
|
864
886
|
const to = url.searchParams.get("to") || "";
|
|
865
887
|
const timeZoneContext = getTimeZoneContext(url);
|
|
866
|
-
const rows =
|
|
888
|
+
const { rows, scope, excludedSources } = scopedQueueRows(qp, url);
|
|
867
889
|
const daily = aggregateByDay(rows, timeZoneContext).filter((d) => d.day >= from && d.day <= to);
|
|
868
|
-
json(res, { from, to, data: daily });
|
|
890
|
+
json(res, { from, to, scope, excluded_sources: excludedSources, data: daily });
|
|
869
891
|
return true;
|
|
870
892
|
}
|
|
871
893
|
|
|
@@ -873,7 +895,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
873
895
|
if (p === "/functions/tokentracker-usage-heatmap") {
|
|
874
896
|
const weeks = parseInt(url.searchParams.get("weeks") || "52", 10);
|
|
875
897
|
const timeZoneContext = getTimeZoneContext(url);
|
|
876
|
-
const rows =
|
|
898
|
+
const { rows, scope, excludedSources } = scopedQueueRows(qp, url);
|
|
877
899
|
const daily = aggregateByDay(rows, timeZoneContext);
|
|
878
900
|
const todayParts = getZonedParts(new Date(), timeZoneContext);
|
|
879
901
|
const todayStr = formatPartsDayKey(todayParts) || new Date().toISOString().slice(0, 10);
|
|
@@ -910,7 +932,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
910
932
|
for (let i = 0; i < cells.length; i += 7) {
|
|
911
933
|
weeksArr.push(cells.slice(i, i + 7));
|
|
912
934
|
}
|
|
913
|
-
json(res, { from, to, week_starts_on: "sun", active_days: cells.filter((c) => c.billable_total_tokens > 0).length, streak_days: 0, weeks: weeksArr });
|
|
935
|
+
json(res, { from, to, scope, excluded_sources: excludedSources, week_starts_on: "sun", active_days: cells.filter((c) => c.billable_total_tokens > 0).length, streak_days: 0, weeks: weeksArr });
|
|
914
936
|
return true;
|
|
915
937
|
}
|
|
916
938
|
|
|
@@ -919,7 +941,8 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
919
941
|
const from = url.searchParams.get("from") || "";
|
|
920
942
|
const to = url.searchParams.get("to") || "";
|
|
921
943
|
const timeZoneContext = getTimeZoneContext(url);
|
|
922
|
-
const rows =
|
|
944
|
+
const { rows: scopedRows, scope, excludedSources } = scopedQueueRows(qp, url);
|
|
945
|
+
const rows = scopedRows.filter((r) => {
|
|
923
946
|
if (!r.hour_start) return false;
|
|
924
947
|
const d = rowDayKey(r, timeZoneContext);
|
|
925
948
|
return d >= from && d <= to;
|
|
@@ -930,10 +953,10 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
930
953
|
const src = row.source || "unknown";
|
|
931
954
|
const mdl = row.model || "unknown";
|
|
932
955
|
if (!bySource.has(src))
|
|
933
|
-
bySource.set(src, { source: src, totals: { 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, total_cost_usd: "0" }, models: new Map() });
|
|
956
|
+
bySource.set(src, { source: src, source_scope: getSourceScope(src), totals: { 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, total_cost_usd: "0" }, models: new Map() });
|
|
934
957
|
const sa = bySource.get(src);
|
|
935
958
|
sa.totals.total_tokens += row.total_tokens || 0;
|
|
936
|
-
sa.totals.billable_total_tokens += row.total_tokens
|
|
959
|
+
sa.totals.billable_total_tokens += row.billable_total_tokens ?? row.total_tokens ?? 0;
|
|
937
960
|
sa.totals.input_tokens += row.input_tokens || 0;
|
|
938
961
|
sa.totals.output_tokens += row.output_tokens || 0;
|
|
939
962
|
sa.totals.cached_input_tokens += row.cached_input_tokens || 0;
|
|
@@ -943,7 +966,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
943
966
|
sa.models.set(mdl, { model: mdl, model_id: mdl, totals: { 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, total_cost_usd: "0" } });
|
|
944
967
|
const ma = sa.models.get(mdl);
|
|
945
968
|
ma.totals.total_tokens += row.total_tokens || 0;
|
|
946
|
-
ma.totals.billable_total_tokens += row.total_tokens
|
|
969
|
+
ma.totals.billable_total_tokens += row.billable_total_tokens ?? row.total_tokens ?? 0;
|
|
947
970
|
ma.totals.input_tokens += row.input_tokens || 0;
|
|
948
971
|
ma.totals.output_tokens += row.output_tokens || 0;
|
|
949
972
|
ma.totals.cached_input_tokens += row.cached_input_tokens || 0;
|
|
@@ -968,7 +991,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
968
991
|
});
|
|
969
992
|
|
|
970
993
|
json(res, {
|
|
971
|
-
from, to, days: 0, sources,
|
|
994
|
+
from, to, days: 0, scope, excluded_sources: excludedSources, sources,
|
|
972
995
|
pricing: { model: "per-model", pricing_mode: "per_token_type", source: "litellm", effective_from: new Date().toISOString().slice(0, 10) },
|
|
973
996
|
});
|
|
974
997
|
return true;
|
|
@@ -1061,9 +1084,9 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1061
1084
|
if (p === "/functions/tokentracker-usage-hourly") {
|
|
1062
1085
|
const day = url.searchParams.get("day") || new Date().toISOString().slice(0, 10);
|
|
1063
1086
|
const timeZoneContext = getTimeZoneContext(url);
|
|
1064
|
-
const rows =
|
|
1087
|
+
const { rows, scope, excludedSources } = scopedQueueRows(qp, url);
|
|
1065
1088
|
const data = aggregateHourlyByDay(rows, day, timeZoneContext);
|
|
1066
|
-
json(res, { day, data });
|
|
1089
|
+
json(res, { day, scope, excluded_sources: excludedSources, data });
|
|
1067
1090
|
return true;
|
|
1068
1091
|
}
|
|
1069
1092
|
|
|
@@ -1072,7 +1095,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1072
1095
|
const from = url.searchParams.get("from") || "";
|
|
1073
1096
|
const to = url.searchParams.get("to") || "";
|
|
1074
1097
|
const timeZoneContext = getTimeZoneContext(url);
|
|
1075
|
-
const rows =
|
|
1098
|
+
const { rows, scope, excludedSources } = scopedQueueRows(qp, url);
|
|
1076
1099
|
const byMonth = new Map();
|
|
1077
1100
|
for (const row of rows) {
|
|
1078
1101
|
if (!row.hour_start) continue;
|
|
@@ -1091,7 +1114,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
1091
1114
|
a.reasoning_output_tokens += row.reasoning_output_tokens || 0;
|
|
1092
1115
|
a.conversation_count += row.conversation_count || 0;
|
|
1093
1116
|
}
|
|
1094
|
-
json(res, { from, to, data: Array.from(byMonth.values()).sort((a, b) => a.month.localeCompare(b.month)) });
|
|
1117
|
+
json(res, { from, to, scope, excluded_sources: excludedSources, data: Array.from(byMonth.values()).sort((a, b) => a.month.localeCompare(b.month)) });
|
|
1095
1118
|
return true;
|
|
1096
1119
|
}
|
|
1097
1120
|
|