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.
Files changed (44) hide show
  1. {code_meter-0.1.0/src/code_meter.egg-info → code_meter-0.3.0}/PKG-INFO +42 -25
  2. {code_meter-0.1.0 → code_meter-0.3.0}/README.md +41 -22
  3. {code_meter-0.1.0 → code_meter-0.3.0}/pyproject.toml +1 -1
  4. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/cli.py +122 -18
  5. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/engine.py +35 -1
  6. code_meter-0.3.0/src/code_meter/pricing/live.py +72 -0
  7. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/storage/database.py +11 -0
  8. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/storage/repository.py +31 -0
  9. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/ui/dashboard.py +38 -4
  10. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/watcher.py +16 -8
  11. {code_meter-0.1.0 → code_meter-0.3.0/src/code_meter.egg-info}/PKG-INFO +42 -25
  12. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/SOURCES.txt +1 -1
  13. {code_meter-0.1.0 → code_meter-0.3.0}/tests/test_pricing.py +5 -0
  14. code_meter-0.1.0/LICENSE +0 -21
  15. {code_meter-0.1.0 → code_meter-0.3.0}/setup.cfg +0 -0
  16. {code_meter-0.1.0 → code_meter-0.3.0}/setup.py +0 -0
  17. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/__init__.py +0 -0
  18. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/__init__.py +0 -0
  19. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/costs.py +0 -0
  20. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/reports.py +0 -0
  21. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/analytics/tokens.py +0 -0
  22. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/config.py +0 -0
  23. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/models/__init__.py +0 -0
  24. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/models/project.py +0 -0
  25. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/models/usage.py +0 -0
  26. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/__init__.py +0 -0
  27. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/anthropic.py +0 -0
  28. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/google.py +0 -0
  29. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/pricing/openai.py +0 -0
  30. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/__init__.py +0 -0
  31. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/antigravity.py +0 -0
  32. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/base.py +0 -0
  33. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/claude_code.py +0 -0
  34. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/providers/codex.py +0 -0
  35. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/storage/__init__.py +0 -0
  36. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/ui/__init__.py +0 -0
  37. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter/ui/tables.py +0 -0
  38. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/dependency_links.txt +0 -0
  39. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/entry_points.txt +0 -0
  40. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/requires.txt +0 -0
  41. {code_meter-0.1.0 → code_meter-0.3.0}/src/code_meter.egg-info/top_level.txt +0 -0
  42. {code_meter-0.1.0 → code_meter-0.3.0}/tests/test_analytics.py +0 -0
  43. {code_meter-0.1.0 → code_meter-0.3.0}/tests/test_database.py +0 -0
  44. {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.1.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 asked per session over time |
87
- | `code-meter history` | View log of code edits, file writes, and snapshots recorded per session |
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
- Pricing Engine (Multi-Provider Model Pricing Tables)
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
- Terminal UI & Typer CLI Commands
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 asked per session over time |
63
- | `code-meter history` | View log of code edits, file writes, and snapshots recorded per session |
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
- Pricing Engine (Multi-Provider Model Pricing Tables)
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
- 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
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.1.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(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
+ ):
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(summary, title_suffix="Overview", console=console)
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
- config, repo, pricing_engine, providers, report_gen = _get_services()
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
- """Interactive Rich terminal dashboard layout."""
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 splash")
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 target_dir and trigger on_change_callback whenever session jsonl files update."""
26
+ """Monitor target directories and trigger on_change_callback whenever session log files update."""
27
27
  c = console or Console()
28
- c.print(f"[bold green]Watching directory:[/bold green] {target_dir}")
29
- c.print("[dim]Press Ctrl+C to stop watch mode.[/dim]\n")
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 target_dir.exists():
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
- observer.schedule(JSONLHandler(), str(target_dir), recursive=True)
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.1.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 asked per session over time |
87
- | `code-meter history` | View log of code edits, file writes, and snapshots recorded per session |
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
- Pricing Engine (Multi-Provider Model Pricing Tables)
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
- Terminal UI & Typer CLI Commands
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
@@ -56,3 +56,8 @@ def test_calculate_antigravity_cost(pricing_engine):
56
56
  )
57
57
  # input 0.15 + output 0.60 = 0.75
58
58
  assert cost == 0.75
59
+
60
+
61
+ def test_update_live_pricing(pricing_engine):
62
+ count = pricing_engine.update_live_pricing()
63
+ assert count > 100
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