code-meter 0.2.0__tar.gz → 0.4.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.2.0 → code_meter-0.4.0}/PKG-INFO +43 -23
- {code_meter-0.2.0 → code_meter-0.4.0}/README.md +42 -22
- {code_meter-0.2.0 → code_meter-0.4.0}/pyproject.toml +1 -1
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/cli.py +67 -25
- code_meter-0.4.0/src/code_meter/ui/web/__init__.py +5 -0
- code_meter-0.4.0/src/code_meter/ui/web/server.py +194 -0
- code_meter-0.4.0/src/code_meter/ui/web/static.py +497 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/watcher.py +16 -8
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/PKG-INFO +43 -23
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/SOURCES.txt +3 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/setup.cfg +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/setup.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/__init__.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/__init__.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/costs.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/reports.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/tokens.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/config.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/models/__init__.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/models/project.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/models/usage.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/__init__.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/anthropic.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/engine.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/google.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/live.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/openai.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/__init__.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/antigravity.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/base.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/claude_code.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/codex.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/storage/__init__.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/storage/database.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/storage/repository.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/ui/__init__.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/ui/dashboard.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/ui/tables.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/dependency_links.txt +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/entry_points.txt +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/requires.txt +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/top_level.txt +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/tests/test_analytics.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/tests/test_database.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.0}/tests/test_pricing.py +0 -0
- {code_meter-0.2.0 → code_meter-0.4.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.4.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
|
|
@@ -71,18 +71,52 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
71
71
|
|
|
72
72
|
---
|
|
73
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
|
+
|
|
74
104
|
## 🛠️ CLI Commands
|
|
75
105
|
|
|
76
106
|
| Command | Description |
|
|
77
107
|
| :--- | :--- |
|
|
78
|
-
| `code-meter` | Open interactive Rich terminal dashboard |
|
|
108
|
+
| `code-meter` | Open main interactive Rich terminal dashboard UI |
|
|
109
|
+
| `code-meter ui` | Launch live Web Dashboard on **http://localhost:5005** (auto-opens browser) |
|
|
110
|
+
| `code-meter ui --terminal` | Launch live interactive Rich terminal UI |
|
|
111
|
+
| `code-meter watch` | Live watch mode (auto-refreshes UI as AI agents write logs) |
|
|
112
|
+
| `code-meter update-pricing` | Fetch latest real-time token pricing for 2,800+ models |
|
|
79
113
|
| `code-meter scan` | Force an incremental scan of local usage directories |
|
|
80
114
|
| `code-meter today` | Summary of token usage and cost for today |
|
|
81
115
|
| `code-meter week` | Summary of token usage and cost for the past 7 days |
|
|
82
116
|
| `code-meter month` | Summary of token usage and cost for the past 30 days |
|
|
83
117
|
| `code-meter status` | View system status (scanned files, records, prompts, code edits, DB size) |
|
|
84
|
-
| `code-meter prompts` | View log of user prompts
|
|
85
|
-
| `code-meter history` | View log of code
|
|
118
|
+
| `code-meter prompts` | View log of user prompts and queries asked over time |
|
|
119
|
+
| `code-meter history` | View log of code modifications, file writes, and assistant actions |
|
|
86
120
|
| `code-meter diffs` | View formatted code diff summaries saved in SQLite |
|
|
87
121
|
| `code-meter report` | Detailed aggregated report with optional filters |
|
|
88
122
|
| `code-meter models` | Usage and cost breakdown by model |
|
|
@@ -90,7 +124,6 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
90
124
|
| `code-meter sessions` | Usage and cost breakdown by session ID |
|
|
91
125
|
| `code-meter export` | Export usage records to CSV or JSON format |
|
|
92
126
|
| `code-meter config` | Display active configuration settings and file paths |
|
|
93
|
-
| `code-meter watch` | Live file watcher mode for real-time dashboard updates |
|
|
94
127
|
|
|
95
128
|
---
|
|
96
129
|
|
|
@@ -99,6 +132,9 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
99
132
|
Combine filters on reporting, prompts, and history commands:
|
|
100
133
|
|
|
101
134
|
```bash
|
|
135
|
+
# Update real-time token costs for 2,800+ models
|
|
136
|
+
code-meter update-pricing
|
|
137
|
+
|
|
102
138
|
# Filter report by provider, project, model, and timeframe
|
|
103
139
|
code-meter report --provider codex --model gpt-4o --days 30
|
|
104
140
|
code-meter report --provider antigravity --model gemini-2.5-flash
|
|
@@ -165,29 +201,13 @@ ClaudeCodeProvider CodexProvider AntigravityProvider
|
|
|
165
201
|
Database Repository & Incremental Scanner (SQLite)
|
|
166
202
|
│
|
|
167
203
|
▼
|
|
168
|
-
|
|
204
|
+
Pricing Engine (Auto Daily Sync & Live Model Pricing)
|
|
169
205
|
│
|
|
170
206
|
▼
|
|
171
207
|
Analytics Engine (Token counts, Costs, Aggregations)
|
|
172
208
|
│
|
|
173
209
|
▼
|
|
174
|
-
|
|
175
|
-
```
|
|
176
|
-
│
|
|
177
|
-
▼
|
|
178
|
-
UsageRecord / SessionPrompt / SessionCodeChange (Pydantic models)
|
|
179
|
-
│
|
|
180
|
-
▼
|
|
181
|
-
Database Repository & Incremental Scanner (SQLite)
|
|
182
|
-
│
|
|
183
|
-
▼
|
|
184
|
-
Pricing Engine (Model Pricing Tables)
|
|
185
|
-
│
|
|
186
|
-
▼
|
|
187
|
-
Analytics Engine (Token counts, Costs, Aggregations)
|
|
188
|
-
│
|
|
189
|
-
▼
|
|
190
|
-
Terminal UI & Typer CLI Commands
|
|
210
|
+
Rich Terminal UI & Interactive Dashboard Commands
|
|
191
211
|
```
|
|
192
212
|
|
|
193
213
|
---
|
|
@@ -49,18 +49,52 @@ 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 Web Dashboard on **http://localhost:5005** (auto-opens browser) |
|
|
88
|
+
| `code-meter ui --terminal` | Launch live interactive Rich terminal UI |
|
|
89
|
+
| `code-meter watch` | Live watch mode (auto-refreshes UI as AI agents write logs) |
|
|
90
|
+
| `code-meter update-pricing` | Fetch latest real-time token pricing for 2,800+ models |
|
|
57
91
|
| `code-meter scan` | Force an incremental scan of local usage directories |
|
|
58
92
|
| `code-meter today` | Summary of token usage and cost for today |
|
|
59
93
|
| `code-meter week` | Summary of token usage and cost for the past 7 days |
|
|
60
94
|
| `code-meter month` | Summary of token usage and cost for the past 30 days |
|
|
61
95
|
| `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
|
|
96
|
+
| `code-meter prompts` | View log of user prompts and queries asked over time |
|
|
97
|
+
| `code-meter history` | View log of code modifications, file writes, and assistant actions |
|
|
64
98
|
| `code-meter diffs` | View formatted code diff summaries saved in SQLite |
|
|
65
99
|
| `code-meter report` | Detailed aggregated report with optional filters |
|
|
66
100
|
| `code-meter models` | Usage and cost breakdown by model |
|
|
@@ -68,7 +102,6 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
68
102
|
| `code-meter sessions` | Usage and cost breakdown by session ID |
|
|
69
103
|
| `code-meter export` | Export usage records to CSV or JSON format |
|
|
70
104
|
| `code-meter config` | Display active configuration settings and file paths |
|
|
71
|
-
| `code-meter watch` | Live file watcher mode for real-time dashboard updates |
|
|
72
105
|
|
|
73
106
|
---
|
|
74
107
|
|
|
@@ -77,6 +110,9 @@ This launches the interactive Rich terminal dashboard displaying key metrics, mo
|
|
|
77
110
|
Combine filters on reporting, prompts, and history commands:
|
|
78
111
|
|
|
79
112
|
```bash
|
|
113
|
+
# Update real-time token costs for 2,800+ models
|
|
114
|
+
code-meter update-pricing
|
|
115
|
+
|
|
80
116
|
# Filter report by provider, project, model, and timeframe
|
|
81
117
|
code-meter report --provider codex --model gpt-4o --days 30
|
|
82
118
|
code-meter report --provider antigravity --model gemini-2.5-flash
|
|
@@ -143,29 +179,13 @@ ClaudeCodeProvider CodexProvider AntigravityProvider
|
|
|
143
179
|
Database Repository & Incremental Scanner (SQLite)
|
|
144
180
|
│
|
|
145
181
|
▼
|
|
146
|
-
|
|
182
|
+
Pricing Engine (Auto Daily Sync & Live Model Pricing)
|
|
147
183
|
│
|
|
148
184
|
▼
|
|
149
185
|
Analytics Engine (Token counts, Costs, Aggregations)
|
|
150
186
|
│
|
|
151
187
|
▼
|
|
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
|
|
188
|
+
Rich Terminal UI & Interactive Dashboard Commands
|
|
169
189
|
```
|
|
170
190
|
|
|
171
191
|
---
|
|
@@ -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.4.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" }]
|
|
@@ -88,10 +88,49 @@ def _perform_scan(quiet: bool = False) -> int:
|
|
|
88
88
|
return total_new_records
|
|
89
89
|
|
|
90
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
|
+
|
|
91
123
|
@app.callback()
|
|
92
|
-
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
|
+
):
|
|
93
128
|
"""Default action when running `code-meter` without subcommands."""
|
|
94
129
|
if ctx.invoked_subcommand is None:
|
|
130
|
+
if watch:
|
|
131
|
+
_launch_live_ui()
|
|
132
|
+
return
|
|
133
|
+
|
|
95
134
|
_perform_scan(quiet=True)
|
|
96
135
|
config, repo, pricing_engine, providers, report_gen = _get_services()
|
|
97
136
|
|
|
@@ -383,33 +422,36 @@ def update_pricing_cmd():
|
|
|
383
422
|
console.print("[yellow]Could not update pricing or offline using fallback pricing.[/yellow]")
|
|
384
423
|
|
|
385
424
|
|
|
425
|
+
@app.command("ui")
|
|
426
|
+
@app.command("dashboard")
|
|
427
|
+
@app.command("web")
|
|
428
|
+
def ui_cmd(
|
|
429
|
+
port: int = typer.Option(5005, "--port", "-p", help="Port number for local dashboard server"),
|
|
430
|
+
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host address for dashboard server"),
|
|
431
|
+
terminal: bool = typer.Option(False, "--terminal", "-t", help="Launch Rich terminal UI instead of Web UI"),
|
|
432
|
+
no_browser: bool = typer.Option(False, "--no-browser", help="Do not open web browser automatically"),
|
|
433
|
+
):
|
|
434
|
+
"""Launch local web dashboard UI on http://localhost:5005 (or terminal UI with --terminal)."""
|
|
435
|
+
if terminal:
|
|
436
|
+
_launch_live_ui()
|
|
437
|
+
else:
|
|
438
|
+
from code_meter.ui.web.server import start_web_server
|
|
439
|
+
config, repo, pricing_engine, _, report_gen = _get_services()
|
|
440
|
+
start_web_server(
|
|
441
|
+
host=host,
|
|
442
|
+
port=port,
|
|
443
|
+
open_browser=not no_browser,
|
|
444
|
+
config=config,
|
|
445
|
+
repo=repo,
|
|
446
|
+
pricing_engine=pricing_engine,
|
|
447
|
+
report_gen=report_gen,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
386
451
|
@app.command("watch")
|
|
387
452
|
def watch_cmd():
|
|
388
453
|
"""Monitor local session log files in real-time and auto-update metrics."""
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
def update_and_render():
|
|
392
|
-
_perform_scan(quiet=True)
|
|
393
|
-
records = repo.query_usage_events()
|
|
394
|
-
prompts = repo.query_prompts(limit=10)
|
|
395
|
-
code_changes = repo.query_code_changes(limit=10)
|
|
396
|
-
summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
|
|
397
|
-
console.clear()
|
|
398
|
-
render_dashboard(
|
|
399
|
-
summary=summary,
|
|
400
|
-
prompts=prompts,
|
|
401
|
-
code_changes=code_changes,
|
|
402
|
-
title_suffix="Watch Mode (Live)",
|
|
403
|
-
console=console,
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
target_dir = providers[0].claude_dir if providers else config.get_claude_dir()
|
|
407
|
-
run_watch_loop(
|
|
408
|
-
target_dir=target_dir,
|
|
409
|
-
on_change_callback=update_and_render,
|
|
410
|
-
refresh_seconds=config.get_refresh_seconds(),
|
|
411
|
-
console=console,
|
|
412
|
-
)
|
|
454
|
+
_launch_live_ui()
|
|
413
455
|
|
|
414
456
|
|
|
415
457
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Lightweight Web Dashboard HTTP Server running on http://localhost:5005 for code-meter."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import webbrowser
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from code_meter.analytics.reports import ReportGenerator
|
|
13
|
+
from code_meter.pricing.engine import PricingEngine
|
|
14
|
+
from code_meter.providers.antigravity import AntigravityProvider
|
|
15
|
+
from code_meter.providers.claude_code import ClaudeCodeProvider
|
|
16
|
+
from code_meter.providers.codex import CodexProvider
|
|
17
|
+
from code_meter.storage.repository import UsageRepository
|
|
18
|
+
from code_meter.ui.web.static import HTML_DASHBOARD
|
|
19
|
+
from code_meter.watcher import run_watch_loop
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def create_web_handler(repo: UsageRepository, pricing_engine: PricingEngine, report_gen: ReportGenerator):
|
|
25
|
+
"""Factory creating HTTPRequestHandler with injected service dependencies."""
|
|
26
|
+
|
|
27
|
+
class CodeMeterWebHandler(BaseHTTPRequestHandler):
|
|
28
|
+
def log_message(self, format: str, *args: Any) -> None:
|
|
29
|
+
# Suppress default noisy HTTP request logs
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def do_GET(self) -> None:
|
|
33
|
+
path = self.path.split("?")[0]
|
|
34
|
+
|
|
35
|
+
if path in ("/", "/index.html"):
|
|
36
|
+
self.send_response(200)
|
|
37
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
38
|
+
self.end_headers()
|
|
39
|
+
self.wfile.write(HTML_DASHBOARD.encode("utf-8"))
|
|
40
|
+
|
|
41
|
+
elif path == "/api/summary":
|
|
42
|
+
self.handle_api_summary()
|
|
43
|
+
|
|
44
|
+
elif path == "/api/prompts":
|
|
45
|
+
self.handle_api_prompts()
|
|
46
|
+
|
|
47
|
+
elif path == "/api/changes":
|
|
48
|
+
self.handle_api_changes()
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
self.send_error(404, "Not Found")
|
|
52
|
+
|
|
53
|
+
def handle_api_summary(self) -> None:
|
|
54
|
+
records = repo.query_usage_events()
|
|
55
|
+
prompts = repo.query_prompts(limit=15)
|
|
56
|
+
code_changes = repo.query_code_changes(limit=15)
|
|
57
|
+
|
|
58
|
+
costs = [
|
|
59
|
+
pricing_engine.calculate_cost(
|
|
60
|
+
r.provider, r.model, r.input_tokens, r.output_tokens, r.cache_read_tokens, r.cache_write_tokens, r.timestamp
|
|
61
|
+
)
|
|
62
|
+
for r in records
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
|
|
66
|
+
by_model = report_gen.aggregate_by_model(records, costs)
|
|
67
|
+
by_project = report_gen.aggregate_by_project(records, costs)
|
|
68
|
+
|
|
69
|
+
payload = {
|
|
70
|
+
"summary": {
|
|
71
|
+
"total_requests": summary.total_requests,
|
|
72
|
+
"total_input_tokens": summary.input_tokens,
|
|
73
|
+
"total_output_tokens": summary.output_tokens,
|
|
74
|
+
"total_cache_read_tokens": summary.cache_read_tokens,
|
|
75
|
+
"total_cache_write_tokens": summary.cache_write_tokens,
|
|
76
|
+
"total_tokens": summary.total_tokens,
|
|
77
|
+
"total_cost": summary.estimated_cost_usd or 0.0,
|
|
78
|
+
},
|
|
79
|
+
"by_model": [
|
|
80
|
+
{
|
|
81
|
+
"model": m.model,
|
|
82
|
+
"requests": m.request_count,
|
|
83
|
+
"total_tokens": m.total_tokens,
|
|
84
|
+
"total_cost": m.estimated_cost_usd or 0.0,
|
|
85
|
+
}
|
|
86
|
+
for m in by_model
|
|
87
|
+
],
|
|
88
|
+
"by_project": [
|
|
89
|
+
{
|
|
90
|
+
"project_name": p.project_name,
|
|
91
|
+
"requests": p.request_count,
|
|
92
|
+
"total_tokens": p.total_tokens,
|
|
93
|
+
"total_cost": p.estimated_cost_usd or 0.0,
|
|
94
|
+
}
|
|
95
|
+
for p in by_project
|
|
96
|
+
],
|
|
97
|
+
"prompts": [
|
|
98
|
+
{
|
|
99
|
+
"timestamp": p.timestamp,
|
|
100
|
+
"provider": p.provider,
|
|
101
|
+
"prompt_text": p.prompt_text,
|
|
102
|
+
"project_id": p.project_id or "",
|
|
103
|
+
}
|
|
104
|
+
for p in prompts
|
|
105
|
+
],
|
|
106
|
+
"code_changes": [
|
|
107
|
+
{
|
|
108
|
+
"timestamp": c.timestamp,
|
|
109
|
+
"provider": c.provider,
|
|
110
|
+
"file_path": c.file_path,
|
|
111
|
+
"change_type": c.change_type,
|
|
112
|
+
"diff_summary": c.diff_summary,
|
|
113
|
+
}
|
|
114
|
+
for c in code_changes
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
self.send_json_response(payload)
|
|
119
|
+
|
|
120
|
+
def handle_api_prompts(self) -> None:
|
|
121
|
+
prompts = repo.query_prompts(limit=50)
|
|
122
|
+
self.send_json_response({"prompts": [p.model_dump() for p in prompts]})
|
|
123
|
+
|
|
124
|
+
def handle_api_changes(self) -> None:
|
|
125
|
+
changes = repo.query_code_changes(limit=50)
|
|
126
|
+
self.send_json_response({"code_changes": [c.model_dump() for c in changes]})
|
|
127
|
+
|
|
128
|
+
def send_json_response(self, data: Dict[str, Any]) -> None:
|
|
129
|
+
body = json.dumps(data, indent=2, default=str).encode("utf-8")
|
|
130
|
+
self.send_response(200)
|
|
131
|
+
self.send_header("Content-Type", "application/json")
|
|
132
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
133
|
+
self.end_headers()
|
|
134
|
+
self.wfile.write(body)
|
|
135
|
+
|
|
136
|
+
return CodeMeterWebHandler
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def start_web_server(
|
|
140
|
+
host: str = "127.0.0.1",
|
|
141
|
+
port: int = 5005,
|
|
142
|
+
open_browser: bool = True,
|
|
143
|
+
config: Optional[Any] = None,
|
|
144
|
+
repo: Optional[UsageRepository] = None,
|
|
145
|
+
pricing_engine: Optional[PricingEngine] = None,
|
|
146
|
+
report_gen: Optional[ReportGenerator] = None,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Launch HTTP server on specified host/port serving localhost web dashboard."""
|
|
149
|
+
if not (repo and pricing_engine and report_gen and config):
|
|
150
|
+
from code_meter.cli import _get_services
|
|
151
|
+
config, repo, pricing_engine, providers, report_gen = _get_services()
|
|
152
|
+
else:
|
|
153
|
+
providers = [
|
|
154
|
+
ClaudeCodeProvider(config.get_claude_dir()),
|
|
155
|
+
CodexProvider(config.get_codex_dir()),
|
|
156
|
+
AntigravityProvider(config.get_antigravity_dir()),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
# Background scanner loop
|
|
160
|
+
def background_scan_and_watch():
|
|
161
|
+
def do_scan():
|
|
162
|
+
states = repo.get_scan_states()
|
|
163
|
+
for p in providers:
|
|
164
|
+
res = p.scan(states)
|
|
165
|
+
repo.save_records(res.records)
|
|
166
|
+
repo.save_prompts(res.prompts)
|
|
167
|
+
repo.save_code_changes(res.code_changes)
|
|
168
|
+
repo.update_scan_states(res.updated_states)
|
|
169
|
+
|
|
170
|
+
target_dirs = [config.get_claude_dir(), config.get_codex_dir(), config.get_antigravity_dir()]
|
|
171
|
+
run_watch_loop(target_dir=target_dirs, on_change_callback=do_scan, refresh_seconds=config.get_refresh_seconds())
|
|
172
|
+
|
|
173
|
+
watch_thread = threading.Thread(target=background_scan_and_watch, daemon=True)
|
|
174
|
+
watch_thread.start()
|
|
175
|
+
|
|
176
|
+
handler_cls = create_web_handler(repo, pricing_engine, report_gen)
|
|
177
|
+
server = ThreadingHTTPServer((host, port), handler_cls)
|
|
178
|
+
|
|
179
|
+
url = f"http://localhost:{port}"
|
|
180
|
+
print(f"\n[code-meter] Web Dashboard running at: {url}")
|
|
181
|
+
print("Press Ctrl+C to stop the web server.\n")
|
|
182
|
+
|
|
183
|
+
if open_browser:
|
|
184
|
+
def open_browser_later():
|
|
185
|
+
time.sleep(0.5)
|
|
186
|
+
webbrowser.open(url)
|
|
187
|
+
threading.Thread(target=open_browser_later, daemon=True).start()
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
server.serve_forever()
|
|
191
|
+
except KeyboardInterrupt:
|
|
192
|
+
print("\nShutting down web dashboard server...")
|
|
193
|
+
server.shutdown()
|
|
194
|
+
server.server_close()
|