ai-burnrate-tool 0.0.0-stage → 1.0.1
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/README.md +151 -2
- package/config/pricing.json +247 -0
- package/dist/cli/index.js +126 -0
- package/dist/cli/reporter.js +132 -0
- package/dist/core/parser.js +497 -0
- package/dist/core/pricing.js +583 -0
- package/dist/core/types.js +1 -0
- package/dist/core/watcher.js +61 -0
- package/dist/public/assets/index-B9NR2QIc.css +1 -0
- package/dist/public/assets/index-_wmJph4q.js +197 -0
- package/dist/public/index.html +16 -0
- package/dist/server/index.js +156 -0
- package/package.json +61 -4
- package/scripts/install.ps1 +61 -0
- package/scripts/install.sh +52 -0
- package/scripts/uninstall.ps1 +14 -0
- package/scripts/uninstall.sh +19 -0
package/README.md
CHANGED
|
@@ -1,3 +1,152 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# 🔥 ai-burnrate ⚡
|
|
4
|
+
|
|
5
|
+
**Real-Time Local Token & Equivalent API Cost Tracker for Antigravity CLI & Claude Code**
|
|
6
|
+
|
|
7
|
+
[](https://github.com/Cise07/ai-burnrate-tool)
|
|
8
|
+
[](https://nodejs.org/)
|
|
9
|
+
[](https://github.com/Cise07/ai-burnrate-tool)
|
|
10
|
+
[](#)
|
|
11
|
+
|
|
12
|
+
Track in real time how many tokens you are consuming across **Antigravity CLI** (`~/.gemini`) and **Claude Code** (`~/.claude`), what your equivalent pay-as-you-go API spend would be in **USD (`$`) or EUR (`€`)**, and how much money you are saving through Prompt Caching and your monthly subscriptions.
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## ✨ Key Features
|
|
19
|
+
|
|
20
|
+
- 🔒 **100% Local, Dynamic & Cross-Platform:** Automatically resolves your user home directory (`os.homedir()` → `~/.gemini` and `~/.claude`) on Windows, macOS, and Linux with zero hardcoded paths. You can also override watched folders via `BURNRATE_GEMINI_DIR` and `BURNRATE_CLAUDE_DIR`.
|
|
21
|
+
- 🟢 **Live Dashboard & Server-Sent Events (SSE):** A background file watcher (`chokidar`) monitors your active AI transcripts and pushes instant real-time updates to charts, KPIs, and session tables in the browser via `/api/events`.
|
|
22
|
+
- 🔄 **Automatic Official Price Sync & Historical Tariff Protection:** On startup (and on manual rescan), `ai-burnrate` automatically fetches the latest official per-token model prices from the public **LiteLLM Pricing Registry** and the official repository feed. Whenever a model's price changes, a new timestamped rate snapshot (`effectiveFrom`) is appended to `~/.burnrate/pricing-ledger.json`—**tokens consumed before the price change are never recalculated**, and the new tariff applies strictly to turns recorded after the change.
|
|
23
|
+
- 💱 **Instant `$ USD` / `€ EUR` Currency Switch:** Toggle the entire dashboard (KPI cards, monthly subscription comparison, charts, and live session table) between US Dollars and Euros with persistent `localStorage` state.
|
|
24
|
+
- 🧠 **Granular Per-Model Token Breakdown:** Dedicated central widget and ANSI CLI tables breaking down *Input (Uncached)*, *Cache Read (Context)*, and *Output (Thinking + Response/Code)* with automatic streaming chunk deduplication.
|
|
25
|
+
- 🖥️ **Instant Terminal Mode (`--cli`):** Prints colorized ANSI tables (Tool Breakdown, Model Breakdown, and Top 12 Recent Sessions) directly in your terminal and exits immediately.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 🚀 Quick Install (Windows / macOS / Linux)
|
|
30
|
+
|
|
31
|
+
Install globally from npm using **one command** from any terminal:
|
|
32
|
+
|
|
33
|
+
> ⚠️ **Prerequisite:** Make sure you have [Node.js (>= 18)](https://nodejs.org/) and `npm` installed.
|
|
34
|
+
|
|
35
|
+
### Latest Version
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g ai-burnrate-tool
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
*(Want to run it once without installing? Use `npx ai-burnrate-tool`)*
|
|
41
|
+
|
|
42
|
+
### 🏷️ Install a Specific Version
|
|
43
|
+
To install a specific release (e.g., `1.0.0`), specify the version tag:
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g ai-burnrate-tool@X.Y.Z
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🎮 Usage & CLI Flags
|
|
51
|
+
|
|
52
|
+
Once installed, the **`burnrate`** (or **`ai-burnrate`**) command is available globally from **any folder** in your terminal:
|
|
53
|
+
|
|
54
|
+
| Command | Description |
|
|
55
|
+
| :--- | :--- |
|
|
56
|
+
| `burnrate` | 🟢 **Live Mode (Default):** Syncs official pricing, starts the local HTTP + SSE server, watches `~/.gemini` & `~/.claude`, and opens the React Dashboard in your browser. |
|
|
57
|
+
| `burnrate --cli` | 📊 **Terminal Mode:** Syncs pricing, prints the full token & cost report in colorized ANSI tables in the terminal, and exits immediately. |
|
|
58
|
+
| `burnrate --no-open` | 🔇 **Headless Mode:** Starts the live server and file watcher without automatically opening the browser. |
|
|
59
|
+
| `burnrate -p, --port <number>` | 🔌 **Custom Port:** Starts the dashboard on a specific port (default: `4321`, automatically falling back to the next free port if busy). |
|
|
60
|
+
| `burnrate --update` | 🔄 **Self-Update:** Updates `ai-burnrate-tool` to the latest version from npm. |
|
|
61
|
+
| `burnrate --uninstall` | 🗑️ **Self-Uninstall:** Completely removes the global `burnrate` package from your system. |
|
|
62
|
+
| `burnrate -V, --version` | 🏷️ Prints the installed version. |
|
|
63
|
+
| `burnrate -h, --help` | ℹ️ Displays all available CLI options. |
|
|
64
|
+
|
|
65
|
+
> 💡 **Clean Shutdown:** While running in live mode (`burnrate`), press `Ctrl+C` (`SIGINT`) to cleanly close all SSE streams, active HTTP sockets, and the `chokidar` file watcher.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🗑️ How to Uninstall `ai-burnrate`
|
|
70
|
+
|
|
71
|
+
You can remove the tool at any time using any of the following methods:
|
|
72
|
+
|
|
73
|
+
### 1️⃣ Built-in CLI Flag (Recommended)
|
|
74
|
+
From any directory in your terminal, simply run:
|
|
75
|
+
```bash
|
|
76
|
+
burnrate --uninstall
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2️⃣ Via `npm`
|
|
80
|
+
```bash
|
|
81
|
+
npm uninstall -g ai-burnrate-tool
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 💰 How Pricing & Historical Rate Versioning Work
|
|
87
|
+
|
|
88
|
+
`ai-burnrate` uses a time-versioned pricing engine (`src/core/pricing.ts`) so that price updates from AI providers never distort your past spending history:
|
|
89
|
+
|
|
90
|
+
1. **Initial Seed (`config/pricing.json`):** Provides the initial baseline rates (effective from `1970-01-01T00:00:00.000Z`) and offline fallback for **Claude** (*Opus 4.x, Sonnet 4.x, 3.7 Sonnet, 3.5 Sonnet, Haiku*) and **Google Gemini** (*Gemini 4 Argon, Gemini Pro, Gemini 3.8 Flash High, Gemini Flash, Flash-Lite*).
|
|
91
|
+
2. **Automatic Live Sync (`syncOfficialPricing`):** At launch and whenever you click the **Rescan** button in the UI, `ai-burnrate` queries the official **LiteLLM Model Pricing Registry** and the GitHub repository feed (with a 3.5s non-blocking timeout for offline resilience).
|
|
92
|
+
3. **Immutable Historical Ledger (`~/.burnrate/pricing-ledger.json`):**
|
|
93
|
+
- When the sync detects that a provider has changed a model's `input`, `output`, `cacheRead`, or `cacheWrite` rate, it **does not overwrite** previous rates.
|
|
94
|
+
- Instead, it appends a new `RateSnapshot` to the `history` array in `~/.burnrate/pricing-ledger.json` stamped with `effectiveFrom: <current ISO timestamp>`.
|
|
95
|
+
4. **Per-Turn Timestamp Matching (`getHistoricalRate`):**
|
|
96
|
+
- Every turn parsed from `~/.gemini` and `~/.claude` has its own `timestamp`.
|
|
97
|
+
- `calculateTurnCost` matches each turn's timestamp against `pricing.history` (`snapshot.effectiveFrom <= turn.timestamp`).
|
|
98
|
+
- All tokens used **before** a price change remain permanently valued at the old rate, while tokens used **after** the price change are valued at the new rate.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 📊 Dashboard Overview
|
|
103
|
+
|
|
104
|
+
- **🔝 Non-Wrapping Top Bar:** Displays the pulsing `LIVE` SSE connection badge, tool filter (`All`, `Antigravity`, `Claude`), time window selector (`24h`, `7d`, `30d`, `all`), `$ USD` / `€ EUR` currency switch, and manual rescan button.
|
|
105
|
+
- **💸 4 Top KPI Cards:**
|
|
106
|
+
1. **API Equivalent Spend:** Total spend with per-tool split (`AGY` vs `Claude`) and ROI multiplier against monthly subscription plans.
|
|
107
|
+
2. **Total Tokens:** Cumulative tokens processed across all LLM turns, split into `Input`, `Output`, and `Cached`.
|
|
108
|
+
3. **Cache Savings:** Total money saved via context caching and overall *Cache Hit Ratio* percentage.
|
|
109
|
+
4. **Sessions & Projects:** Active sessions count, distinct workspaces count, and average cost per session.
|
|
110
|
+
- **📈 Interactive Charts & Model Token Widget:**
|
|
111
|
+
- **Daily API Burn Rate:** Stacked area chart comparing Antigravity CLI and Claude Code spend over time.
|
|
112
|
+
- **Cost by Model:** Horizontal bar chart ranking spend by model.
|
|
113
|
+
- **Total Token Usage by Model:** Central grid showing total tokens, percentage share, and `Input / Cached / Output` breakdown for every model.
|
|
114
|
+
- **🗂️ Live Sessions & Workspaces Table:** Real-time searchable table displaying tool badge, relative workspace path (`~/...`), primary model, turns, input/output tokens, cache read tokens, cache savings, API cost, and relative last-active time.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 📁 Project Structure
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
ai-burnrate/
|
|
122
|
+
├── config/
|
|
123
|
+
│ └── pricing.json # Initial baseline model rates & subscription references
|
|
124
|
+
├── scripts/
|
|
125
|
+
│ ├── install.ps1 # Windows remote one-liner & local installer
|
|
126
|
+
│ ├── uninstall.ps1 # Windows one-liner uninstaller
|
|
127
|
+
│ ├── install.sh # macOS/Linux remote one-liner & local installer
|
|
128
|
+
│ └── uninstall.sh # macOS/Linux one-liner uninstaller
|
|
129
|
+
├── src/
|
|
130
|
+
│ ├── cli/
|
|
131
|
+
│ │ ├── index.ts # CLI entrypoint (#!/usr/bin/env node)
|
|
132
|
+
│ │ └── reporter.ts # ANSI terminal table formatter (--cli)
|
|
133
|
+
│ ├── core/
|
|
134
|
+
│ │ ├── parser.ts # Dynamic ~/.gemini & ~/.claude JSONL parser
|
|
135
|
+
│ │ ├── pricing.ts # Official price auto-sync & historical rate ledger
|
|
136
|
+
│ │ ├── watcher.ts # Real-time file watcher with chokidar
|
|
137
|
+
│ │ └── types.ts # Shared TypeScript interfaces
|
|
138
|
+
│ ├── server/
|
|
139
|
+
│ │ └── index.ts # Local HTTP server + SSE stream (/api/events)
|
|
140
|
+
│ └── web/
|
|
141
|
+
│ ├── App.tsx # Dark Mode React Dashboard + USD/EUR toggle
|
|
142
|
+
│ ├── main.tsx # React entrypoint
|
|
143
|
+
│ └── index.css # Tailwind CSS styles
|
|
144
|
+
├── dist/ # Prebuilt bundle (CLI + static web assets)
|
|
145
|
+
└── package.json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 📄 License
|
|
151
|
+
|
|
152
|
+
Distributed under the **MIT** License by [Cise07](https://github.com/Cise07). Feel free to open Issues or Pull Requests! ❤️
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
{
|
|
2
|
+
"updatedAt": "2026-11-13",
|
|
3
|
+
"currency": "USD",
|
|
4
|
+
"unit": "per_1m_tokens",
|
|
5
|
+
"subscriptions": {
|
|
6
|
+
"claude_max": {
|
|
7
|
+
"name": "Claude Pro / Max",
|
|
8
|
+
"monthlyUSD": 20.0
|
|
9
|
+
},
|
|
10
|
+
"antigravity_pro": {
|
|
11
|
+
"name": "Google AI Ultra / Pro",
|
|
12
|
+
"monthlyUSD": 20.0
|
|
13
|
+
},
|
|
14
|
+
"combinedMonthlyUSD": 40.0
|
|
15
|
+
},
|
|
16
|
+
"models": {
|
|
17
|
+
"claude-3-7-sonnet": {
|
|
18
|
+
"label": "Claude 3.7 Sonnet",
|
|
19
|
+
"provider": "anthropic",
|
|
20
|
+
"input": 3.0,
|
|
21
|
+
"output": 15.0,
|
|
22
|
+
"cacheRead": 0.3,
|
|
23
|
+
"cacheWrite": 3.75
|
|
24
|
+
},
|
|
25
|
+
"claude-sonnet-4": {
|
|
26
|
+
"label": "Claude Sonnet 4.x",
|
|
27
|
+
"provider": "anthropic",
|
|
28
|
+
"input": 3.0,
|
|
29
|
+
"output": 15.0,
|
|
30
|
+
"cacheRead": 0.3,
|
|
31
|
+
"cacheWrite": 3.75
|
|
32
|
+
},
|
|
33
|
+
"claude-3-5-sonnet": {
|
|
34
|
+
"label": "Claude 3.5 Sonnet",
|
|
35
|
+
"provider": "anthropic",
|
|
36
|
+
"input": 3.0,
|
|
37
|
+
"output": 15.0,
|
|
38
|
+
"cacheRead": 0.3,
|
|
39
|
+
"cacheWrite": 3.75
|
|
40
|
+
},
|
|
41
|
+
"claude-opus-4": {
|
|
42
|
+
"label": "Claude Opus 4.x",
|
|
43
|
+
"provider": "anthropic",
|
|
44
|
+
"input": 15.0,
|
|
45
|
+
"output": 75.0,
|
|
46
|
+
"cacheRead": 1.5,
|
|
47
|
+
"cacheWrite": 18.75
|
|
48
|
+
},
|
|
49
|
+
"claude-3-opus": {
|
|
50
|
+
"label": "Claude 3 Opus",
|
|
51
|
+
"provider": "anthropic",
|
|
52
|
+
"input": 15.0,
|
|
53
|
+
"output": 75.0,
|
|
54
|
+
"cacheRead": 1.5,
|
|
55
|
+
"cacheWrite": 18.75
|
|
56
|
+
},
|
|
57
|
+
"claude-3-5-haiku": {
|
|
58
|
+
"label": "Claude 3.5 Haiku",
|
|
59
|
+
"provider": "anthropic",
|
|
60
|
+
"input": 0.8,
|
|
61
|
+
"output": 4.0,
|
|
62
|
+
"cacheRead": 0.08,
|
|
63
|
+
"cacheWrite": 1.0
|
|
64
|
+
},
|
|
65
|
+
"claude-haiku-4": {
|
|
66
|
+
"label": "Claude Haiku 4.x",
|
|
67
|
+
"provider": "anthropic",
|
|
68
|
+
"input": 0.8,
|
|
69
|
+
"output": 4.0,
|
|
70
|
+
"cacheRead": 0.08,
|
|
71
|
+
"cacheWrite": 1.0
|
|
72
|
+
},
|
|
73
|
+
"gemini-4-argon": {
|
|
74
|
+
"label": "Gemini 4 Argon",
|
|
75
|
+
"provider": "google",
|
|
76
|
+
"input": 2.0,
|
|
77
|
+
"output": 10.0,
|
|
78
|
+
"cacheRead": 0.1,
|
|
79
|
+
"cacheWrite": 2.0,
|
|
80
|
+
"isPromo": true,
|
|
81
|
+
"promoStart": "2026-10-21T00:00:00.000Z",
|
|
82
|
+
"promoEnd": "2026-11-30T23:59:59.999Z",
|
|
83
|
+
"promoNote": "Promo Google AI Launch (-50% dal 21/10/2026 al 30/11/2026)",
|
|
84
|
+
"standardInput": 4.0,
|
|
85
|
+
"standardOutput": 20.0,
|
|
86
|
+
"standardCacheRead": 0.2,
|
|
87
|
+
"standardCacheWrite": 4.0
|
|
88
|
+
},
|
|
89
|
+
"gemini-4-carbon": {
|
|
90
|
+
"label": "Gemini 4 Carbon",
|
|
91
|
+
"provider": "google",
|
|
92
|
+
"input": 2.0,
|
|
93
|
+
"output": 10.0,
|
|
94
|
+
"cacheRead": 0.1,
|
|
95
|
+
"cacheWrite": 2.0,
|
|
96
|
+
"isPromo": true,
|
|
97
|
+
"promoStart": "2026-10-21T00:00:00.000Z",
|
|
98
|
+
"promoEnd": "2026-11-30T23:59:59.999Z",
|
|
99
|
+
"promoNote": "Promo Google AI Launch (-50% dal 21/10/2026 al 30/11/2026)",
|
|
100
|
+
"standardInput": 4.0,
|
|
101
|
+
"standardOutput": 20.0,
|
|
102
|
+
"standardCacheRead": 0.2,
|
|
103
|
+
"standardCacheWrite": 4.0
|
|
104
|
+
},
|
|
105
|
+
"gemini-4-hydrogen": {
|
|
106
|
+
"label": "Gemini 4 Hydrogen",
|
|
107
|
+
"provider": "google",
|
|
108
|
+
"input": 1.5,
|
|
109
|
+
"output": 7.5,
|
|
110
|
+
"cacheRead": 0.075,
|
|
111
|
+
"cacheWrite": 1.5,
|
|
112
|
+
"isPromo": false
|
|
113
|
+
},
|
|
114
|
+
"orchestrator-carbon": {
|
|
115
|
+
"label": "Orchestrator (Carbon)",
|
|
116
|
+
"provider": "google",
|
|
117
|
+
"input": 1.65,
|
|
118
|
+
"output": 8.25,
|
|
119
|
+
"cacheRead": 0.0825,
|
|
120
|
+
"cacheWrite": 1.65,
|
|
121
|
+
"isPromo": true,
|
|
122
|
+
"promoStart": "2026-10-21T00:00:00.000Z",
|
|
123
|
+
"promoEnd": "2026-11-30T23:59:59.999Z",
|
|
124
|
+
"promoNote": "Promo Google AI Launch su Planner Carbon (-50% fino al 30/11/2026) + Hydrogen",
|
|
125
|
+
"standardInput": 2.25,
|
|
126
|
+
"standardOutput": 11.25,
|
|
127
|
+
"standardCacheRead": 0.1125,
|
|
128
|
+
"standardCacheWrite": 2.25
|
|
129
|
+
},
|
|
130
|
+
"orchestrator-argon": {
|
|
131
|
+
"label": "Orchestrator (Argon)",
|
|
132
|
+
"provider": "google",
|
|
133
|
+
"input": 1.65,
|
|
134
|
+
"output": 8.25,
|
|
135
|
+
"cacheRead": 0.0825,
|
|
136
|
+
"cacheWrite": 1.65,
|
|
137
|
+
"isPromo": true,
|
|
138
|
+
"promoStart": "2026-10-21T00:00:00.000Z",
|
|
139
|
+
"promoEnd": "2026-11-30T23:59:59.999Z",
|
|
140
|
+
"promoNote": "Promo Google AI Launch su Planner Argon (-50% fino al 30/11/2026) + Hydrogen",
|
|
141
|
+
"standardInput": 2.25,
|
|
142
|
+
"standardOutput": 11.25,
|
|
143
|
+
"standardCacheRead": 0.1125,
|
|
144
|
+
"standardCacheWrite": 2.25
|
|
145
|
+
},
|
|
146
|
+
"gemini-2.5-pro": {
|
|
147
|
+
"label": "Gemini Pro",
|
|
148
|
+
"provider": "google",
|
|
149
|
+
"input": 1.25,
|
|
150
|
+
"output": 10.0,
|
|
151
|
+
"cacheRead": 0.125,
|
|
152
|
+
"cacheWrite": 1.25,
|
|
153
|
+
"isPromo": true,
|
|
154
|
+
"promoStart": "2026-06-01T00:00:00.000Z",
|
|
155
|
+
"promoEnd": "2026-12-31T23:59:59.999Z",
|
|
156
|
+
"promoNote": "Promo Google AI (<200k tokens)",
|
|
157
|
+
"standardInput": 2.5,
|
|
158
|
+
"standardOutput": 12.5,
|
|
159
|
+
"standardCacheRead": 0.625,
|
|
160
|
+
"standardCacheWrite": 2.5
|
|
161
|
+
},
|
|
162
|
+
"gemini-pro": {
|
|
163
|
+
"label": "Gemini Pro",
|
|
164
|
+
"provider": "google",
|
|
165
|
+
"input": 1.25,
|
|
166
|
+
"output": 10.0,
|
|
167
|
+
"cacheRead": 0.125,
|
|
168
|
+
"cacheWrite": 1.25,
|
|
169
|
+
"isPromo": true,
|
|
170
|
+
"promoStart": "2026-06-01T00:00:00.000Z",
|
|
171
|
+
"promoEnd": "2026-12-31T23:59:59.999Z",
|
|
172
|
+
"promoNote": "Promo Google AI (<200k tokens)",
|
|
173
|
+
"standardInput": 2.5,
|
|
174
|
+
"standardOutput": 12.5,
|
|
175
|
+
"standardCacheRead": 0.625,
|
|
176
|
+
"standardCacheWrite": 2.5
|
|
177
|
+
},
|
|
178
|
+
"gemini-3.8-flash": {
|
|
179
|
+
"label": "Gemini 3.8 Flash (High)",
|
|
180
|
+
"provider": "google",
|
|
181
|
+
"input": 0.75,
|
|
182
|
+
"output": 3.75,
|
|
183
|
+
"cacheRead": 0.075,
|
|
184
|
+
"cacheWrite": 0.75,
|
|
185
|
+
"isPromo": true,
|
|
186
|
+
"promoStart": "2026-09-15T00:00:00.000Z",
|
|
187
|
+
"promoEnd": "2026-12-31T23:59:59.999Z",
|
|
188
|
+
"promoNote": "Promo Google AI (-50% dal 15/09/2026 al 31/12/2026)",
|
|
189
|
+
"standardInput": 1.5,
|
|
190
|
+
"standardOutput": 7.5,
|
|
191
|
+
"standardCacheRead": 0.15,
|
|
192
|
+
"standardCacheWrite": 1.5
|
|
193
|
+
},
|
|
194
|
+
"gemini-2.5-flash": {
|
|
195
|
+
"label": "Gemini Flash",
|
|
196
|
+
"provider": "google",
|
|
197
|
+
"input": 0.3,
|
|
198
|
+
"output": 2.5,
|
|
199
|
+
"cacheRead": 0.03,
|
|
200
|
+
"cacheWrite": 0.3,
|
|
201
|
+
"isPromo": true,
|
|
202
|
+
"promoStart": "2026-06-01T00:00:00.000Z",
|
|
203
|
+
"promoEnd": "2026-12-31T23:59:59.999Z",
|
|
204
|
+
"promoNote": "Promo Google AI Introductory",
|
|
205
|
+
"standardInput": 0.5,
|
|
206
|
+
"standardOutput": 2.5,
|
|
207
|
+
"standardCacheRead": 0.125,
|
|
208
|
+
"standardCacheWrite": 0.5
|
|
209
|
+
},
|
|
210
|
+
"gemini-flash": {
|
|
211
|
+
"label": "Gemini Flash",
|
|
212
|
+
"provider": "google",
|
|
213
|
+
"input": 0.3,
|
|
214
|
+
"output": 2.5,
|
|
215
|
+
"cacheRead": 0.03,
|
|
216
|
+
"cacheWrite": 0.3,
|
|
217
|
+
"isPromo": true,
|
|
218
|
+
"promoStart": "2026-06-01T00:00:00.000Z",
|
|
219
|
+
"promoEnd": "2026-12-31T23:59:59.999Z",
|
|
220
|
+
"promoNote": "Promo Google AI Introductory",
|
|
221
|
+
"standardInput": 0.5,
|
|
222
|
+
"standardOutput": 2.5,
|
|
223
|
+
"standardCacheRead": 0.125,
|
|
224
|
+
"standardCacheWrite": 0.5
|
|
225
|
+
},
|
|
226
|
+
"gemini-flash-lite": {
|
|
227
|
+
"label": "Gemini Flash-Lite",
|
|
228
|
+
"provider": "google",
|
|
229
|
+
"input": 0.1,
|
|
230
|
+
"output": 0.4,
|
|
231
|
+
"cacheRead": 0.01,
|
|
232
|
+
"cacheWrite": 0.1,
|
|
233
|
+
"isPromo": true,
|
|
234
|
+
"promoStart": "2026-06-01T00:00:00.000Z",
|
|
235
|
+
"promoEnd": "2026-12-31T23:59:59.999Z",
|
|
236
|
+
"promoNote": "Promo Google AI Tier",
|
|
237
|
+
"standardInput": 0.15,
|
|
238
|
+
"standardOutput": 0.6,
|
|
239
|
+
"standardCacheRead": 0.0375,
|
|
240
|
+
"standardCacheWrite": 0.15
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
"defaults": {
|
|
244
|
+
"claude": "claude-sonnet-4",
|
|
245
|
+
"antigravity": "gemini-4-argon"
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import open from 'open';
|
|
7
|
+
import pc from 'picocolors';
|
|
8
|
+
import { scanAllLogs } from '../core/parser.js';
|
|
9
|
+
import { getPackageRoot, syncOfficialPricing } from '../core/pricing.js';
|
|
10
|
+
import { startLogWatcher } from '../core/watcher.js';
|
|
11
|
+
import { startServer } from '../server/index.js';
|
|
12
|
+
import { printCliReport } from './reporter.js';
|
|
13
|
+
const NPM_PACKAGE = 'ai-burnrate-tool';
|
|
14
|
+
function getPackageVersion() {
|
|
15
|
+
try {
|
|
16
|
+
const pkgPath = path.join(getPackageRoot(), 'package.json');
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
18
|
+
return pkg.version || '1.0.0';
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return '1.0.0';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function resolveNpmUpdateSpec() {
|
|
25
|
+
const requested = process.env.BURNRATE_VERSION?.trim();
|
|
26
|
+
if (requested) {
|
|
27
|
+
const version = requested.replace(/^v/, '');
|
|
28
|
+
return `${NPM_PACKAGE}@${version}`;
|
|
29
|
+
}
|
|
30
|
+
return `${NPM_PACKAGE}@latest`;
|
|
31
|
+
}
|
|
32
|
+
const program = new Command();
|
|
33
|
+
program
|
|
34
|
+
.name('burnrate')
|
|
35
|
+
.description('Real-time local token & API equivalent cost tracker for Antigravity CLI and Claude Code')
|
|
36
|
+
.version(getPackageVersion())
|
|
37
|
+
.option('--cli', 'Print the usage report in ANSI tables in the terminal and exit immediately', false)
|
|
38
|
+
.option('--no-open', 'Start the live server and file watcher without automatically opening the browser')
|
|
39
|
+
.option('-p, --port <number>', 'Specify a custom port for the dashboard server', '4321')
|
|
40
|
+
.option('--update', 'Update ai-burnrate-tool to the latest version from npm', false)
|
|
41
|
+
.option('--uninstall', 'Uninstall ai-burnrate globally from your system', false);
|
|
42
|
+
program.parse(process.argv);
|
|
43
|
+
const opts = program.opts();
|
|
44
|
+
async function main() {
|
|
45
|
+
if (opts.update) {
|
|
46
|
+
const targetSpec = resolveNpmUpdateSpec();
|
|
47
|
+
console.log(`\n ${pc.cyan('●')} Updating ${pc.bold(targetSpec)} from npm...`);
|
|
48
|
+
try {
|
|
49
|
+
execSync(`npm install -g ${targetSpec}`, {
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
});
|
|
52
|
+
console.log(` ${pc.green('✓')} ${pc.bold(targetSpec)} updated successfully!\n`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
console.error(` ${pc.red('✗')} Failed to update automatically. Try running:\n ${pc.cyan(`npm install -g ${targetSpec}`)}\n`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (opts.uninstall) {
|
|
61
|
+
console.log(`\n ${pc.yellow('●')} Uninstalling ${pc.bold('ai-burnrate')} globally...`);
|
|
62
|
+
try {
|
|
63
|
+
execSync('npm uninstall -g ai-burnrate-tool', { stdio: 'inherit' });
|
|
64
|
+
console.log(` ${pc.green('✓')} ${pc.bold('ai-burnrate')} has been completely removed from your system.\n`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
console.error(` ${pc.red('✗')} Failed to uninstall automatically. Run: ${pc.cyan('npm uninstall -g ai-burnrate-tool')}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const syncResult = await syncOfficialPricing();
|
|
73
|
+
if (syncResult.updated && syncResult.effectiveFrom) {
|
|
74
|
+
console.log(` ${pc.cyan('↻')} Official pricing updated (new tariff active for turns from ${pc.bold(syncResult.effectiveFrom)}; past turns preserved).`);
|
|
75
|
+
}
|
|
76
|
+
const snapshot = scanAllLogs(true);
|
|
77
|
+
if (opts.cli) {
|
|
78
|
+
printCliReport(snapshot);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
const preferredPort = Number.parseInt(String(opts.port || '4321'), 10) || 4321;
|
|
82
|
+
const server = await startServer(preferredPort, snapshot);
|
|
83
|
+
const watcherHandle = startLogWatcher((updatedSnapshot, changedPath) => {
|
|
84
|
+
server.broadcast(updatedSnapshot, changedPath);
|
|
85
|
+
const timeStr = new Date().toLocaleTimeString();
|
|
86
|
+
console.log(`${pc.dim(`[${timeStr}]`)} ${pc.green('● SSE Sync')} ${pc.dim('←')} ${pc.cyan(changedPath)}`);
|
|
87
|
+
});
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(` ${pc.bgGreen(pc.black(pc.bold(' AI-BURNRATE ')))} ${pc.green('● LIVE')} ${pc.bold('Real-Time Token & Cost Tracker')}`);
|
|
90
|
+
console.log(` ${pc.dim('─'.repeat(58))}`);
|
|
91
|
+
console.log(` ${pc.bold('Dashboard URL:')} ${pc.cyan(pc.underline(server.url))}`);
|
|
92
|
+
console.log(` ${pc.bold('SSE Stream:')} ${pc.dim(`${server.url}/api/events`)}`);
|
|
93
|
+
console.log(` ${pc.bold('Watching:')} ${pc.dim(snapshot.watchedPaths.join(' & '))}`);
|
|
94
|
+
console.log(` ${pc.bold('Sessions Found:')} ${pc.yellow(String(snapshot.sessions.length))}`);
|
|
95
|
+
console.log(` ${pc.dim('─'.repeat(58))}`);
|
|
96
|
+
console.log(` ${pc.dim('Press Ctrl+C to stop the live watcher and exit.')}\n`);
|
|
97
|
+
if (opts.open !== false) {
|
|
98
|
+
try {
|
|
99
|
+
await open(server.url);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
console.log(` ${pc.yellow('!')} Could not auto-open browser. Open ${pc.cyan(server.url)} manually.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let shuttingDown = false;
|
|
106
|
+
const shutdown = async (signal) => {
|
|
107
|
+
if (shuttingDown)
|
|
108
|
+
return;
|
|
109
|
+
shuttingDown = true;
|
|
110
|
+
console.log(`\n ${pc.yellow('●')} Received ${signal}. Closing file watcher and SSE connections...`);
|
|
111
|
+
try {
|
|
112
|
+
await Promise.all([watcherHandle.close(), server.close()]);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// ignore
|
|
116
|
+
}
|
|
117
|
+
console.log(` ${pc.green('✓')} ai-burnrate stopped cleanly.`);
|
|
118
|
+
process.exit(0);
|
|
119
|
+
};
|
|
120
|
+
process.on('SIGINT', () => void shutdown('SIGINT'));
|
|
121
|
+
process.on('SIGTERM', () => void shutdown('SIGTERM'));
|
|
122
|
+
}
|
|
123
|
+
main().catch((err) => {
|
|
124
|
+
console.error(pc.red('Fatal error starting ai-burnrate:'), err);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { isPromoActive } from '../core/pricing.js';
|
|
3
|
+
function fmtTokens(n) {
|
|
4
|
+
if (n >= 1_000_000)
|
|
5
|
+
return `${(n / 1_000_000).toFixed(2)}M`;
|
|
6
|
+
if (n >= 1_000)
|
|
7
|
+
return `${(n / 1_000).toFixed(1)}K`;
|
|
8
|
+
return String(n);
|
|
9
|
+
}
|
|
10
|
+
function fmtUSD(n) {
|
|
11
|
+
return `$${n.toFixed(2)}`;
|
|
12
|
+
}
|
|
13
|
+
function padRight(str, width) {
|
|
14
|
+
const visible = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
15
|
+
if (visible.length >= width)
|
|
16
|
+
return str;
|
|
17
|
+
return str + ' '.repeat(width - visible.length);
|
|
18
|
+
}
|
|
19
|
+
function padLeft(str, width) {
|
|
20
|
+
const visible = str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
21
|
+
if (visible.length >= width)
|
|
22
|
+
return str;
|
|
23
|
+
return ' '.repeat(width - visible.length) + str;
|
|
24
|
+
}
|
|
25
|
+
export function printCliReport(snapshot) {
|
|
26
|
+
const sessions = snapshot.sessions;
|
|
27
|
+
let totalCost = 0;
|
|
28
|
+
let totalCacheSavings = 0;
|
|
29
|
+
let totalInput = 0;
|
|
30
|
+
let totalOutput = 0;
|
|
31
|
+
let totalCacheRead = 0;
|
|
32
|
+
let totalTokens = 0;
|
|
33
|
+
const byTool = {
|
|
34
|
+
antigravity: { sessions: 0, turns: 0, input: 0, cache: 0, output: 0, tokens: 0, cost: 0, savings: 0 },
|
|
35
|
+
claude: { sessions: 0, turns: 0, input: 0, cache: 0, output: 0, tokens: 0, cost: 0, savings: 0 },
|
|
36
|
+
};
|
|
37
|
+
const byModel = new Map();
|
|
38
|
+
for (const s of sessions) {
|
|
39
|
+
totalCost += s.costUSD;
|
|
40
|
+
totalCacheSavings += s.cacheSavingsUSD;
|
|
41
|
+
totalInput += s.inputTokens;
|
|
42
|
+
totalOutput += s.outputTokens;
|
|
43
|
+
totalCacheRead += s.cacheReadTokens;
|
|
44
|
+
totalTokens += s.totalTokens;
|
|
45
|
+
const bucket = byTool[s.source];
|
|
46
|
+
bucket.sessions += 1;
|
|
47
|
+
bucket.turns += s.turns;
|
|
48
|
+
bucket.input += s.inputTokens;
|
|
49
|
+
bucket.cache += s.cacheReadTokens;
|
|
50
|
+
bucket.output += s.outputTokens;
|
|
51
|
+
bucket.tokens += s.totalTokens;
|
|
52
|
+
bucket.cost += s.costUSD;
|
|
53
|
+
bucket.savings += s.cacheSavingsUSD;
|
|
54
|
+
for (const t of s.turnHistory) {
|
|
55
|
+
const m = byModel.get(t.modelLabel) || {
|
|
56
|
+
label: t.modelLabel,
|
|
57
|
+
normalizedModel: t.normalizedModel,
|
|
58
|
+
turns: 0,
|
|
59
|
+
input: 0,
|
|
60
|
+
cache: 0,
|
|
61
|
+
output: 0,
|
|
62
|
+
tokens: 0,
|
|
63
|
+
cost: 0,
|
|
64
|
+
savings: 0,
|
|
65
|
+
};
|
|
66
|
+
m.turns += 1;
|
|
67
|
+
m.input += t.inputTokens;
|
|
68
|
+
m.cache += t.cacheReadTokens;
|
|
69
|
+
m.output += t.outputTokens;
|
|
70
|
+
m.tokens += t.totalTokens;
|
|
71
|
+
m.cost += t.costUSD;
|
|
72
|
+
m.savings += t.cacheSavingsUSD;
|
|
73
|
+
byModel.set(t.modelLabel, m);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const subMonthly = snapshot.pricing.subscriptions.combinedMonthlyUSD || 40;
|
|
77
|
+
const subRatio = subMonthly > 0 ? ((totalCost / subMonthly) * 100).toFixed(0) : '0';
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(`${pc.bgGreen(pc.black(pc.bold(' AI-BURNRATE ')))} ${pc.bold('Local Token & API Equivalent Cost Report')}`);
|
|
80
|
+
console.log(pc.dim(`Scanned: ${snapshot.watchedPaths.join(' • ')}`));
|
|
81
|
+
console.log(pc.dim('─'.repeat(98)));
|
|
82
|
+
console.log(` ${pc.bold('API Equiv. Spend:')} ${pc.green(pc.bold(fmtUSD(totalCost)))} ${pc.dim(`(${subRatio}% of $${subMonthly}/mo sub)`)} ` +
|
|
83
|
+
`${pc.bold('Cache Saved:')} ${pc.cyan(pc.bold(fmtUSD(totalCacheSavings)))} ` +
|
|
84
|
+
`${pc.bold('Total Tokens:')} ${pc.yellow(pc.bold(fmtTokens(totalTokens)))} ` +
|
|
85
|
+
`${pc.bold('Sessions:')} ${pc.white(pc.bold(String(sessions.length)))}`);
|
|
86
|
+
console.log(` ${pc.dim(`Input (Uncached): ${fmtTokens(totalInput)} • Cache Read (Context): ${fmtTokens(totalCacheRead)} • Output (Think+Resp): ${fmtTokens(totalOutput)}`)}`);
|
|
87
|
+
console.log(pc.dim('─'.repeat(98)));
|
|
88
|
+
// Tool Summary Table
|
|
89
|
+
console.log(`\n${pc.bold(pc.white(' TOOL BREAKDOWN'))}`);
|
|
90
|
+
console.log(` ${padRight(pc.dim('Tool'), 18)} ${padLeft(pc.dim('Sess'), 6)} ${padLeft(pc.dim('Turns'), 7)} ${padLeft(pc.dim('Input'), 10)} ${padLeft(pc.dim('Cache Read'), 12)} ${padLeft(pc.dim('Output'), 10)} ${padLeft(pc.dim('Total Tok'), 11)} ${padLeft(pc.dim('Cache Saved'), 12)} ${padLeft(pc.dim('API Cost'), 10)}`);
|
|
91
|
+
console.log(` ${pc.dim('─'.repeat(96))}`);
|
|
92
|
+
const toolRows = [
|
|
93
|
+
{ label: pc.cyan('Antigravity CLI'), data: byTool.antigravity },
|
|
94
|
+
{ label: pc.magenta('Claude Code'), data: byTool.claude },
|
|
95
|
+
];
|
|
96
|
+
for (const row of toolRows) {
|
|
97
|
+
console.log(` ${padRight(row.label, 18)} ${padLeft(String(row.data.sessions), 6)} ${padLeft(String(row.data.turns), 7)} ${padLeft(fmtTokens(row.data.input), 10)} ${padLeft(pc.cyan(fmtTokens(row.data.cache)), 12)} ${padLeft(pc.yellow(fmtTokens(row.data.output)), 10)} ${padLeft(fmtTokens(row.data.tokens), 11)} ${padLeft(pc.cyan(fmtUSD(row.data.savings)), 12)} ${padLeft(pc.green(pc.bold(fmtUSD(row.data.cost))), 10)}`);
|
|
98
|
+
}
|
|
99
|
+
// Model Breakdown Table
|
|
100
|
+
if (byModel.size > 0) {
|
|
101
|
+
console.log(`\n${pc.bold(pc.white(' MODEL BREAKDOWN'))}`);
|
|
102
|
+
console.log(` ${padRight(pc.dim('Model'), 25)} ${padLeft(pc.dim('Turns'), 7)} ${padLeft(pc.dim('Input'), 10)} ${padLeft(pc.dim('Cache Read'), 12)} ${padLeft(pc.dim('Output'), 10)} ${padLeft(pc.dim('Total Tok'), 11)} ${padLeft(pc.dim('Cache Saved'), 12)} ${padLeft(pc.dim('API Cost'), 10)}`);
|
|
103
|
+
console.log(` ${pc.dim('─'.repeat(96))}`);
|
|
104
|
+
const sortedModels = Array.from(byModel.values()).sort((a, b) => b.cost - a.cost);
|
|
105
|
+
let hasPromo = false;
|
|
106
|
+
for (const m of sortedModels) {
|
|
107
|
+
const rate = snapshot.pricing.models[m.normalizedModel] ||
|
|
108
|
+
Object.values(snapshot.pricing.models).find((r) => r.label === m.label || m.label.startsWith(r.label));
|
|
109
|
+
const isPromo = isPromoActive(rate);
|
|
110
|
+
if (isPromo)
|
|
111
|
+
hasPromo = true;
|
|
112
|
+
const displayLabel = isPromo ? `${m.label} ${pc.yellow('★PROMO')}` : m.label;
|
|
113
|
+
console.log(` ${padRight(displayLabel, 32)} ${padLeft(String(m.turns), 7)} ${padLeft(fmtTokens(m.input), 10)} ${padLeft(pc.cyan(fmtTokens(m.cache)), 12)} ${padLeft(pc.yellow(fmtTokens(m.output)), 10)} ${padLeft(fmtTokens(m.tokens), 11)} ${padLeft(pc.cyan(fmtUSD(m.savings)), 12)} ${padLeft(pc.green(fmtUSD(m.cost)), 10)}`);
|
|
114
|
+
}
|
|
115
|
+
if (hasPromo) {
|
|
116
|
+
console.log(` ${pc.yellow('★')} ${pc.dim('Modelli con tariffe promozionali Google AI Studio attive.')}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Recent Sessions Table
|
|
120
|
+
console.log(`\n${pc.bold(pc.white(' RECENT SESSIONS / WORKSPACES (Top 12)'))}`);
|
|
121
|
+
console.log(` ${padRight(pc.dim('Tool'), 13)} ${padRight(pc.dim('Workspace'), 22)} ${padRight(pc.dim('Model'), 18)} ${padLeft(pc.dim('Input'), 8)} ${padLeft(pc.dim('Cache'), 8)} ${padLeft(pc.dim('Output'), 7)} ${padLeft(pc.dim('Cost'), 7)} ${padLeft(pc.dim('Created'), 11)} ${padLeft(pc.dim('Modified'), 11)}`);
|
|
122
|
+
console.log(` ${pc.dim('─'.repeat(108))}`);
|
|
123
|
+
for (const s of sessions.slice(0, 12)) {
|
|
124
|
+
const toolTag = s.source === 'antigravity' ? pc.cyan('Antigravity') : pc.magenta('Claude Code');
|
|
125
|
+
const ws = s.workspaceName.length > 20 ? '…' + s.workspaceName.slice(-19) : s.workspaceName;
|
|
126
|
+
const mod = s.primaryModelLabel.length > 16 ? s.primaryModelLabel.slice(0, 16) : s.primaryModelLabel;
|
|
127
|
+
const createdStr = s.startedAt ? s.startedAt.slice(0, 10) : '-';
|
|
128
|
+
const activeStr = s.lastActiveAt ? s.lastActiveAt.slice(0, 10) : '-';
|
|
129
|
+
console.log(` ${padRight(toolTag, 13)} ${padRight(ws, 22)} ${padRight(mod, 18)} ${padLeft(fmtTokens(s.inputTokens), 8)} ${padLeft(pc.cyan(fmtTokens(s.cacheReadTokens)), 8)} ${padLeft(pc.yellow(fmtTokens(s.outputTokens)), 7)} ${padLeft(pc.green(fmtUSD(s.costUSD)), 7)} ${padLeft(pc.dim(createdStr), 11)} ${padLeft(pc.dim(activeStr), 11)}`);
|
|
130
|
+
}
|
|
131
|
+
console.log('');
|
|
132
|
+
}
|