@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ipv9/tokentracker-cli",
3
- "version": "0.39.4",
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
@@ -13,7 +13,7 @@ async function run(argv) {
13
13
 
14
14
  // No args → launch dashboard
15
15
  if (!command) {
16
- await cmdServe(argv);
16
+ await cmdServe(["--sync"]);
17
17
  return;
18
18
  }
19
19
 
@@ -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
@@ -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
+ };