tokentracker-cli 0.2.25 → 0.2.27
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/{AsciiBox-BTY8AuX7.js → AsciiBox-BZ2xYyXa.js} +1 -1
- package/dashboard/dist/assets/{DashboardPage-BNW-6Myn.js → DashboardPage-ynq92wZf.js} +1 -1
- package/dashboard/dist/assets/{LandingExtras-C6oIZxN4.js → LandingExtras-BUwK_c-9.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardPage-CdAHOBHv.js → LeaderboardPage-IzN8LibU.js} +1 -1
- package/dashboard/dist/assets/{LeaderboardProfilePage-CPBD0FCr.js → LeaderboardProfilePage-3JjGYRaz.js} +1 -1
- package/dashboard/dist/assets/{MatrixRain-hFJ62iN5.js → MatrixRain-D7Pm88QF.js} +1 -1
- package/dashboard/dist/assets/{MatrixShell-BfXpCNQj.js → MatrixShell-BtjokX5f.js} +1 -1
- package/dashboard/dist/assets/{main-DepLUlUX.js → main-oIbJf8pA.js} +4 -4
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/lib/local-api.js +12 -12
- package/src/lib/rollout.js +33 -7
- package/README.old.md +0 -324
- package/README.zh-CN.md +0 -440
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
]
|
|
108
108
|
}
|
|
109
109
|
</script>
|
|
110
|
-
<script type="module" crossorigin src="/assets/main-
|
|
110
|
+
<script type="module" crossorigin src="/assets/main-oIbJf8pA.js"></script>
|
|
111
111
|
<link rel="stylesheet" crossorigin href="/assets/main-hwTpulbk.css">
|
|
112
112
|
</head>
|
|
113
113
|
<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-oIbJf8pA.js"></script>
|
|
55
55
|
<link rel="stylesheet" crossorigin href="/assets/main-hwTpulbk.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
package/package.json
CHANGED
package/src/lib/local-api.js
CHANGED
|
@@ -12,14 +12,7 @@ const TRACKER_BIN = path.resolve(__dirname, "../../bin/tracker.js");
|
|
|
12
12
|
|
|
13
13
|
function resolveQueuePath() {
|
|
14
14
|
const home = os.homedir();
|
|
15
|
-
|
|
16
|
-
path.join(home, ".tokentracker", "tracker", "queue.jsonl"),
|
|
17
|
-
path.join(home, ".vibeusage", "tracker", "queue.jsonl"),
|
|
18
|
-
];
|
|
19
|
-
for (const p of candidates) {
|
|
20
|
-
if (fs.existsSync(p)) return p;
|
|
21
|
-
}
|
|
22
|
-
return candidates[0];
|
|
15
|
+
return path.join(home, ".tokentracker", "tracker", "queue.jsonl");
|
|
23
16
|
}
|
|
24
17
|
|
|
25
18
|
function readQueueData(queuePath) {
|
|
@@ -46,6 +39,7 @@ function aggregateByDay(rows) {
|
|
|
46
39
|
input_tokens: 0,
|
|
47
40
|
output_tokens: 0,
|
|
48
41
|
cached_input_tokens: 0,
|
|
42
|
+
cache_creation_input_tokens: 0,
|
|
49
43
|
reasoning_output_tokens: 0,
|
|
50
44
|
conversation_count: 0,
|
|
51
45
|
});
|
|
@@ -56,6 +50,7 @@ function aggregateByDay(rows) {
|
|
|
56
50
|
a.input_tokens += row.input_tokens || 0;
|
|
57
51
|
a.output_tokens += row.output_tokens || 0;
|
|
58
52
|
a.cached_input_tokens += row.cached_input_tokens || 0;
|
|
53
|
+
a.cache_creation_input_tokens += row.cache_creation_input_tokens || 0;
|
|
59
54
|
a.reasoning_output_tokens += row.reasoning_output_tokens || 0;
|
|
60
55
|
a.conversation_count += row.conversation_count || 0;
|
|
61
56
|
}
|
|
@@ -261,11 +256,12 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
261
256
|
acc.input_tokens += r.input_tokens;
|
|
262
257
|
acc.output_tokens += r.output_tokens;
|
|
263
258
|
acc.cached_input_tokens += r.cached_input_tokens;
|
|
259
|
+
acc.cache_creation_input_tokens += r.cache_creation_input_tokens;
|
|
264
260
|
acc.reasoning_output_tokens += r.reasoning_output_tokens;
|
|
265
261
|
acc.conversation_count += r.conversation_count;
|
|
266
262
|
return acc;
|
|
267
263
|
},
|
|
268
|
-
{ total_tokens: 0, billable_total_tokens: 0, input_tokens: 0, output_tokens: 0, cached_input_tokens: 0, reasoning_output_tokens: 0, conversation_count: 0 },
|
|
264
|
+
{ 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 },
|
|
269
265
|
);
|
|
270
266
|
const totalCost = (totals.total_tokens * 1.75) / 1_000_000;
|
|
271
267
|
|
|
@@ -379,22 +375,24 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
379
375
|
const src = row.source || "unknown";
|
|
380
376
|
const mdl = row.model || "unknown";
|
|
381
377
|
if (!bySource.has(src))
|
|
382
|
-
bySource.set(src, { source: src, totals: { total_tokens: 0, billable_total_tokens: 0, input_tokens: 0, output_tokens: 0, cached_input_tokens: 0, reasoning_output_tokens: 0, total_cost_usd: "0" }, models: new Map() });
|
|
378
|
+
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() });
|
|
383
379
|
const sa = bySource.get(src);
|
|
384
380
|
sa.totals.total_tokens += row.total_tokens || 0;
|
|
385
381
|
sa.totals.billable_total_tokens += row.total_tokens || 0;
|
|
386
382
|
sa.totals.input_tokens += row.input_tokens || 0;
|
|
387
383
|
sa.totals.output_tokens += row.output_tokens || 0;
|
|
388
384
|
sa.totals.cached_input_tokens += row.cached_input_tokens || 0;
|
|
385
|
+
sa.totals.cache_creation_input_tokens += row.cache_creation_input_tokens || 0;
|
|
389
386
|
sa.totals.reasoning_output_tokens += row.reasoning_output_tokens || 0;
|
|
390
387
|
if (!sa.models.has(mdl))
|
|
391
|
-
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, reasoning_output_tokens: 0, total_cost_usd: "0" } });
|
|
388
|
+
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" } });
|
|
392
389
|
const ma = sa.models.get(mdl);
|
|
393
390
|
ma.totals.total_tokens += row.total_tokens || 0;
|
|
394
391
|
ma.totals.billable_total_tokens += row.total_tokens || 0;
|
|
395
392
|
ma.totals.input_tokens += row.input_tokens || 0;
|
|
396
393
|
ma.totals.output_tokens += row.output_tokens || 0;
|
|
397
394
|
ma.totals.cached_input_tokens += row.cached_input_tokens || 0;
|
|
395
|
+
ma.totals.cache_creation_input_tokens += row.cache_creation_input_tokens || 0;
|
|
398
396
|
ma.totals.reasoning_output_tokens += row.reasoning_output_tokens || 0;
|
|
399
397
|
}
|
|
400
398
|
|
|
@@ -471,6 +469,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
471
469
|
input_tokens: r.input_tokens || 0,
|
|
472
470
|
output_tokens: r.output_tokens || 0,
|
|
473
471
|
cached_input_tokens: r.cached_input_tokens || 0,
|
|
472
|
+
cache_creation_input_tokens: r.cache_creation_input_tokens || 0,
|
|
474
473
|
reasoning_output_tokens: r.reasoning_output_tokens || 0,
|
|
475
474
|
conversation_count: r.conversation_count || 0,
|
|
476
475
|
}));
|
|
@@ -490,13 +489,14 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
490
489
|
if (day < from || day > to) continue;
|
|
491
490
|
const month = day.slice(0, 7);
|
|
492
491
|
if (!byMonth.has(month))
|
|
493
|
-
byMonth.set(month, { month, total_tokens: 0, billable_total_tokens: 0, input_tokens: 0, output_tokens: 0, cached_input_tokens: 0, reasoning_output_tokens: 0, conversation_count: 0 });
|
|
492
|
+
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 });
|
|
494
493
|
const a = byMonth.get(month);
|
|
495
494
|
a.total_tokens += row.total_tokens || 0;
|
|
496
495
|
a.billable_total_tokens += row.total_tokens || 0;
|
|
497
496
|
a.input_tokens += row.input_tokens || 0;
|
|
498
497
|
a.output_tokens += row.output_tokens || 0;
|
|
499
498
|
a.cached_input_tokens += row.cached_input_tokens || 0;
|
|
499
|
+
a.cache_creation_input_tokens += row.cache_creation_input_tokens || 0;
|
|
500
500
|
a.reasoning_output_tokens += row.reasoning_output_tokens || 0;
|
|
501
501
|
a.conversation_count += row.conversation_count || 0;
|
|
502
502
|
}
|
package/src/lib/rollout.js
CHANGED
|
@@ -206,6 +206,7 @@ async function parseClaudeIncremental({
|
|
|
206
206
|
const projectMetaCache = projectEnabled ? new Map() : null;
|
|
207
207
|
const publicRepoCache = projectEnabled ? new Map() : null;
|
|
208
208
|
const touchedBuckets = new Set();
|
|
209
|
+
const seenMessageHashes = new Set();
|
|
209
210
|
const defaultSource = normalizeSourceInput(source) || "claude";
|
|
210
211
|
|
|
211
212
|
if (!cursors.files || typeof cursors.files !== "object") {
|
|
@@ -250,6 +251,7 @@ async function parseClaudeIncremental({
|
|
|
250
251
|
projectTouchedBuckets,
|
|
251
252
|
projectRef,
|
|
252
253
|
projectKey,
|
|
254
|
+
seenMessageHashes,
|
|
253
255
|
});
|
|
254
256
|
|
|
255
257
|
cursors.files[key] = {
|
|
@@ -822,6 +824,7 @@ async function parseClaudeFile({
|
|
|
822
824
|
projectTouchedBuckets,
|
|
823
825
|
projectRef,
|
|
824
826
|
projectKey,
|
|
827
|
+
seenMessageHashes,
|
|
825
828
|
}) {
|
|
826
829
|
const st = await fs.stat(filePath).catch(() => null);
|
|
827
830
|
if (!st || !st.isFile()) return { endOffset: startOffset, eventsAggregated: 0 };
|
|
@@ -845,6 +848,16 @@ async function parseClaudeFile({
|
|
|
845
848
|
const usage = obj?.message?.usage || obj?.usage;
|
|
846
849
|
if (!usage || typeof usage !== "object") continue;
|
|
847
850
|
|
|
851
|
+
if (seenMessageHashes) {
|
|
852
|
+
const msgId = obj?.message?.id;
|
|
853
|
+
const reqId = obj?.requestId;
|
|
854
|
+
if (msgId && reqId) {
|
|
855
|
+
const hash = `${msgId}:${reqId}`;
|
|
856
|
+
if (seenMessageHashes.has(hash)) continue;
|
|
857
|
+
seenMessageHashes.add(hash);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
848
861
|
const model = normalizeModelInput(obj?.message?.model || obj?.model) || DEFAULT_MODEL;
|
|
849
862
|
const tokenTimestamp = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
850
863
|
if (!tokenTimestamp) continue;
|
|
@@ -1180,6 +1193,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
1180
1193
|
hour_start: group.hourStart,
|
|
1181
1194
|
input_tokens: zeroTotals.input_tokens,
|
|
1182
1195
|
cached_input_tokens: zeroTotals.cached_input_tokens,
|
|
1196
|
+
cache_creation_input_tokens: zeroTotals.cache_creation_input_tokens,
|
|
1183
1197
|
output_tokens: zeroTotals.output_tokens,
|
|
1184
1198
|
reasoning_output_tokens: zeroTotals.reasoning_output_tokens,
|
|
1185
1199
|
total_tokens: zeroTotals.total_tokens,
|
|
@@ -1227,6 +1241,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
1227
1241
|
hour_start: group.hourStart,
|
|
1228
1242
|
input_tokens: totals.input_tokens,
|
|
1229
1243
|
cached_input_tokens: totals.cached_input_tokens,
|
|
1244
|
+
cache_creation_input_tokens: totals.cache_creation_input_tokens,
|
|
1230
1245
|
output_tokens: totals.output_tokens,
|
|
1231
1246
|
reasoning_output_tokens: totals.reasoning_output_tokens,
|
|
1232
1247
|
total_tokens: totals.total_tokens,
|
|
@@ -1274,6 +1289,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
1274
1289
|
hour_start: group.hourStart,
|
|
1275
1290
|
input_tokens: zeroTotals.input_tokens,
|
|
1276
1291
|
cached_input_tokens: zeroTotals.cached_input_tokens,
|
|
1292
|
+
cache_creation_input_tokens: zeroTotals.cache_creation_input_tokens,
|
|
1277
1293
|
output_tokens: zeroTotals.output_tokens,
|
|
1278
1294
|
reasoning_output_tokens: zeroTotals.reasoning_output_tokens,
|
|
1279
1295
|
total_tokens: zeroTotals.total_tokens,
|
|
@@ -1294,6 +1310,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
1294
1310
|
hour_start: group.hourStart,
|
|
1295
1311
|
input_tokens: unknownBucket.totals.input_tokens,
|
|
1296
1312
|
cached_input_tokens: unknownBucket.totals.cached_input_tokens,
|
|
1313
|
+
cache_creation_input_tokens: unknownBucket.totals.cache_creation_input_tokens,
|
|
1297
1314
|
output_tokens: unknownBucket.totals.output_tokens,
|
|
1298
1315
|
reasoning_output_tokens: unknownBucket.totals.reasoning_output_tokens,
|
|
1299
1316
|
total_tokens: unknownBucket.totals.total_tokens,
|
|
@@ -1339,6 +1356,7 @@ async function enqueueTouchedBuckets({ queuePath, hourlyState, touchedBuckets })
|
|
|
1339
1356
|
hour_start: group.hourStart,
|
|
1340
1357
|
input_tokens: group.totals.input_tokens,
|
|
1341
1358
|
cached_input_tokens: group.totals.cached_input_tokens,
|
|
1359
|
+
cache_creation_input_tokens: group.totals.cache_creation_input_tokens,
|
|
1342
1360
|
output_tokens: group.totals.output_tokens,
|
|
1343
1361
|
reasoning_output_tokens: group.totals.reasoning_output_tokens,
|
|
1344
1362
|
total_tokens: group.totals.total_tokens,
|
|
@@ -1392,6 +1410,7 @@ async function enqueueTouchedProjectBuckets({
|
|
|
1392
1410
|
hour_start: bucket.hour_start,
|
|
1393
1411
|
input_tokens: totals.input_tokens,
|
|
1394
1412
|
cached_input_tokens: totals.cached_input_tokens,
|
|
1413
|
+
cache_creation_input_tokens: totals.cache_creation_input_tokens,
|
|
1395
1414
|
output_tokens: totals.output_tokens,
|
|
1396
1415
|
reasoning_output_tokens: totals.reasoning_output_tokens,
|
|
1397
1416
|
total_tokens: totals.total_tokens,
|
|
@@ -1646,6 +1665,7 @@ function initTotals() {
|
|
|
1646
1665
|
return {
|
|
1647
1666
|
input_tokens: 0,
|
|
1648
1667
|
cached_input_tokens: 0,
|
|
1668
|
+
cache_creation_input_tokens: 0,
|
|
1649
1669
|
output_tokens: 0,
|
|
1650
1670
|
reasoning_output_tokens: 0,
|
|
1651
1671
|
total_tokens: 0,
|
|
@@ -1656,6 +1676,7 @@ function initTotals() {
|
|
|
1656
1676
|
function addTotals(target, delta) {
|
|
1657
1677
|
target.input_tokens += delta.input_tokens || 0;
|
|
1658
1678
|
target.cached_input_tokens += delta.cached_input_tokens || 0;
|
|
1679
|
+
target.cache_creation_input_tokens += delta.cache_creation_input_tokens || 0;
|
|
1659
1680
|
target.output_tokens += delta.output_tokens || 0;
|
|
1660
1681
|
target.reasoning_output_tokens += delta.reasoning_output_tokens || 0;
|
|
1661
1682
|
target.total_tokens += delta.total_tokens || 0;
|
|
@@ -1666,6 +1687,7 @@ function totalsKey(totals) {
|
|
|
1666
1687
|
return [
|
|
1667
1688
|
totals.input_tokens || 0,
|
|
1668
1689
|
totals.cached_input_tokens || 0,
|
|
1690
|
+
totals.cache_creation_input_tokens || 0,
|
|
1669
1691
|
totals.output_tokens || 0,
|
|
1670
1692
|
totals.reasoning_output_tokens || 0,
|
|
1671
1693
|
totals.total_tokens || 0,
|
|
@@ -1998,6 +2020,7 @@ function normalizeGeminiTokens(tokens) {
|
|
|
1998
2020
|
return {
|
|
1999
2021
|
input_tokens: input,
|
|
2000
2022
|
cached_input_tokens: cached,
|
|
2023
|
+
cache_creation_input_tokens: 0,
|
|
2001
2024
|
output_tokens: output + tool,
|
|
2002
2025
|
reasoning_output_tokens: thoughts,
|
|
2003
2026
|
total_tokens: total,
|
|
@@ -2011,12 +2034,12 @@ function normalizeOpencodeTokens(tokens) {
|
|
|
2011
2034
|
const reasoning = toNonNegativeInt(tokens.reasoning);
|
|
2012
2035
|
const cached = toNonNegativeInt(tokens.cache?.read);
|
|
2013
2036
|
const cacheWrite = toNonNegativeInt(tokens.cache?.write);
|
|
2014
|
-
|
|
2015
|
-
const total = input + output + reasoning;
|
|
2037
|
+
const total = input + output + reasoning + cached + cacheWrite;
|
|
2016
2038
|
|
|
2017
2039
|
return {
|
|
2018
2040
|
input_tokens: input,
|
|
2019
2041
|
cached_input_tokens: cached,
|
|
2042
|
+
cache_creation_input_tokens: cacheWrite,
|
|
2020
2043
|
output_tokens: output,
|
|
2021
2044
|
reasoning_output_tokens: reasoning,
|
|
2022
2045
|
total_tokens: total,
|
|
@@ -2122,6 +2145,7 @@ function normalizeUsage(u) {
|
|
|
2122
2145
|
for (const k of [
|
|
2123
2146
|
"input_tokens",
|
|
2124
2147
|
"cached_input_tokens",
|
|
2148
|
+
"cache_creation_input_tokens",
|
|
2125
2149
|
"output_tokens",
|
|
2126
2150
|
"reasoning_output_tokens",
|
|
2127
2151
|
"total_tokens",
|
|
@@ -2135,13 +2159,13 @@ function normalizeUsage(u) {
|
|
|
2135
2159
|
function normalizeClaudeUsage(u) {
|
|
2136
2160
|
const inputTokens = toNonNegativeInt(u?.input_tokens);
|
|
2137
2161
|
const outputTokens = toNonNegativeInt(u?.output_tokens);
|
|
2138
|
-
const
|
|
2139
|
-
const
|
|
2140
|
-
|
|
2141
|
-
: inputTokens + outputTokens;
|
|
2162
|
+
const cacheCreation = toNonNegativeInt(u?.cache_creation_input_tokens);
|
|
2163
|
+
const cacheRead = toNonNegativeInt(u?.cache_read_input_tokens);
|
|
2164
|
+
const totalTokens = inputTokens + outputTokens + cacheCreation + cacheRead;
|
|
2142
2165
|
return {
|
|
2143
2166
|
input_tokens: inputTokens,
|
|
2144
|
-
cached_input_tokens:
|
|
2167
|
+
cached_input_tokens: cacheRead,
|
|
2168
|
+
cache_creation_input_tokens: cacheCreation,
|
|
2145
2169
|
output_tokens: outputTokens,
|
|
2146
2170
|
reasoning_output_tokens: 0,
|
|
2147
2171
|
total_tokens: totalTokens,
|
|
@@ -2157,6 +2181,7 @@ function isAllZeroUsage(u) {
|
|
|
2157
2181
|
for (const k of [
|
|
2158
2182
|
"input_tokens",
|
|
2159
2183
|
"cached_input_tokens",
|
|
2184
|
+
"cache_creation_input_tokens",
|
|
2160
2185
|
"output_tokens",
|
|
2161
2186
|
"reasoning_output_tokens",
|
|
2162
2187
|
"total_tokens",
|
|
@@ -2170,6 +2195,7 @@ function sameUsage(a, b) {
|
|
|
2170
2195
|
for (const k of [
|
|
2171
2196
|
"input_tokens",
|
|
2172
2197
|
"cached_input_tokens",
|
|
2198
|
+
"cache_creation_input_tokens",
|
|
2173
2199
|
"output_tokens",
|
|
2174
2200
|
"reasoning_output_tokens",
|
|
2175
2201
|
"total_tokens",
|
package/README.old.md
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
<img src="dashboard/public/icon-192.png" width="96" alt="VibeUsage Icon" />
|
|
4
|
-
|
|
5
|
-
# 🟢 VIBEUSAGE
|
|
6
|
-
|
|
7
|
-
**QUANTIFY YOUR AI OUTPUT**
|
|
8
|
-
_Real-time AI Analytics for Codex CLI_
|
|
9
|
-
|
|
10
|
-
[**www.vibeusage.cc**](https://www.vibeusage.cc)
|
|
11
|
-
|
|
12
|
-
[](https://opensource.org/licenses/MIT)
|
|
13
|
-
[](https://nodejs.org/)
|
|
14
|
-
[](https://www.apple.com/macos/)
|
|
15
|
-
|
|
16
|
-
[**English**](README.md) • [**中文说明**](README.zh-CN.md)
|
|
17
|
-
|
|
18
|
-
[**Documentation**](docs/) • [**Dashboard**](dashboard/) • [**Backend API**](BACKEND_API.md)
|
|
19
|
-
|
|
20
|
-
<br/>
|
|
21
|
-
|
|
22
|
-
<img src="docs/screenshots/dashboard.png" width="900" alt="VibeUsage Dashboard Preview"/>
|
|
23
|
-
|
|
24
|
-
</div>
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## 🌌 Overview
|
|
29
|
-
|
|
30
|
-
**VibeUsage** is an intelligent token usage tracking system designed specifically for macOS developers. Through the all-new **Matrix-A Design System**, it provides a high-fidelity cyberpunk-style dashboard that transforms your **AI Output** into quantifiable metrics, supported by the **Neural Divergence Map** for real-time monitoring of multi-model compute distribution.
|
|
31
|
-
|
|
32
|
-
> [!TIP] > **Core Index**: Our signature metric that reflects your flow state by analyzing token consumption rates and patterns.
|
|
33
|
-
|
|
34
|
-
## 🔒 Privacy-First Architecture (Stealth Protocol)
|
|
35
|
-
|
|
36
|
-
We believe your code and thoughts are your own. VibeUsage is built with strict privacy pillars to ensure your data never leaves your control.
|
|
37
|
-
|
|
38
|
-
- 🛡️ **No Content Upload**: We never upload prompts or responses. We only compute token counts locally and send counts plus minimal metadata (timestamps, model, device).
|
|
39
|
-
- 📡 **Local Aggregation**: All token consumption analysis happens on your machine. We only relay quantized 30-minute usage buckets to the cloud.
|
|
40
|
-
- 🔐 **Hashed Identity**: Device tokens are hashed using SHA-256 server-side. Your raw credentials never exist in our database.
|
|
41
|
-
- 🔦 **Full Transparency**: Audit the sync logic yourself in `src/lib/rollout.js`. We literally only capture numbers and timestamps.
|
|
42
|
-
|
|
43
|
-
## 🚀 Key Features
|
|
44
|
-
|
|
45
|
-
- 📡 **Auto-Sync**: Real-time interception of AI CLI pipes with **automatic background synchronization**. Once initialized, your tokens are tracked and synced without any manual commands.
|
|
46
|
-
- 🧭 **Universal-Sync**: Native support for multiple AI CLI tools:
|
|
47
|
-
- **Codex CLI** - OpenAI's official CLI
|
|
48
|
-
- **Every Code** - Community Codex alternative
|
|
49
|
-
- **Gemini CLI** - Google's AI CLI
|
|
50
|
-
- **Opencode** - AI coding assistant
|
|
51
|
-
- **Claude Code** - Anthropic's official CLI
|
|
52
|
-
- Whether it's GPT-4, Claude 3.5 Sonnet, or o1/Gemini, token consumption from all models is unified and counted.
|
|
53
|
-
- 📊 **Matrix Dashboard**: High-performance dashboard built with React + Vite, featuring the new **Matrix-A** design language.
|
|
54
|
-
- **Neural Divergence Map**: Visualize multi-engine load balancing and compute distribution.
|
|
55
|
-
- **Cost Intelligence**: Real-time, multi-dimensional cost breakdown and forecasting.
|
|
56
|
-
- **Activity Heatmap**: GitHub-style contribution graph with streak tracking.
|
|
57
|
-
- **Smart Notifications**: Non-intrusive system-level alerts using a Golden (Gold/Amber) visual style for high-value information.
|
|
58
|
-
- ⚡ **AI Analytics**: Deep analysis of Input/Output tokens, with dedicated tracking for Cached and Reasoning components.
|
|
59
|
-
- 📈 **Leaderboard**: Daily, weekly, monthly, and all-time rankings with privacy-safe display names.
|
|
60
|
-
- 🌐 **Public View**: Share your AI usage journey with a privacy-safe public profile.
|
|
61
|
-
- 📁 **Project Stats**: Track token usage by project/repository across all time.
|
|
62
|
-
- 🔒 **Identity Core**: Robust authentication and permission management to secure your development data.
|
|
63
|
-
|
|
64
|
-
### 🌌 Visual Preview
|
|
65
|
-
|
|
66
|
-
<img src="docs/screenshots/landing.png" width="900" alt="VibeUsage Landing Preview"/>
|
|
67
|
-
|
|
68
|
-
## 🛠️ Quick Start
|
|
69
|
-
|
|
70
|
-
### Installation
|
|
71
|
-
|
|
72
|
-
Initialize your environment once and forget it. VibeUsage handles all synchronization in the background automatically.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npx --yes vibeusage init
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Authentication Methods:**
|
|
79
|
-
|
|
80
|
-
1. **Browser Auth** (default) - Opens browser for secure authentication
|
|
81
|
-
2. **Link Code** - Use `--link-code` to authenticate via dashboard-generated code
|
|
82
|
-
3. **Password** - Direct password authentication (fallback)
|
|
83
|
-
4. **Access Token** - For CI/automated environments
|
|
84
|
-
|
|
85
|
-
**CLI Options:**
|
|
86
|
-
- `--yes` - Skip consent prompts in non-interactive environments
|
|
87
|
-
- `--dry-run` - Preview changes without writing files
|
|
88
|
-
- `--link-code <code>` - Authenticate using a link code from dashboard
|
|
89
|
-
- `--base-url <url>` - Override the default API endpoint
|
|
90
|
-
|
|
91
|
-
**Supported CLI Tools Auto-Configuration:**
|
|
92
|
-
|
|
93
|
-
| Tool | Config Location | Method |
|
|
94
|
-
|------|----------------|--------|
|
|
95
|
-
| Codex CLI | `~/.codex/config.toml` | `notify` hook |
|
|
96
|
-
| Every Code | `~/.code/config.toml` (or `CODE_HOME`) | `notify` hook |
|
|
97
|
-
| Gemini CLI | `~/.gemini/settings.json` (or `GEMINI_HOME`) | `SessionEnd` hook |
|
|
98
|
-
| Opencode | Global plugins | Message parser plugin |
|
|
99
|
-
| Claude Code | `~/.claude/hooks/` | Hook configuration |
|
|
100
|
-
|
|
101
|
-
Once `init` completes, all supported CLI tools are automatically configured for data sync. No further intervention required.
|
|
102
|
-
|
|
103
|
-
### Sync & Status
|
|
104
|
-
|
|
105
|
-
While sync happens automatically, you can manually trigger a synchronization or check status anytime:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
# Manually sync latest local session data (Optional)
|
|
109
|
-
npx --yes vibeusage sync
|
|
110
|
-
|
|
111
|
-
# Check current link status
|
|
112
|
-
npx --yes vibeusage status
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Doctor
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
# Run health checks
|
|
119
|
-
npx --yes vibeusage doctor
|
|
120
|
-
|
|
121
|
-
# Emit JSON report (and write to file)
|
|
122
|
-
npx --yes vibeusage doctor --json --out doctor.json
|
|
123
|
-
|
|
124
|
-
# Override base URL (diagnostics only)
|
|
125
|
-
npx --yes vibeusage doctor --base-url https://example.invalid
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### Uninstall
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
# Standard uninstall (keeps data)
|
|
132
|
-
npx --yes vibeusage uninstall
|
|
133
|
-
|
|
134
|
-
# Full purge - removes all data including config and cached sessions
|
|
135
|
-
npx --yes vibeusage uninstall --purge
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Log Sources
|
|
139
|
-
|
|
140
|
-
| Tool | Log Location | Override Env |
|
|
141
|
-
|------|-------------|--------------|
|
|
142
|
-
| Codex CLI | `~/.codex/sessions/**/rollout-*.jsonl` | `CODEX_HOME` |
|
|
143
|
-
| Every Code | `~/.code/sessions/**/rollout-*.jsonl` | `CODE_HOME` |
|
|
144
|
-
| Gemini CLI | `~/.gemini/tmp/**/chats/session-*.json` | `GEMINI_HOME` |
|
|
145
|
-
| Opencode | `~/.opencode/messages/*.json` | - |
|
|
146
|
-
| Claude Code | Parsed from hook output | - |
|
|
147
|
-
|
|
148
|
-
## 🔧 Environment Variables
|
|
149
|
-
|
|
150
|
-
### Core Settings
|
|
151
|
-
|
|
152
|
-
| Variable | Description | Default |
|
|
153
|
-
|----------|-------------|---------|
|
|
154
|
-
| `VIBEUSAGE_HTTP_TIMEOUT_MS` | CLI HTTP timeout in ms (`0` disables, clamped `1000..120000`) | `20000` |
|
|
155
|
-
| `VITE_VIBEUSAGE_HTTP_TIMEOUT_MS` | Dashboard request timeout in ms (`0` disables, clamped `1000..30000`) | `15000` |
|
|
156
|
-
| `VIBEUSAGE_DEBUG` | Enable debug output (`1` or `true` to enable) | - |
|
|
157
|
-
| `VIBEUSAGE_DASHBOARD_URL` | Custom dashboard URL | `https://www.vibeusage.cc` |
|
|
158
|
-
| `VIBEUSAGE_INSFORGE_BASE_URL` | Custom API base URL | `https://5tmappuk.us-east.insforge.app` |
|
|
159
|
-
| `VIBEUSAGE_DEVICE_TOKEN` | Pre-configured device token (for CI) | - |
|
|
160
|
-
|
|
161
|
-
### CLI Tool Overrides
|
|
162
|
-
|
|
163
|
-
| Variable | Description | Default |
|
|
164
|
-
|----------|-------------|---------|
|
|
165
|
-
| `CODEX_HOME` | Codex CLI directory override | `~/.codex` |
|
|
166
|
-
| `CODE_HOME` | Every Code directory override | `~/.code` |
|
|
167
|
-
| `GEMINI_HOME` | Gemini CLI directory override | `~/.gemini` |
|
|
168
|
-
|
|
169
|
-
### Deprecated
|
|
170
|
-
|
|
171
|
-
- `VIBEUSAGE_ROLLUP_ENABLED`: Currently ignored; rollup aggregation is disabled pending table deployment.
|
|
172
|
-
|
|
173
|
-
## 🧰 Troubleshooting
|
|
174
|
-
|
|
175
|
-
### Debug Mode
|
|
176
|
-
|
|
177
|
-
Enable debug output to see detailed request/response information:
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
VIBEUSAGE_DEBUG=1 npx --yes vibeusage sync
|
|
181
|
-
# or
|
|
182
|
-
npx --yes vibeusage sync --debug
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Health Check
|
|
186
|
-
|
|
187
|
-
Run the built-in doctor command to diagnose issues:
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
# Basic health check
|
|
191
|
-
npx --yes vibeusage doctor
|
|
192
|
-
|
|
193
|
-
# JSON output for debugging
|
|
194
|
-
npx --yes vibeusage doctor --json --out doctor.json
|
|
195
|
-
|
|
196
|
-
# Test against a different endpoint
|
|
197
|
-
npx --yes vibeusage doctor --base-url https://your-instance.insforge.app
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### Streak shows 0 days while totals look correct
|
|
201
|
-
|
|
202
|
-
- Streak is defined as consecutive days ending today. If today's total is 0, streak will be 0.
|
|
203
|
-
- If you expect a non-zero streak, clear cached auth/heatmap data and sign in again:
|
|
204
|
-
|
|
205
|
-
```js
|
|
206
|
-
localStorage.removeItem("vibeusage.dashboard.auth.v1");
|
|
207
|
-
Object.keys(localStorage)
|
|
208
|
-
.filter((k) => k.startsWith("vibeusage.heatmap."))
|
|
209
|
-
.forEach((k) => localStorage.removeItem(k));
|
|
210
|
-
location.reload();
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
- Complete the landing page sign-in flow again after reload.
|
|
214
|
-
- Note: `insforge-auth-token` is not used by the dashboard; use `vibeusage.dashboard.auth.v1`.
|
|
215
|
-
|
|
216
|
-
### Sync Issues
|
|
217
|
-
|
|
218
|
-
If data isn't appearing in the dashboard:
|
|
219
|
-
|
|
220
|
-
1. Check status: `npx --yes vibeusage status`
|
|
221
|
-
2. Force manual sync: `npx --yes vibeusage sync`
|
|
222
|
-
3. Verify CLI tool hooks are configured (re-run `init` if needed)
|
|
223
|
-
4. Check debug output: `VIBEUSAGE_DEBUG=1 npx vibeusage sync`
|
|
224
|
-
|
|
225
|
-
### Timeout Errors
|
|
226
|
-
|
|
227
|
-
Increase HTTP timeout for slow connections:
|
|
228
|
-
|
|
229
|
-
```bash
|
|
230
|
-
VIBEUSAGE_HTTP_TIMEOUT_MS=60000 npx --yes vibeusage sync
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## 🏗️ Architecture
|
|
234
|
-
|
|
235
|
-
```mermaid
|
|
236
|
-
graph TD
|
|
237
|
-
A[Codex CLI] -->|Rollout Logs| F(Tracker CLI)
|
|
238
|
-
B[Every Code] -->|Rollout Logs| F
|
|
239
|
-
C[Gemini CLI] -->|Session Logs| F
|
|
240
|
-
D[Opencode] -->|Message Logs| F
|
|
241
|
-
E[Claude Code] -->|Hook Output| F
|
|
242
|
-
F -->|AI Tokens| G{Core Relay}
|
|
243
|
-
G --> H[VibeUsage Dashboard]
|
|
244
|
-
G --> I[AI Analytics Engine]
|
|
245
|
-
G --> J[Leaderboard Service]
|
|
246
|
-
G --> K[Public View API]
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### Components
|
|
250
|
-
|
|
251
|
-
- **Tracker CLI** (`src/`): Node.js CLI that parses logs from multiple AI tools and syncs token data
|
|
252
|
-
- **Core Relay** (InsForge Edge Functions): Serverless backend handling ingestion, aggregation, and API
|
|
253
|
-
- **Dashboard** (`dashboard/`): React + Vite frontend for visualization
|
|
254
|
-
- **AI Analytics Engine**: Cost calculation, model breakdown, and usage forecasting
|
|
255
|
-
|
|
256
|
-
## 💻 Developer Guide
|
|
257
|
-
|
|
258
|
-
To run locally or contribute:
|
|
259
|
-
|
|
260
|
-
### Dashboard Development
|
|
261
|
-
|
|
262
|
-
```bash
|
|
263
|
-
# Install dependencies
|
|
264
|
-
cd dashboard
|
|
265
|
-
npm install
|
|
266
|
-
|
|
267
|
-
# Start dev server
|
|
268
|
-
npm run dev
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Debug Payload (Usage Endpoints)
|
|
272
|
-
|
|
273
|
-
When `debug=1` is included in a usage endpoint request, the response adds a `debug` object that helps the dashboard attribute slow queries without relying on response headers.
|
|
274
|
-
|
|
275
|
-
```ts
|
|
276
|
-
const res = await fetch(
|
|
277
|
-
`${baseUrl}/functions/vibeusage-usage-summary?from=2025-12-30&to=2025-12-30&debug=1`,
|
|
278
|
-
{
|
|
279
|
-
headers: { Authorization: `Bearer ${userJwt}` }
|
|
280
|
-
}
|
|
281
|
-
);
|
|
282
|
-
const data = await res.json();
|
|
283
|
-
|
|
284
|
-
if (data.debug) {
|
|
285
|
-
console.debug('usage debug', {
|
|
286
|
-
requestId: data.debug.request_id,
|
|
287
|
-
status: data.debug.status,
|
|
288
|
-
queryMs: data.debug.query_ms,
|
|
289
|
-
slowThresholdMs: data.debug.slow_threshold_ms,
|
|
290
|
-
slowQuery: data.debug.slow_query
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### Architecture Validation
|
|
296
|
-
|
|
297
|
-
```bash
|
|
298
|
-
# Validate Copy Registry
|
|
299
|
-
npm run validate:copy
|
|
300
|
-
|
|
301
|
-
# Run smoke tests
|
|
302
|
-
npm run smoke
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### Architecture Canvas Focus
|
|
306
|
-
|
|
307
|
-
```bash
|
|
308
|
-
# Generate a focused canvas for a top-level module
|
|
309
|
-
node scripts/ops/architecture-canvas.cjs --focus src
|
|
310
|
-
|
|
311
|
-
# Alias: --module
|
|
312
|
-
node scripts/ops/architecture-canvas.cjs --module dashboard
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
## 📜 License
|
|
316
|
-
|
|
317
|
-
This project is licensed under the [MIT License](LICENSE).
|
|
318
|
-
|
|
319
|
-
---
|
|
320
|
-
|
|
321
|
-
<div align="center">
|
|
322
|
-
<b>System_Ready // 2024 VibeUsage OS</b><br/>
|
|
323
|
-
<i>"More Tokens. More Vibe."</i>
|
|
324
|
-
</div>
|