@ipv9/tokentracker-cli 0.39.4 → 0.39.5
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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/commands/serve.js +18 -1
- package/src/commands/sync.js +59 -50
- package/src/lib/local-data-preflight.js +88 -0
- package/src/lib/pricing/seed-snapshot.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ipv9/tokentracker-cli",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.5",
|
|
4
4
|
"description": "Local-first token and cost dashboard for 22 AI coding tools, including Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Copilot, Antigravity, Zed, and Goose.",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
package/src/cli.js
CHANGED
package/src/commands/serve.js
CHANGED
|
@@ -6,6 +6,10 @@ const cp = require("node:child_process");
|
|
|
6
6
|
|
|
7
7
|
const { resolveTrackerPaths } = require("../lib/tracker-paths");
|
|
8
8
|
const { createLocalApiHandler, resolveQueuePath } = require("../lib/local-api");
|
|
9
|
+
const {
|
|
10
|
+
buildServeDataPreflightMessage,
|
|
11
|
+
summarizeQueueData,
|
|
12
|
+
} = require("../lib/local-data-preflight");
|
|
9
13
|
const { ensurePricingLoaded } = require("../lib/pricing");
|
|
10
14
|
const { serveStaticFile } = require("../lib/static-server");
|
|
11
15
|
const { openInBrowser } = require("../lib/browser-auth");
|
|
@@ -50,11 +54,12 @@ async function cmdServe(argv) {
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
// 1. Optional sync
|
|
57
|
+
let syncSummary = null;
|
|
53
58
|
if (opts.sync) {
|
|
54
59
|
process.stdout.write("Syncing local data...\n");
|
|
55
60
|
try {
|
|
56
61
|
const { cmdSync } = require("./sync");
|
|
57
|
-
await cmdSync(["--auto"]);
|
|
62
|
+
syncSummary = await cmdSync(["--auto"]);
|
|
58
63
|
process.stdout.write("Sync done.\n");
|
|
59
64
|
} catch (e) {
|
|
60
65
|
process.stdout.write(`Sync warning: ${e?.message || e}\n`);
|
|
@@ -65,6 +70,18 @@ async function cmdServe(argv) {
|
|
|
65
70
|
const queuePath = resolveQueuePath();
|
|
66
71
|
const dashboardDir = resolveDashboardDir();
|
|
67
72
|
|
|
73
|
+
if (opts.sync) {
|
|
74
|
+
try {
|
|
75
|
+
const queueSummary = await summarizeQueueData(queuePath);
|
|
76
|
+
const preflight = buildServeDataPreflightMessage({ queueSummary, syncSummary });
|
|
77
|
+
if (preflight?.message) {
|
|
78
|
+
process.stdout.write(`${preflight.message}\n`);
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
process.stdout.write(`Token data preflight warning: ${e?.message || e}\n`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
68
85
|
// 2.1 Refresh LiteLLM pricing data in the background. The seed snapshot is
|
|
69
86
|
// already loaded synchronously at require-time, so cost calculation is
|
|
70
87
|
// functional right now; ensurePricingLoaded() only upgrades to fresh
|
package/src/commands/sync.js
CHANGED
|
@@ -992,57 +992,65 @@ async function cmdSync(argv) {
|
|
|
992
992
|
}
|
|
993
993
|
}
|
|
994
994
|
|
|
995
|
+
const totalParsed =
|
|
996
|
+
parseResult.filesProcessed +
|
|
997
|
+
openclawResult.filesProcessed +
|
|
998
|
+
claudeResult.filesProcessed +
|
|
999
|
+
geminiResult.filesProcessed +
|
|
1000
|
+
antigravityResult.filesProcessed +
|
|
1001
|
+
opencodeResult.filesProcessed +
|
|
1002
|
+
cursorResult.recordsProcessed +
|
|
1003
|
+
kiroResult.recordsProcessed +
|
|
1004
|
+
kiroCliResult.recordsProcessed +
|
|
1005
|
+
hermesResult.recordsProcessed +
|
|
1006
|
+
kimiResult.recordsProcessed +
|
|
1007
|
+
kimiCodeResult.recordsProcessed +
|
|
1008
|
+
codebuddyResult.recordsProcessed +
|
|
1009
|
+
ompResult.recordsProcessed +
|
|
1010
|
+
piResult.recordsProcessed +
|
|
1011
|
+
craftResult.recordsProcessed +
|
|
1012
|
+
grokResult.recordsProcessed +
|
|
1013
|
+
copilotResult.recordsProcessed +
|
|
1014
|
+
kiloResult.messagesProcessed +
|
|
1015
|
+
kilocodeResult.recordsProcessed +
|
|
1016
|
+
roocodeResult.recordsProcessed +
|
|
1017
|
+
zedResult.recordsProcessed +
|
|
1018
|
+
gooseResult.recordsProcessed +
|
|
1019
|
+
droidResult.recordsProcessed;
|
|
1020
|
+
const totalBuckets =
|
|
1021
|
+
parseResult.bucketsQueued +
|
|
1022
|
+
openclawResult.bucketsQueued +
|
|
1023
|
+
claudeResult.bucketsQueued +
|
|
1024
|
+
geminiResult.bucketsQueued +
|
|
1025
|
+
antigravityResult.bucketsQueued +
|
|
1026
|
+
opencodeResult.bucketsQueued +
|
|
1027
|
+
cursorResult.bucketsQueued +
|
|
1028
|
+
kiroResult.bucketsQueued +
|
|
1029
|
+
kiroCliResult.bucketsQueued +
|
|
1030
|
+
hermesResult.bucketsQueued +
|
|
1031
|
+
kimiResult.bucketsQueued +
|
|
1032
|
+
kimiCodeResult.bucketsQueued +
|
|
1033
|
+
codebuddyResult.bucketsQueued +
|
|
1034
|
+
ompResult.bucketsQueued +
|
|
1035
|
+
piResult.bucketsQueued +
|
|
1036
|
+
craftResult.bucketsQueued +
|
|
1037
|
+
grokResult.bucketsQueued +
|
|
1038
|
+
copilotResult.bucketsQueued +
|
|
1039
|
+
kiloResult.bucketsQueued +
|
|
1040
|
+
kilocodeResult.bucketsQueued +
|
|
1041
|
+
roocodeResult.bucketsQueued +
|
|
1042
|
+
zedResult.bucketsQueued +
|
|
1043
|
+
gooseResult.bucketsQueued +
|
|
1044
|
+
droidResult.bucketsQueued;
|
|
1045
|
+
const summary = {
|
|
1046
|
+
totalParsed,
|
|
1047
|
+
totalBuckets,
|
|
1048
|
+
upload: uploadResult,
|
|
1049
|
+
uploadAttempted,
|
|
1050
|
+
pendingBytes,
|
|
1051
|
+
};
|
|
1052
|
+
|
|
995
1053
|
if (!opts.auto) {
|
|
996
|
-
const totalParsed =
|
|
997
|
-
parseResult.filesProcessed +
|
|
998
|
-
openclawResult.filesProcessed +
|
|
999
|
-
claudeResult.filesProcessed +
|
|
1000
|
-
geminiResult.filesProcessed +
|
|
1001
|
-
antigravityResult.filesProcessed +
|
|
1002
|
-
opencodeResult.filesProcessed +
|
|
1003
|
-
cursorResult.recordsProcessed +
|
|
1004
|
-
kiroResult.recordsProcessed +
|
|
1005
|
-
kiroCliResult.recordsProcessed +
|
|
1006
|
-
hermesResult.recordsProcessed +
|
|
1007
|
-
kimiResult.recordsProcessed +
|
|
1008
|
-
kimiCodeResult.recordsProcessed +
|
|
1009
|
-
codebuddyResult.recordsProcessed +
|
|
1010
|
-
ompResult.recordsProcessed +
|
|
1011
|
-
piResult.recordsProcessed +
|
|
1012
|
-
craftResult.recordsProcessed +
|
|
1013
|
-
grokResult.recordsProcessed +
|
|
1014
|
-
copilotResult.recordsProcessed +
|
|
1015
|
-
kiloResult.messagesProcessed +
|
|
1016
|
-
kilocodeResult.recordsProcessed +
|
|
1017
|
-
roocodeResult.recordsProcessed +
|
|
1018
|
-
zedResult.recordsProcessed +
|
|
1019
|
-
gooseResult.recordsProcessed +
|
|
1020
|
-
droidResult.recordsProcessed;
|
|
1021
|
-
const totalBuckets =
|
|
1022
|
-
parseResult.bucketsQueued +
|
|
1023
|
-
openclawResult.bucketsQueued +
|
|
1024
|
-
claudeResult.bucketsQueued +
|
|
1025
|
-
geminiResult.bucketsQueued +
|
|
1026
|
-
antigravityResult.bucketsQueued +
|
|
1027
|
-
opencodeResult.bucketsQueued +
|
|
1028
|
-
cursorResult.bucketsQueued +
|
|
1029
|
-
kiroResult.bucketsQueued +
|
|
1030
|
-
kiroCliResult.bucketsQueued +
|
|
1031
|
-
hermesResult.bucketsQueued +
|
|
1032
|
-
kimiResult.bucketsQueued +
|
|
1033
|
-
kimiCodeResult.bucketsQueued +
|
|
1034
|
-
codebuddyResult.bucketsQueued +
|
|
1035
|
-
ompResult.bucketsQueued +
|
|
1036
|
-
piResult.bucketsQueued +
|
|
1037
|
-
craftResult.bucketsQueued +
|
|
1038
|
-
grokResult.bucketsQueued +
|
|
1039
|
-
copilotResult.bucketsQueued +
|
|
1040
|
-
kiloResult.bucketsQueued +
|
|
1041
|
-
kilocodeResult.bucketsQueued +
|
|
1042
|
-
roocodeResult.bucketsQueued +
|
|
1043
|
-
zedResult.bucketsQueued +
|
|
1044
|
-
gooseResult.bucketsQueued +
|
|
1045
|
-
droidResult.bucketsQueued;
|
|
1046
1054
|
process.stdout.write(
|
|
1047
1055
|
[
|
|
1048
1056
|
"Sync finished:",
|
|
@@ -1060,6 +1068,7 @@ async function cmdSync(argv) {
|
|
|
1060
1068
|
.join("\n"),
|
|
1061
1069
|
);
|
|
1062
1070
|
}
|
|
1071
|
+
return summary;
|
|
1063
1072
|
} finally {
|
|
1064
1073
|
progress?.stop();
|
|
1065
1074
|
await lock.release();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const fs = require("node:fs/promises");
|
|
2
|
+
|
|
3
|
+
async function summarizeQueueData(queuePath) {
|
|
4
|
+
const raw = await fs.readFile(queuePath, "utf8").catch((error) => {
|
|
5
|
+
if (error?.code === "ENOENT") return "";
|
|
6
|
+
throw error;
|
|
7
|
+
});
|
|
8
|
+
const rowsByBucket = new Map();
|
|
9
|
+
let malformedLines = 0;
|
|
10
|
+
let lineCount = 0;
|
|
11
|
+
|
|
12
|
+
for (const line of raw.split("\n")) {
|
|
13
|
+
if (!line.trim()) continue;
|
|
14
|
+
lineCount += 1;
|
|
15
|
+
let row;
|
|
16
|
+
try {
|
|
17
|
+
row = JSON.parse(line);
|
|
18
|
+
} catch (_error) {
|
|
19
|
+
malformedLines += 1;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const hourStart = typeof row?.hour_start === "string" ? row.hour_start : "";
|
|
23
|
+
if (!hourStart) continue;
|
|
24
|
+
const source = typeof row?.source === "string" && row.source.trim() ? row.source.trim() : "codex";
|
|
25
|
+
const model = typeof row?.model === "string" && row.model.trim() ? row.model.trim() : "unknown";
|
|
26
|
+
rowsByBucket.set(`${source}|${model}|${hourStart}`, row);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let totalTokens = 0;
|
|
30
|
+
let billableTotalTokens = 0;
|
|
31
|
+
const sources = new Set();
|
|
32
|
+
for (const row of rowsByBucket.values()) {
|
|
33
|
+
totalTokens += Number(row?.total_tokens || 0);
|
|
34
|
+
billableTotalTokens += Number(row?.billable_total_tokens ?? row?.total_tokens ?? 0);
|
|
35
|
+
if (typeof row?.source === "string" && row.source.trim()) {
|
|
36
|
+
sources.add(row.source.trim());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
queuePath,
|
|
42
|
+
lineCount,
|
|
43
|
+
malformedLines,
|
|
44
|
+
bucketCount: rowsByBucket.size,
|
|
45
|
+
totalTokens,
|
|
46
|
+
billableTotalTokens,
|
|
47
|
+
sources: Array.from(sources).sort(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildServeDataPreflightMessage({ queueSummary, syncSummary } = {}) {
|
|
52
|
+
const totalTokens = Number(queueSummary?.totalTokens || 0);
|
|
53
|
+
const parsedFiles = Number(syncSummary?.totalParsed || 0);
|
|
54
|
+
const queuedBuckets = Number(syncSummary?.totalBuckets || 0);
|
|
55
|
+
|
|
56
|
+
if (totalTokens > 0) {
|
|
57
|
+
const sources = Array.isArray(queueSummary?.sources) ? queueSummary.sources : [];
|
|
58
|
+
return {
|
|
59
|
+
status: "ok",
|
|
60
|
+
message: `Token data preflight: ${queueSummary.bucketCount} buckets, ${totalTokens.toLocaleString()} tokens${sources.length ? ` (${sources.join(", ")})` : ""}.`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (parsedFiles > 0 || queuedBuckets > 0 || Number(queueSummary?.lineCount || 0) > 0) {
|
|
65
|
+
return {
|
|
66
|
+
status: "warn",
|
|
67
|
+
message: [
|
|
68
|
+
"Token data preflight warning: local data was parsed, but the dashboard queue still has 0 tokens.",
|
|
69
|
+
`Parsed files: ${parsedFiles}; new buckets: ${queuedBuckets}; queue rows: ${queueSummary?.lineCount || 0}.`,
|
|
70
|
+
"Run `tokentracker status --light` or `tokentracker doctor` on this machine to check readable tool logs and hook state.",
|
|
71
|
+
].join("\n"),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
status: "warn",
|
|
77
|
+
message: [
|
|
78
|
+
"Token data preflight warning: no local token data was found after sync.",
|
|
79
|
+
"The dashboard can open, but Total tokens will stay empty until this machine has readable AI tool session logs.",
|
|
80
|
+
"Run `tokentracker status --light` or `tokentracker doctor` on this machine to see which providers are configured or skipped.",
|
|
81
|
+
].join("\n"),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
summarizeQueueData,
|
|
87
|
+
buildServeDataPreflightMessage,
|
|
88
|
+
};
|