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.
@@ -107,7 +107,7 @@
107
107
  ]
108
108
  }
109
109
  </script>
110
- <script type="module" crossorigin src="/assets/main-DepLUlUX.js"></script>
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-DepLUlUX.js"></script>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokentracker-cli",
3
- "version": "0.2.25",
3
+ "version": "0.2.27",
4
4
  "description": "Token usage tracker for AI agent CLIs (Codex, Claude Code, Gemini, OpenCode, OpenClaw)",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -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
- const candidates = [
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
  }
@@ -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
- // cache tokens (read/write) excluded from input_tokens and total_tokens
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 hasTotal = u && Object.prototype.hasOwnProperty.call(u, "total_tokens");
2139
- const totalTokens = hasTotal
2140
- ? toNonNegativeInt(u?.total_tokens)
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: toNonNegativeInt(u?.cache_read_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
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
13
- [![Node.js Support](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
14
- [![Platform](https://img.shields.io/badge/Platform-macOS-lightgrey.svg)](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>