code-meter 0.1.0__tar.gz → 0.3.0__tar.gz
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.
- {code_meter-0.1.0/src/code_meter.egg-info → code_meter-0.3.0}/PKG-INFO +42 -25
- {code_meter-0.1.0 → code_meter-0.3.0}/README.md +41 -22
- {code_meter-0.1.0 → code_meter-0.3.0}/pyproject.toml +1 -1
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/cli.py +122 -18
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/engine.py +35 -1
- code_meter-0.3.0/src/code_meter/pricing/live.py +72 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/storage/database.py +11 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/storage/repository.py +31 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/ui/dashboard.py +38 -4
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/watcher.py +16 -8
- {code_meter-0.1.0 → code_meter-0.3.0/src/code_meter.egg-info}/PKG-INFO +42 -25
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/SOURCES.txt +1 -1
- {code_meter-0.1.0 → code_meter-0.3.0}/tests/test_pricing.py +5 -0
- code_meter-0.1.0/LICENSE +0 -21
- {code_meter-0.1.0 → code_meter-0.3.0}/setup.cfg +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/setup.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/__init__.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/__init__.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/costs.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/reports.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/tokens.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/config.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/models/__init__.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/models/project.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/models/usage.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/__init__.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/anthropic.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/google.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/openai.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/__init__.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/antigravity.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/base.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/claude_code.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/codex.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/storage/__init__.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/ui/__init__.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/ui/tables.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/dependency_links.txt +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/entry_points.txt +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/requires.txt +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/top_level.txt +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/tests/test_analytics.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/tests/test_database.py +0 -0
- {code_meter-0.1.0 → code_meter-0.3.0}/tests/test_scanner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-meter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Local AI coding token usage tracker and cost estimator for Claude Code and future agents.
|
|
5
5
|
Author-email: Dhanush Nayak <dhanushnayak.ram@mail.com>
|
|
6
6
|
License: MIT
|
|
@@ -9,7 +9,6 @@ Project-URL: Repository, https://github.com/dhanushnayak/code-meter
|
|
|
9
9
|
Project-URL: Bug Tracker, https://github.com/dhanushnayak/code-meter/issues
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
12
|
Requires-Dist: typer[all]>=0.9.0
|
|
14
13
|
Requires-Dist: rich>=13.0.0
|
|
15
14
|
Requires-Dist: pydantic>=2.0.0
|
|
@@ -20,7 +19,6 @@ Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
|
20
19
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
21
20
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
22
21
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
23
|
-
Dynamic: license-file
|
|
24
22
|
|
|
25
23
|
# code-meter
|
|
26
24
|
|
|
@@ -73,18 +71,51 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
73
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
74
|
+
## 🖥️ Interactive Rich Terminal UI
|
|
75
|
+
|
|
76
|
+
`code-meter` includes a full-featured, responsive **Rich Terminal User Interface** that presents live token metrics, USD costs, user queries, and model outputs in real-time.
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
+--------------------------------------------------------------------+
|
|
80
|
+
| CODE METER |
|
|
81
|
+
| Overview |
|
|
82
|
+
+--------------------------------------------------------------------+
|
|
83
|
+
+--------------------------- Key Metrics ----------------------------+
|
|
84
|
+
| Requests: 3 |
|
|
85
|
+
| Input Tokens: 1.5K |
|
|
86
|
+
| Output Tokens: 400 |
|
|
87
|
+
| Cache Read: 300 |
|
|
88
|
+
| Total Tokens: 2.2K |
|
|
89
|
+
| Estimated Cost: $0.02 |
|
|
90
|
+
+--------------------------------------------------------------------+
|
|
91
|
+
| BY MODEL | Tokens | Cost |
|
|
92
|
+
| gemini-2.5-flash | 2.20K | $0.02 |
|
|
93
|
+
+--------------------------------------------------------------------+
|
|
94
|
+
| RECENT USER QUERIES / PROMPTS |
|
|
95
|
+
| 09-01 12:00 | antigravity | Build an interactive dashboard UI |
|
|
96
|
+
+--------------------------------------------------------------------+
|
|
97
|
+
| RECENT OUTPUTS & CODE EDITS |
|
|
98
|
+
| 09-01 12:01 | WRITE_FILE | src/main.py | + print('Hello') |
|
|
99
|
+
+--------------------------------------------------------------------+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
76
104
|
## 🛠️ CLI Commands
|
|
77
105
|
|
|
78
106
|
| Command | Description |
|
|
79
107
|
| :--- | :--- |
|
|
80
|
-
| `code-meter` | Open interactive Rich terminal dashboard |
|
|
108
|
+
| `code-meter` | Open main interactive Rich terminal dashboard UI |
|
|
109
|
+
| `code-meter ui` | Launch live interactive Rich terminal UI watching all AI log directories |
|
|
110
|
+
| `code-meter watch` | Live watch mode (auto-refreshes UI as AI agents write logs) |
|
|
111
|
+
| `code-meter update-pricing` | Fetch latest real-time token pricing for 2,800+ models |
|
|
81
112
|
| `code-meter scan` | Force an incremental scan of local usage directories |
|
|
82
113
|
| `code-meter today` | Summary of token usage and cost for today |
|
|
83
114
|
| `code-meter week` | Summary of token usage and cost for the past 7 days |
|
|
84
115
|
| `code-meter month` | Summary of token usage and cost for the past 30 days |
|
|
85
116
|
| `code-meter status` | View system status (scanned files, records, prompts, code edits, DB size) |
|
|
86
|
-
| `code-meter prompts` | View log of user prompts
|
|
87
|
-
| `code-meter history` | View log of code
|
|
117
|
+
| `code-meter prompts` | View log of user prompts and queries asked over time |
|
|
118
|
+
| `code-meter history` | View log of code modifications, file writes, and assistant actions |
|
|
88
119
|
| `code-meter diffs` | View formatted code diff summaries saved in SQLite |
|
|
89
120
|
| `code-meter report` | Detailed aggregated report with optional filters |
|
|
90
121
|
| `code-meter models` | Usage and cost breakdown by model |
|
|
@@ -92,7 +123,6 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
92
123
|
| `code-meter sessions` | Usage and cost breakdown by session ID |
|
|
93
124
|
| `code-meter export` | Export usage records to CSV or JSON format |
|
|
94
125
|
| `code-meter config` | Display active configuration settings and file paths |
|
|
95
|
-
| `code-meter watch` | Live file watcher mode for real-time dashboard updates |
|
|
96
126
|
|
|
97
127
|
---
|
|
98
128
|
|
|
@@ -101,6 +131,9 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
101
131
|
Combine filters on reporting, prompts, and history commands:
|
|
102
132
|
|
|
103
133
|
```bash
|
|
134
|
+
# Update real-time token costs for 2,800+ models
|
|
135
|
+
code-meter update-pricing
|
|
136
|
+
|
|
104
137
|
# Filter report by provider, project, model, and timeframe
|
|
105
138
|
code-meter report --provider codex --model gpt-4o --days 30
|
|
106
139
|
code-meter report --provider antigravity --model gemini-2.5-flash
|
|
@@ -167,29 +200,13 @@ ClaudeCodeProvider CodexProvider AntigravityProvider
|
|
|
167
200
|
Database Repository & Incremental Scanner (SQLite)
|
|
168
201
|
│
|
|
169
202
|
▼
|
|
170
|
-
|
|
203
|
+
Pricing Engine (Auto Daily Sync & Live Model Pricing)
|
|
171
204
|
│
|
|
172
205
|
▼
|
|
173
206
|
Analytics Engine (Token counts, Costs, Aggregations)
|
|
174
207
|
│
|
|
175
208
|
▼
|
|
176
|
-
|
|
177
|
-
```
|
|
178
|
-
│
|
|
179
|
-
▼
|
|
180
|
-
UsageRecord / SessionPrompt / SessionCodeChange (Pydantic models)
|
|
181
|
-
│
|
|
182
|
-
▼
|
|
183
|
-
Database Repository & Incremental Scanner (SQLite)
|
|
184
|
-
│
|
|
185
|
-
▼
|
|
186
|
-
Pricing Engine (Model Pricing Tables)
|
|
187
|
-
│
|
|
188
|
-
▼
|
|
189
|
-
Analytics Engine (Token counts, Costs, Aggregations)
|
|
190
|
-
│
|
|
191
|
-
▼
|
|
192
|
-
Terminal UI & Typer CLI Commands
|
|
209
|
+
Rich Terminal UI & Interactive Dashboard Commands
|
|
193
210
|
```
|
|
194
211
|
|
|
195
212
|
---
|
|
@@ -49,18 +49,51 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
+
## 🖥️ Interactive Rich Terminal UI
|
|
53
|
+
|
|
54
|
+
`code-meter` includes a full-featured, responsive **Rich Terminal User Interface** that presents live token metrics, USD costs, user queries, and model outputs in real-time.
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
+--------------------------------------------------------------------+
|
|
58
|
+
| CODE METER |
|
|
59
|
+
| Overview |
|
|
60
|
+
+--------------------------------------------------------------------+
|
|
61
|
+
+--------------------------- Key Metrics ----------------------------+
|
|
62
|
+
| Requests: 3 |
|
|
63
|
+
| Input Tokens: 1.5K |
|
|
64
|
+
| Output Tokens: 400 |
|
|
65
|
+
| Cache Read: 300 |
|
|
66
|
+
| Total Tokens: 2.2K |
|
|
67
|
+
| Estimated Cost: $0.02 |
|
|
68
|
+
+--------------------------------------------------------------------+
|
|
69
|
+
| BY MODEL | Tokens | Cost |
|
|
70
|
+
| gemini-2.5-flash | 2.20K | $0.02 |
|
|
71
|
+
+--------------------------------------------------------------------+
|
|
72
|
+
| RECENT USER QUERIES / PROMPTS |
|
|
73
|
+
| 09-01 12:00 | antigravity | Build an interactive dashboard UI |
|
|
74
|
+
+--------------------------------------------------------------------+
|
|
75
|
+
| RECENT OUTPUTS & CODE EDITS |
|
|
76
|
+
| 09-01 12:01 | WRITE_FILE | src/main.py | + print('Hello') |
|
|
77
|
+
+--------------------------------------------------------------------+
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
52
82
|
## 🛠️ CLI Commands
|
|
53
83
|
|
|
54
84
|
| Command | Description |
|
|
55
85
|
| :--- | :--- |
|
|
56
|
-
| `code-meter` | Open interactive Rich terminal dashboard |
|
|
86
|
+
| `code-meter` | Open main interactive Rich terminal dashboard UI |
|
|
87
|
+
| `code-meter ui` | Launch live interactive Rich terminal UI watching all AI log directories |
|
|
88
|
+
| `code-meter watch` | Live watch mode (auto-refreshes UI as AI agents write logs) |
|
|
89
|
+
| `code-meter update-pricing` | Fetch latest real-time token pricing for 2,800+ models |
|
|
57
90
|
| `code-meter scan` | Force an incremental scan of local usage directories |
|
|
58
91
|
| `code-meter today` | Summary of token usage and cost for today |
|
|
59
92
|
| `code-meter week` | Summary of token usage and cost for the past 7 days |
|
|
60
93
|
| `code-meter month` | Summary of token usage and cost for the past 30 days |
|
|
61
94
|
| `code-meter status` | View system status (scanned files, records, prompts, code edits, DB size) |
|
|
62
|
-
| `code-meter prompts` | View log of user prompts
|
|
63
|
-
| `code-meter history` | View log of code
|
|
95
|
+
| `code-meter prompts` | View log of user prompts and queries asked over time |
|
|
96
|
+
| `code-meter history` | View log of code modifications, file writes, and assistant actions |
|
|
64
97
|
| `code-meter diffs` | View formatted code diff summaries saved in SQLite |
|
|
65
98
|
| `code-meter report` | Detailed aggregated report with optional filters |
|
|
66
99
|
| `code-meter models` | Usage and cost breakdown by model |
|
|
@@ -68,7 +101,6 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
68
101
|
| `code-meter sessions` | Usage and cost breakdown by session ID |
|
|
69
102
|
| `code-meter export` | Export usage records to CSV or JSON format |
|
|
70
103
|
| `code-meter config` | Display active configuration settings and file paths |
|
|
71
|
-
| `code-meter watch` | Live file watcher mode for real-time dashboard updates |
|
|
72
104
|
|
|
73
105
|
---
|
|
74
106
|
|
|
@@ -77,6 +109,9 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
77
109
|
Combine filters on reporting, prompts, and history commands:
|
|
78
110
|
|
|
79
111
|
```bash
|
|
112
|
+
# Update real-time token costs for 2,800+ models
|
|
113
|
+
code-meter update-pricing
|
|
114
|
+
|
|
80
115
|
# Filter report by provider, project, model, and timeframe
|
|
81
116
|
code-meter report --provider codex --model gpt-4o --days 30
|
|
82
117
|
code-meter report --provider antigravity --model gemini-2.5-flash
|
|
@@ -143,29 +178,13 @@ ClaudeCodeProvider CodexProvider AntigravityProvider
|
|
|
143
178
|
Database Repository & Incremental Scanner (SQLite)
|
|
144
179
|
│
|
|
145
180
|
▼
|
|
146
|
-
|
|
181
|
+
Pricing Engine (Auto Daily Sync & Live Model Pricing)
|
|
147
182
|
│
|
|
148
183
|
▼
|
|
149
184
|
Analytics Engine (Token counts, Costs, Aggregations)
|
|
150
185
|
│
|
|
151
186
|
▼
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
│
|
|
155
|
-
▼
|
|
156
|
-
UsageRecord / SessionPrompt / SessionCodeChange (Pydantic models)
|
|
157
|
-
│
|
|
158
|
-
▼
|
|
159
|
-
Database Repository & Incremental Scanner (SQLite)
|
|
160
|
-
│
|
|
161
|
-
▼
|
|
162
|
-
Pricing Engine (Model Pricing Tables)
|
|
163
|
-
│
|
|
164
|
-
▼
|
|
165
|
-
Analytics Engine (Token counts, Costs, Aggregations)
|
|
166
|
-
│
|
|
167
|
-
▼
|
|
168
|
-
Terminal UI & Typer CLI Commands
|
|
187
|
+
Rich Terminal UI & Interactive Dashboard Commands
|
|
169
188
|
```
|
|
170
189
|
|
|
171
190
|
---
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-meter"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Local AI coding token usage tracker and cost estimator for Claude Code and future agents."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "Dhanush Nayak", email = "dhanushnayak.ram@mail.com" }]
|
|
@@ -63,6 +63,7 @@ def _get_services():
|
|
|
63
63
|
def _perform_scan(quiet: bool = False) -> int:
|
|
64
64
|
"""Execute scan across all AI providers and return number of new records saved."""
|
|
65
65
|
config, repo, pricing_engine, providers, _ = _get_services()
|
|
66
|
+
pricing_engine.auto_update_if_stale(max_age_hours=24.0)
|
|
66
67
|
existing_states = repo.get_scan_states()
|
|
67
68
|
|
|
68
69
|
total_new_records = 0
|
|
@@ -87,10 +88,49 @@ def _perform_scan(quiet: bool = False) -> int:
|
|
|
87
88
|
return total_new_records
|
|
88
89
|
|
|
89
90
|
|
|
91
|
+
def _launch_live_ui() -> None:
|
|
92
|
+
"""Launch the live watch mode dashboard UI monitoring all active AI provider directories."""
|
|
93
|
+
config, repo, pricing_engine, providers, report_gen = _get_services()
|
|
94
|
+
|
|
95
|
+
def update_and_render():
|
|
96
|
+
_perform_scan(quiet=True)
|
|
97
|
+
records = repo.query_usage_events()
|
|
98
|
+
prompts = repo.query_prompts(limit=10)
|
|
99
|
+
code_changes = repo.query_code_changes(limit=10)
|
|
100
|
+
summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
|
|
101
|
+
console.clear()
|
|
102
|
+
render_dashboard(
|
|
103
|
+
summary=summary,
|
|
104
|
+
prompts=prompts,
|
|
105
|
+
code_changes=code_changes,
|
|
106
|
+
title_suffix="Live Watch Dashboard",
|
|
107
|
+
console=console,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
target_dirs = [
|
|
111
|
+
config.get_claude_dir(),
|
|
112
|
+
config.get_codex_dir(),
|
|
113
|
+
config.get_antigravity_dir(),
|
|
114
|
+
]
|
|
115
|
+
run_watch_loop(
|
|
116
|
+
target_dir=target_dirs,
|
|
117
|
+
on_change_callback=update_and_render,
|
|
118
|
+
refresh_seconds=config.get_refresh_seconds(),
|
|
119
|
+
console=console,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
90
123
|
@app.callback()
|
|
91
|
-
def main_callback(
|
|
124
|
+
def main_callback(
|
|
125
|
+
ctx: typer.Context,
|
|
126
|
+
watch: bool = typer.Option(False, "--watch", "-w", help="Launch live watch UI mode"),
|
|
127
|
+
):
|
|
92
128
|
"""Default action when running `code-meter` without subcommands."""
|
|
93
129
|
if ctx.invoked_subcommand is None:
|
|
130
|
+
if watch:
|
|
131
|
+
_launch_live_ui()
|
|
132
|
+
return
|
|
133
|
+
|
|
94
134
|
_perform_scan(quiet=True)
|
|
95
135
|
config, repo, pricing_engine, providers, report_gen = _get_services()
|
|
96
136
|
|
|
@@ -100,9 +140,18 @@ def main_callback(ctx: typer.Context):
|
|
|
100
140
|
console.print("[dim]Scanned providers: Claude Code, Codex, Antigravity[/dim]")
|
|
101
141
|
return
|
|
102
142
|
|
|
143
|
+
prompts = repo.query_prompts(limit=10)
|
|
144
|
+
code_changes = repo.query_code_changes(limit=10)
|
|
145
|
+
|
|
103
146
|
summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
|
|
104
147
|
console.clear()
|
|
105
|
-
render_dashboard(
|
|
148
|
+
render_dashboard(
|
|
149
|
+
summary=summary,
|
|
150
|
+
prompts=prompts,
|
|
151
|
+
code_changes=code_changes,
|
|
152
|
+
title_suffix="Overview",
|
|
153
|
+
console=console,
|
|
154
|
+
)
|
|
106
155
|
|
|
107
156
|
|
|
108
157
|
@app.command("scan")
|
|
@@ -295,6 +344,58 @@ def export_cmd(
|
|
|
295
344
|
sys.stdout.write(content)
|
|
296
345
|
|
|
297
346
|
|
|
347
|
+
@app.command("prompts")
|
|
348
|
+
def prompts_cmd(
|
|
349
|
+
session: Optional[str] = typer.Option(None, "--session", help="Filter by session ID"),
|
|
350
|
+
project: Optional[str] = typer.Option(None, "--project", help="Filter by project name or path"),
|
|
351
|
+
limit: int = typer.Option(50, "--limit", "-n", help="Maximum number of prompts to display"),
|
|
352
|
+
):
|
|
353
|
+
"""Display log of user prompts and queries asked per session over time."""
|
|
354
|
+
_perform_scan(quiet=True)
|
|
355
|
+
_, repo, _, _, _ = _get_services()
|
|
356
|
+
prompts = repo.query_prompts(session_id=session, project_id=project, limit=limit)
|
|
357
|
+
if not prompts:
|
|
358
|
+
console.print("[yellow]No user prompts recorded yet.[/yellow]")
|
|
359
|
+
return
|
|
360
|
+
from code_meter.ui.tables import render_prompts_table
|
|
361
|
+
render_prompts_table(prompts, console=console)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@app.command("history")
|
|
365
|
+
def history_cmd(
|
|
366
|
+
session: Optional[str] = typer.Option(None, "--session", help="Filter by session ID"),
|
|
367
|
+
project: Optional[str] = typer.Option(None, "--project", help="Filter by project name"),
|
|
368
|
+
file: Optional[str] = typer.Option(None, "--file", help="Filter by modified file path"),
|
|
369
|
+
limit: int = typer.Option(50, "--limit", "-n", help="Maximum number of changes to display"),
|
|
370
|
+
):
|
|
371
|
+
"""Display history of code modifications, file writes, and assistant output actions."""
|
|
372
|
+
_perform_scan(quiet=True)
|
|
373
|
+
_, repo, _, _, _ = _get_services()
|
|
374
|
+
changes = repo.query_code_changes(session_id=session, project_id=project, file_path=file, limit=limit)
|
|
375
|
+
if not changes:
|
|
376
|
+
console.print("[yellow]No code modifications or output actions recorded yet.[/yellow]")
|
|
377
|
+
return
|
|
378
|
+
from code_meter.ui.tables import render_code_changes_table
|
|
379
|
+
render_code_changes_table(changes, console=console)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@app.command("diffs")
|
|
383
|
+
def diffs_cmd(
|
|
384
|
+
session: Optional[str] = typer.Option(None, "--session", help="Filter by session ID"),
|
|
385
|
+
file: Optional[str] = typer.Option(None, "--file", help="Filter by file path"),
|
|
386
|
+
limit: int = typer.Option(50, "--limit", "-n", help="Maximum number of diffs to display"),
|
|
387
|
+
):
|
|
388
|
+
"""Display formatted code diff summaries saved in database."""
|
|
389
|
+
_perform_scan(quiet=True)
|
|
390
|
+
_, repo, _, _, _ = _get_services()
|
|
391
|
+
changes = repo.query_code_changes(session_id=session, file_path=file, limit=limit)
|
|
392
|
+
if not changes:
|
|
393
|
+
console.print("[yellow]No diff summaries recorded yet.[/yellow]")
|
|
394
|
+
return
|
|
395
|
+
from code_meter.ui.tables import render_code_changes_table
|
|
396
|
+
render_code_changes_table(changes, console=console)
|
|
397
|
+
|
|
398
|
+
|
|
298
399
|
@app.command("config")
|
|
299
400
|
def config_cmd():
|
|
300
401
|
"""Display current configuration file path and contents."""
|
|
@@ -309,25 +410,28 @@ def config_cmd():
|
|
|
309
410
|
console.print(f"[bold white]Currency:[/bold white] {config.pricing.currency}")
|
|
310
411
|
|
|
311
412
|
|
|
413
|
+
@app.command("update-pricing")
|
|
414
|
+
def update_pricing_cmd():
|
|
415
|
+
"""Fetch latest real-time token pricing for AI models from live catalog."""
|
|
416
|
+
_, _, pricing_engine, _, _ = _get_services()
|
|
417
|
+
console.print("[cyan]Fetching real-time model token pricing...[/cyan]")
|
|
418
|
+
count = pricing_engine.update_live_pricing()
|
|
419
|
+
if count > 0:
|
|
420
|
+
console.print(f"[bold green]Successfully updated real-time pricing for {count:,} models![/bold green]")
|
|
421
|
+
else:
|
|
422
|
+
console.print("[yellow]Could not update pricing or offline using fallback pricing.[/yellow]")
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
@app.command("ui")
|
|
426
|
+
def ui_cmd():
|
|
427
|
+
"""Launch the live interactive Rich terminal UI with auto-refresh watch mode."""
|
|
428
|
+
_launch_live_ui()
|
|
429
|
+
|
|
430
|
+
|
|
312
431
|
@app.command("watch")
|
|
313
432
|
def watch_cmd():
|
|
314
433
|
"""Monitor local session log files in real-time and auto-update metrics."""
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
def update_and_render():
|
|
318
|
-
_perform_scan(quiet=True)
|
|
319
|
-
records = repo.query_usage_events()
|
|
320
|
-
summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
|
|
321
|
-
console.clear()
|
|
322
|
-
render_dashboard(summary, title_suffix="Watch Mode (Live)", console=console)
|
|
323
|
-
|
|
324
|
-
target_dir = providers[0].claude_dir if providers else config.get_claude_dir()
|
|
325
|
-
run_watch_loop(
|
|
326
|
-
target_dir=target_dir,
|
|
327
|
-
on_change_callback=update_and_render,
|
|
328
|
-
refresh_seconds=config.get_refresh_seconds(),
|
|
329
|
-
console=console,
|
|
330
|
-
)
|
|
434
|
+
_launch_live_ui()
|
|
331
435
|
|
|
332
436
|
|
|
333
437
|
if __name__ == "__main__":
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Dynamic Pricing Engine for calculating USD estimated cost of AI token usage."""
|
|
2
2
|
|
|
3
|
-
from datetime import datetime
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
4
|
from typing import Any, Dict, List, Optional
|
|
5
5
|
from code_meter.pricing.anthropic import DEFAULT_ANTHROPIC_PRICING
|
|
6
6
|
from code_meter.pricing.google import DEFAULT_GOOGLE_PRICING
|
|
7
|
+
from code_meter.pricing.live import fetch_realtime_pricing
|
|
7
8
|
from code_meter.pricing.openai import DEFAULT_OPENAI_PRICING
|
|
8
9
|
from code_meter.storage.repository import UsageRepository
|
|
9
10
|
|
|
@@ -29,6 +30,39 @@ class PricingEngine:
|
|
|
29
30
|
else:
|
|
30
31
|
self._pricing_cache = DEFAULT_PRICING
|
|
31
32
|
|
|
33
|
+
def update_live_pricing(self) -> int:
|
|
34
|
+
"""Fetch latest real-time model token pricing and update active pricing engine."""
|
|
35
|
+
records = fetch_realtime_pricing(self.repository)
|
|
36
|
+
if records:
|
|
37
|
+
self.reload_pricing()
|
|
38
|
+
if self.repository:
|
|
39
|
+
self.repository.set_meta_value("last_pricing_sync", datetime.now(timezone.utc).isoformat())
|
|
40
|
+
return len(records)
|
|
41
|
+
|
|
42
|
+
def auto_update_if_stale(self, max_age_hours: float = 24.0) -> bool:
|
|
43
|
+
"""Automatically check if pricing data is older than max_age_hours and update daily in background."""
|
|
44
|
+
if not self.repository:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
last_sync_str = self.repository.get_meta_value("last_pricing_sync")
|
|
48
|
+
should_update = False
|
|
49
|
+
|
|
50
|
+
if not last_sync_str:
|
|
51
|
+
should_update = True
|
|
52
|
+
else:
|
|
53
|
+
try:
|
|
54
|
+
last_sync = datetime.fromisoformat(last_sync_str)
|
|
55
|
+
age = datetime.now(timezone.utc) - last_sync
|
|
56
|
+
if age.total_seconds() > (max_age_hours * 3600.0):
|
|
57
|
+
should_update = True
|
|
58
|
+
except Exception:
|
|
59
|
+
should_update = True
|
|
60
|
+
|
|
61
|
+
if should_update:
|
|
62
|
+
count = self.update_live_pricing()
|
|
63
|
+
return count > 0
|
|
64
|
+
return False
|
|
65
|
+
|
|
32
66
|
def calculate_cost(
|
|
33
67
|
self,
|
|
34
68
|
provider: str,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Real-time live model token pricing fetcher."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import urllib.request
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from code_meter.storage.repository import UsageRepository
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
PRICING_API_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def fetch_realtime_pricing(repository: Optional[UsageRepository] = None, timeout: int = 10) -> List[Dict[str, Any]]:
|
|
17
|
+
"""Fetch real-time token pricing for AI models and save to SQLite repository."""
|
|
18
|
+
pricing_records: List[Dict[str, Any]] = []
|
|
19
|
+
now_str = datetime.now(timezone.utc).isoformat()
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
req = urllib.request.Request(
|
|
23
|
+
PRICING_API_URL,
|
|
24
|
+
headers={"User-Agent": "code-meter/0.1.0 (https://github.com/dhanushnayak/code-meter)"},
|
|
25
|
+
)
|
|
26
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
27
|
+
raw_data = resp.read()
|
|
28
|
+
data = json.loads(raw_data.decode("utf-8"))
|
|
29
|
+
|
|
30
|
+
for model_name, info in data.items():
|
|
31
|
+
if not isinstance(info, dict):
|
|
32
|
+
continue
|
|
33
|
+
|
|
34
|
+
input_per_tok = info.get("input_cost_per_token")
|
|
35
|
+
output_per_tok = info.get("output_cost_per_token")
|
|
36
|
+
|
|
37
|
+
if input_per_tok is None and output_per_tok is None:
|
|
38
|
+
continue
|
|
39
|
+
|
|
40
|
+
input_ppm = float(input_per_tok or 0.0) * 1_000_000.0
|
|
41
|
+
output_ppm = float(output_per_tok or 0.0) * 1_000_000.0
|
|
42
|
+
cache_read_ppm = float(info.get("cache_read_input_token_cost") or 0.0) * 1_000_000.0
|
|
43
|
+
cache_write_ppm = float(info.get("cache_creation_input_token_cost") or 0.0) * 1_000_000.0
|
|
44
|
+
|
|
45
|
+
raw_provider = str(info.get("litellm_provider") or "unknown").lower()
|
|
46
|
+
if "anthropic" in raw_provider or "claude" in model_name:
|
|
47
|
+
provider = "claude_code"
|
|
48
|
+
elif "openai" in raw_provider or "gpt" in model_name or "o1" in model_name or "o3" in model_name:
|
|
49
|
+
provider = "codex"
|
|
50
|
+
elif "gemini" in model_name or "vertex" in raw_provider or "google" in raw_provider:
|
|
51
|
+
provider = "antigravity"
|
|
52
|
+
else:
|
|
53
|
+
provider = raw_provider
|
|
54
|
+
|
|
55
|
+
record = {
|
|
56
|
+
"provider": provider,
|
|
57
|
+
"model": model_name,
|
|
58
|
+
"input_price_per_million": input_ppm,
|
|
59
|
+
"output_price_per_million": output_ppm,
|
|
60
|
+
"cache_read_price_per_million": cache_read_ppm,
|
|
61
|
+
"cache_write_price_per_million": cache_write_ppm,
|
|
62
|
+
"effective_from": now_str,
|
|
63
|
+
}
|
|
64
|
+
pricing_records.append(record)
|
|
65
|
+
|
|
66
|
+
if repository and pricing_records:
|
|
67
|
+
repository.save_pricing(pricing_records)
|
|
68
|
+
|
|
69
|
+
except Exception as e:
|
|
70
|
+
logger.warning(f"Could not fetch real-time pricing from {PRICING_API_URL}: {e}")
|
|
71
|
+
|
|
72
|
+
return pricing_records
|
|
@@ -132,6 +132,17 @@ class Database:
|
|
|
132
132
|
"""
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
+
# system_meta table for metadata tracking (e.g. last_pricing_sync)
|
|
136
|
+
cursor.execute(
|
|
137
|
+
"""
|
|
138
|
+
CREATE TABLE IF NOT EXISTS system_meta (
|
|
139
|
+
key TEXT PRIMARY KEY,
|
|
140
|
+
value TEXT NOT NULL,
|
|
141
|
+
updated_at TEXT NOT NULL
|
|
142
|
+
);
|
|
143
|
+
"""
|
|
144
|
+
)
|
|
145
|
+
|
|
135
146
|
# Indexes for high performance queries
|
|
136
147
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_usage_ts ON usage_events(timestamp);")
|
|
137
148
|
cursor.execute("CREATE INDEX IF NOT EXISTS idx_usage_model ON usage_events(model);")
|
|
@@ -480,3 +480,34 @@ class UsageRepository:
|
|
|
480
480
|
}
|
|
481
481
|
finally:
|
|
482
482
|
conn.close()
|
|
483
|
+
|
|
484
|
+
def get_meta_value(self, key: str) -> Optional[str]:
|
|
485
|
+
"""Retrieve system metadata string value by key."""
|
|
486
|
+
conn = self.db.get_connection()
|
|
487
|
+
try:
|
|
488
|
+
cursor = conn.cursor()
|
|
489
|
+
cursor.execute("SELECT value FROM system_meta WHERE key = ?;", (key,))
|
|
490
|
+
row = cursor.fetchone()
|
|
491
|
+
return row["value"] if row else None
|
|
492
|
+
finally:
|
|
493
|
+
conn.close()
|
|
494
|
+
|
|
495
|
+
def set_meta_value(self, key: str, value: str) -> None:
|
|
496
|
+
"""Upsert system metadata string value by key."""
|
|
497
|
+
now_str = datetime.now(timezone.utc).isoformat()
|
|
498
|
+
conn = self.db.get_connection()
|
|
499
|
+
try:
|
|
500
|
+
cursor = conn.cursor()
|
|
501
|
+
cursor.execute(
|
|
502
|
+
"""
|
|
503
|
+
INSERT INTO system_meta (key, value, updated_at)
|
|
504
|
+
VALUES (?, ?, ?)
|
|
505
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
506
|
+
value = excluded.value,
|
|
507
|
+
updated_at = excluded.updated_at;
|
|
508
|
+
""",
|
|
509
|
+
(key, value, now_str),
|
|
510
|
+
)
|
|
511
|
+
conn.commit()
|
|
512
|
+
finally:
|
|
513
|
+
conn.close()
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
from typing import Optional
|
|
1
|
+
from typing import List, Optional
|
|
4
2
|
from rich.console import Console
|
|
5
3
|
from rich.panel import Panel
|
|
6
4
|
from rich.table import Table
|
|
7
5
|
from rich.text import Text
|
|
8
6
|
|
|
9
7
|
from code_meter.models.project import AggregateSummary
|
|
8
|
+
from code_meter.models.usage import SessionCodeChange, SessionPrompt
|
|
10
9
|
from code_meter.ui.tables import format_cost, format_tokens
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def render_dashboard(
|
|
14
13
|
summary: AggregateSummary,
|
|
14
|
+
prompts: Optional[List[SessionPrompt]] = None,
|
|
15
|
+
code_changes: Optional[List[SessionCodeChange]] = None,
|
|
15
16
|
title_suffix: str = "Overview",
|
|
16
17
|
console: Optional[Console] = None,
|
|
17
18
|
) -> None:
|
|
18
19
|
c = console or Console()
|
|
19
20
|
|
|
20
21
|
header_text = Text()
|
|
21
|
-
header_text.append("CODE METER\n", style="bold cyan
|
|
22
|
+
header_text.append("CODE METER\n", style="bold cyan")
|
|
22
23
|
header_text.append(f"{title_suffix}", style="dim white")
|
|
23
24
|
header_panel = Panel(header_text, style="bold blue", expand=True)
|
|
24
25
|
|
|
@@ -109,3 +110,36 @@ def render_dashboard(
|
|
|
109
110
|
c.print(project_table)
|
|
110
111
|
c.print(today_panel)
|
|
111
112
|
c.print(burn_panel)
|
|
113
|
+
|
|
114
|
+
# 1. User Prompts / Queries Panel
|
|
115
|
+
if prompts:
|
|
116
|
+
prompt_table = Table(title="RECENT USER QUERIES / PROMPTS", header_style="bold cyan", expand=True)
|
|
117
|
+
prompt_table.add_column("Time", style="dim", width=16)
|
|
118
|
+
prompt_table.add_column("Provider", style="yellow", width=12)
|
|
119
|
+
prompt_table.add_column("User Query", style="white")
|
|
120
|
+
|
|
121
|
+
for p in prompts[:5]:
|
|
122
|
+
ts_str = p.timestamp.strftime("%m-%d %H:%M")
|
|
123
|
+
txt = p.prompt_text.replace("\n", " ")
|
|
124
|
+
if len(txt) > 80:
|
|
125
|
+
txt = txt[:77] + "..."
|
|
126
|
+
prompt_table.add_row(ts_str, p.provider, txt)
|
|
127
|
+
|
|
128
|
+
c.print(prompt_table)
|
|
129
|
+
|
|
130
|
+
# 2. Output Code Changes / Diff Actions Panel
|
|
131
|
+
if code_changes:
|
|
132
|
+
changes_table = Table(title="RECENT OUTPUTS & CODE EDITS", header_style="bold green", expand=True)
|
|
133
|
+
changes_table.add_column("Time", style="dim", width=16)
|
|
134
|
+
changes_table.add_column("Type", style="magenta", width=10)
|
|
135
|
+
changes_table.add_column("File Path", style="cyan", width=30)
|
|
136
|
+
changes_table.add_column("Output / Action Summary", style="white")
|
|
137
|
+
|
|
138
|
+
for ch in code_changes[:5]:
|
|
139
|
+
ts_str = ch.timestamp.strftime("%m-%d %H:%M")
|
|
140
|
+
diff_txt = ch.diff_summary.replace("\n", " | ")
|
|
141
|
+
if len(diff_txt) > 60:
|
|
142
|
+
diff_txt = diff_txt[:57] + "..."
|
|
143
|
+
changes_table.add_row(ts_str, ch.change_type.upper(), ch.file_path, diff_txt)
|
|
144
|
+
|
|
145
|
+
c.print(changes_table)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import time
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Callable, Optional
|
|
6
|
+
from typing import Callable, List, Optional, Union
|
|
7
7
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
|
|
@@ -18,26 +18,34 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def run_watch_loop(
|
|
21
|
-
target_dir: Path,
|
|
21
|
+
target_dir: Union[Path, List[Path]],
|
|
22
22
|
on_change_callback: Callable[[], None],
|
|
23
23
|
refresh_seconds: int = 2,
|
|
24
24
|
console: Optional[Console] = None,
|
|
25
25
|
) -> None:
|
|
26
|
-
"""Monitor
|
|
26
|
+
"""Monitor target directories and trigger on_change_callback whenever session log files update."""
|
|
27
27
|
c = console or Console()
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
dirs = [target_dir] if isinstance(target_dir, Path) else target_dir
|
|
29
|
+
existing_dirs = [d for d in dirs if d.exists()]
|
|
30
|
+
|
|
31
|
+
if existing_dirs:
|
|
32
|
+
dir_strs = ", ".join(str(d) for d in existing_dirs)
|
|
33
|
+
c.print(f"[bold green]Watching directories:[/bold green] {dir_strs}")
|
|
34
|
+
else:
|
|
35
|
+
c.print("[yellow]No target provider directories found yet. Waiting for logs...[/yellow]")
|
|
36
|
+
c.print("[dim]Press Ctrl+C to stop live watch UI mode.[/dim]\n")
|
|
30
37
|
|
|
31
38
|
on_change_callback()
|
|
32
39
|
|
|
33
|
-
if HAS_WATCHDOG and
|
|
40
|
+
if HAS_WATCHDOG and existing_dirs:
|
|
34
41
|
class JSONLHandler(FileSystemEventHandler):
|
|
35
42
|
def on_any_event(self, event):
|
|
36
|
-
if not event.is_directory and event.src_path.endswith(".jsonl"):
|
|
43
|
+
if not event.is_directory and (event.src_path.endswith(".jsonl") or event.src_path.endswith(".json")):
|
|
37
44
|
on_change_callback()
|
|
38
45
|
|
|
39
46
|
observer = Observer()
|
|
40
|
-
|
|
47
|
+
for d in existing_dirs:
|
|
48
|
+
observer.schedule(JSONLHandler(), str(d), recursive=True)
|
|
41
49
|
observer.start()
|
|
42
50
|
try:
|
|
43
51
|
while True:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-meter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Local AI coding token usage tracker and cost estimator for Claude Code and future agents.
|
|
5
5
|
Author-email: Dhanush Nayak <dhanushnayak.ram@mail.com>
|
|
6
6
|
License: MIT
|
|
@@ -9,7 +9,6 @@ Project-URL: Repository, https://github.com/dhanushnayak/code-meter
|
|
|
9
9
|
Project-URL: Bug Tracker, https://github.com/dhanushnayak/code-meter/issues
|
|
10
10
|
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
|
-
License-File: LICENSE
|
|
13
12
|
Requires-Dist: typer[all]>=0.9.0
|
|
14
13
|
Requires-Dist: rich>=13.0.0
|
|
15
14
|
Requires-Dist: pydantic>=2.0.0
|
|
@@ -20,7 +19,6 @@ Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
|
20
19
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
21
20
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
22
21
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
23
|
-
Dynamic: license-file
|
|
24
22
|
|
|
25
23
|
# code-meter
|
|
26
24
|
|
|
@@ -73,18 +71,51 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
73
71
|
|
|
74
72
|
---
|
|
75
73
|
|
|
74
|
+
## 🖥️ Interactive Rich Terminal UI
|
|
75
|
+
|
|
76
|
+
`code-meter` includes a full-featured, responsive **Rich Terminal User Interface** that presents live token metrics, USD costs, user queries, and model outputs in real-time.
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
+--------------------------------------------------------------------+
|
|
80
|
+
| CODE METER |
|
|
81
|
+
| Overview |
|
|
82
|
+
+--------------------------------------------------------------------+
|
|
83
|
+
+--------------------------- Key Metrics ----------------------------+
|
|
84
|
+
| Requests: 3 |
|
|
85
|
+
| Input Tokens: 1.5K |
|
|
86
|
+
| Output Tokens: 400 |
|
|
87
|
+
| Cache Read: 300 |
|
|
88
|
+
| Total Tokens: 2.2K |
|
|
89
|
+
| Estimated Cost: $0.02 |
|
|
90
|
+
+--------------------------------------------------------------------+
|
|
91
|
+
| BY MODEL | Tokens | Cost |
|
|
92
|
+
| gemini-2.5-flash | 2.20K | $0.02 |
|
|
93
|
+
+--------------------------------------------------------------------+
|
|
94
|
+
| RECENT USER QUERIES / PROMPTS |
|
|
95
|
+
| 09-01 12:00 | antigravity | Build an interactive dashboard UI |
|
|
96
|
+
+--------------------------------------------------------------------+
|
|
97
|
+
| RECENT OUTPUTS & CODE EDITS |
|
|
98
|
+
| 09-01 12:01 | WRITE_FILE | src/main.py | + print('Hello') |
|
|
99
|
+
+--------------------------------------------------------------------+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
76
104
|
## 🛠️ CLI Commands
|
|
77
105
|
|
|
78
106
|
| Command | Description |
|
|
79
107
|
| :--- | :--- |
|
|
80
|
-
| `code-meter` | Open interactive Rich terminal dashboard |
|
|
108
|
+
| `code-meter` | Open main interactive Rich terminal dashboard UI |
|
|
109
|
+
| `code-meter ui` | Launch live interactive Rich terminal UI watching all AI log directories |
|
|
110
|
+
| `code-meter watch` | Live watch mode (auto-refreshes UI as AI agents write logs) |
|
|
111
|
+
| `code-meter update-pricing` | Fetch latest real-time token pricing for 2,800+ models |
|
|
81
112
|
| `code-meter scan` | Force an incremental scan of local usage directories |
|
|
82
113
|
| `code-meter today` | Summary of token usage and cost for today |
|
|
83
114
|
| `code-meter week` | Summary of token usage and cost for the past 7 days |
|
|
84
115
|
| `code-meter month` | Summary of token usage and cost for the past 30 days |
|
|
85
116
|
| `code-meter status` | View system status (scanned files, records, prompts, code edits, DB size) |
|
|
86
|
-
| `code-meter prompts` | View log of user prompts
|
|
87
|
-
| `code-meter history` | View log of code
|
|
117
|
+
| `code-meter prompts` | View log of user prompts and queries asked over time |
|
|
118
|
+
| `code-meter history` | View log of code modifications, file writes, and assistant actions |
|
|
88
119
|
| `code-meter diffs` | View formatted code diff summaries saved in SQLite |
|
|
89
120
|
| `code-meter report` | Detailed aggregated report with optional filters |
|
|
90
121
|
| `code-meter models` | Usage and cost breakdown by model |
|
|
@@ -92,7 +123,6 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
92
123
|
| `code-meter sessions` | Usage and cost breakdown by session ID |
|
|
93
124
|
| `code-meter export` | Export usage records to CSV or JSON format |
|
|
94
125
|
| `code-meter config` | Display active configuration settings and file paths |
|
|
95
|
-
| `code-meter watch` | Live file watcher mode for real-time dashboard updates |
|
|
96
126
|
|
|
97
127
|
---
|
|
98
128
|
|
|
@@ -101,6 +131,9 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
101
131
|
Combine filters on reporting, prompts, and history commands:
|
|
102
132
|
|
|
103
133
|
```bash
|
|
134
|
+
# Update real-time token costs for 2,800+ models
|
|
135
|
+
code-meter update-pricing
|
|
136
|
+
|
|
104
137
|
# Filter report by provider, project, model, and timeframe
|
|
105
138
|
code-meter report --provider codex --model gpt-4o --days 30
|
|
106
139
|
code-meter report --provider antigravity --model gemini-2.5-flash
|
|
@@ -167,29 +200,13 @@ ClaudeCodeProvider CodexProvider AntigravityProvider
|
|
|
167
200
|
Database Repository & Incremental Scanner (SQLite)
|
|
168
201
|
│
|
|
169
202
|
▼
|
|
170
|
-
|
|
203
|
+
Pricing Engine (Auto Daily Sync & Live Model Pricing)
|
|
171
204
|
│
|
|
172
205
|
▼
|
|
173
206
|
Analytics Engine (Token counts, Costs, Aggregations)
|
|
174
207
|
│
|
|
175
208
|
▼
|
|
176
|
-
|
|
177
|
-
```
|
|
178
|
-
│
|
|
179
|
-
▼
|
|
180
|
-
UsageRecord / SessionPrompt / SessionCodeChange (Pydantic models)
|
|
181
|
-
│
|
|
182
|
-
▼
|
|
183
|
-
Database Repository & Incremental Scanner (SQLite)
|
|
184
|
-
│
|
|
185
|
-
▼
|
|
186
|
-
Pricing Engine (Model Pricing Tables)
|
|
187
|
-
│
|
|
188
|
-
▼
|
|
189
|
-
Analytics Engine (Token counts, Costs, Aggregations)
|
|
190
|
-
│
|
|
191
|
-
▼
|
|
192
|
-
Terminal UI & Typer CLI Commands
|
|
209
|
+
Rich Terminal UI & Interactive Dashboard Commands
|
|
193
210
|
```
|
|
194
211
|
|
|
195
212
|
---
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
LICENSE
|
|
2
1
|
README.md
|
|
3
2
|
pyproject.toml
|
|
4
3
|
setup.py
|
|
@@ -23,6 +22,7 @@ src/code_meter/pricing/__init__.py
|
|
|
23
22
|
src/code_meter/pricing/anthropic.py
|
|
24
23
|
src/code_meter/pricing/engine.py
|
|
25
24
|
src/code_meter/pricing/google.py
|
|
25
|
+
src/code_meter/pricing/live.py
|
|
26
26
|
src/code_meter/pricing/openai.py
|
|
27
27
|
src/code_meter/providers/__init__.py
|
|
28
28
|
src/code_meter/providers/antigravity.py
|
code_meter-0.1.0/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Antigravity Team
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF WARRANTY OF ANY KIND,
|
|
16
|
-
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
-
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
18
|
-
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
19
|
-
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|