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.
Files changed (46) hide show
  1. {code_meter-0.2.0 → code_meter-0.4.0}/PKG-INFO +43 -23
  2. {code_meter-0.2.0 → code_meter-0.4.0}/README.md +42 -22
  3. {code_meter-0.2.0 → code_meter-0.4.0}/pyproject.toml +1 -1
  4. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/cli.py +67 -25
  5. code_meter-0.4.0/src/code_meter/ui/web/__init__.py +5 -0
  6. code_meter-0.4.0/src/code_meter/ui/web/server.py +194 -0
  7. code_meter-0.4.0/src/code_meter/ui/web/static.py +497 -0
  8. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/watcher.py +16 -8
  9. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/PKG-INFO +43 -23
  10. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/SOURCES.txt +3 -0
  11. {code_meter-0.2.0 → code_meter-0.4.0}/setup.cfg +0 -0
  12. {code_meter-0.2.0 → code_meter-0.4.0}/setup.py +0 -0
  13. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/__init__.py +0 -0
  14. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/__init__.py +0 -0
  15. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/costs.py +0 -0
  16. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/reports.py +0 -0
  17. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/analytics/tokens.py +0 -0
  18. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/config.py +0 -0
  19. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/models/__init__.py +0 -0
  20. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/models/project.py +0 -0
  21. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/models/usage.py +0 -0
  22. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/__init__.py +0 -0
  23. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/anthropic.py +0 -0
  24. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/engine.py +0 -0
  25. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/google.py +0 -0
  26. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/live.py +0 -0
  27. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/pricing/openai.py +0 -0
  28. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/__init__.py +0 -0
  29. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/antigravity.py +0 -0
  30. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/base.py +0 -0
  31. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/claude_code.py +0 -0
  32. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/providers/codex.py +0 -0
  33. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/storage/__init__.py +0 -0
  34. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/storage/database.py +0 -0
  35. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/storage/repository.py +0 -0
  36. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/ui/__init__.py +0 -0
  37. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/ui/dashboard.py +0 -0
  38. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter/ui/tables.py +0 -0
  39. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/dependency_links.txt +0 -0
  40. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/entry_points.txt +0 -0
  41. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/requires.txt +0 -0
  42. {code_meter-0.2.0 → code_meter-0.4.0}/src/code_meter.egg-info/top_level.txt +0 -0
  43. {code_meter-0.2.0 → code_meter-0.4.0}/tests/test_analytics.py +0 -0
  44. {code_meter-0.2.0 → code_meter-0.4.0}/tests/test_database.py +0 -0
  45. {code_meter-0.2.0 → code_meter-0.4.0}/tests/test_pricing.py +0 -0
  46. {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.2.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 asked per session over time |
85
- | `code-meter history` | View log of code edits, file writes, and snapshots recorded per session |
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
- Pricing Engine (Multi-Provider Model Pricing Tables)
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
- Terminal UI & Typer CLI Commands
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 asked per session over time |
63
- | `code-meter history` | View log of code edits, file writes, and snapshots recorded per session |
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
- Pricing Engine (Multi-Provider Model Pricing Tables)
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
- Terminal UI & Typer CLI Commands
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.2.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(ctx: typer.Context):
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
- config, repo, pricing_engine, providers, report_gen = _get_services()
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,5 @@
1
+ """Web UI package for code-meter dashboard."""
2
+
3
+ from code_meter.ui.web.server import start_web_server
4
+
5
+ __all__ = ["start_web_server"]
@@ -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()