code-meter 0.1.0__tar.gz → 0.2.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.2.0}/PKG-INFO +1 -3
  2. {code_meter-0.1.0 → code_meter-0.2.0}/pyproject.toml +1 -1
  3. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/cli.py +84 -2
  4. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/pricing/engine.py +35 -1
  5. code_meter-0.2.0/src/code_meter/pricing/live.py +72 -0
  6. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/storage/database.py +11 -0
  7. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/storage/repository.py +31 -0
  8. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/ui/dashboard.py +38 -4
  9. {code_meter-0.1.0 → code_meter-0.2.0/src/code_meter.egg-info}/PKG-INFO +1 -3
  10. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter.egg-info/SOURCES.txt +1 -1
  11. {code_meter-0.1.0 → code_meter-0.2.0}/tests/test_pricing.py +5 -0
  12. code_meter-0.1.0/LICENSE +0 -21
  13. {code_meter-0.1.0 → code_meter-0.2.0}/README.md +0 -0
  14. {code_meter-0.1.0 → code_meter-0.2.0}/setup.cfg +0 -0
  15. {code_meter-0.1.0 → code_meter-0.2.0}/setup.py +0 -0
  16. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/__init__.py +0 -0
  17. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/analytics/__init__.py +0 -0
  18. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/analytics/costs.py +0 -0
  19. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/analytics/reports.py +0 -0
  20. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/analytics/tokens.py +0 -0
  21. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/config.py +0 -0
  22. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/models/__init__.py +0 -0
  23. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/models/project.py +0 -0
  24. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/models/usage.py +0 -0
  25. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/pricing/__init__.py +0 -0
  26. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/pricing/anthropic.py +0 -0
  27. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/pricing/google.py +0 -0
  28. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/pricing/openai.py +0 -0
  29. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/providers/__init__.py +0 -0
  30. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/providers/antigravity.py +0 -0
  31. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/providers/base.py +0 -0
  32. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/providers/claude_code.py +0 -0
  33. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/providers/codex.py +0 -0
  34. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/storage/__init__.py +0 -0
  35. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/ui/__init__.py +0 -0
  36. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/ui/tables.py +0 -0
  37. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter/watcher.py +0 -0
  38. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter.egg-info/dependency_links.txt +0 -0
  39. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter.egg-info/entry_points.txt +0 -0
  40. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter.egg-info/requires.txt +0 -0
  41. {code_meter-0.1.0 → code_meter-0.2.0}/src/code_meter.egg-info/top_level.txt +0 -0
  42. {code_meter-0.1.0 → code_meter-0.2.0}/tests/test_analytics.py +0 -0
  43. {code_meter-0.1.0 → code_meter-0.2.0}/tests/test_database.py +0 -0
  44. {code_meter-0.1.0 → code_meter-0.2.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.2.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
 
@@ -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.2.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
@@ -100,9 +101,18 @@ def main_callback(ctx: typer.Context):
100
101
  console.print("[dim]Scanned providers: Claude Code, Codex, Antigravity[/dim]")
101
102
  return
102
103
 
104
+ prompts = repo.query_prompts(limit=10)
105
+ code_changes = repo.query_code_changes(limit=10)
106
+
103
107
  summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
104
108
  console.clear()
105
- render_dashboard(summary, title_suffix="Overview", console=console)
109
+ render_dashboard(
110
+ summary=summary,
111
+ prompts=prompts,
112
+ code_changes=code_changes,
113
+ title_suffix="Overview",
114
+ console=console,
115
+ )
106
116
 
107
117
 
108
118
  @app.command("scan")
@@ -295,6 +305,58 @@ def export_cmd(
295
305
  sys.stdout.write(content)
296
306
 
297
307
 
308
+ @app.command("prompts")
309
+ def prompts_cmd(
310
+ session: Optional[str] = typer.Option(None, "--session", help="Filter by session ID"),
311
+ project: Optional[str] = typer.Option(None, "--project", help="Filter by project name or path"),
312
+ limit: int = typer.Option(50, "--limit", "-n", help="Maximum number of prompts to display"),
313
+ ):
314
+ """Display log of user prompts and queries asked per session over time."""
315
+ _perform_scan(quiet=True)
316
+ _, repo, _, _, _ = _get_services()
317
+ prompts = repo.query_prompts(session_id=session, project_id=project, limit=limit)
318
+ if not prompts:
319
+ console.print("[yellow]No user prompts recorded yet.[/yellow]")
320
+ return
321
+ from code_meter.ui.tables import render_prompts_table
322
+ render_prompts_table(prompts, console=console)
323
+
324
+
325
+ @app.command("history")
326
+ def history_cmd(
327
+ session: Optional[str] = typer.Option(None, "--session", help="Filter by session ID"),
328
+ project: Optional[str] = typer.Option(None, "--project", help="Filter by project name"),
329
+ file: Optional[str] = typer.Option(None, "--file", help="Filter by modified file path"),
330
+ limit: int = typer.Option(50, "--limit", "-n", help="Maximum number of changes to display"),
331
+ ):
332
+ """Display history of code modifications, file writes, and assistant output actions."""
333
+ _perform_scan(quiet=True)
334
+ _, repo, _, _, _ = _get_services()
335
+ changes = repo.query_code_changes(session_id=session, project_id=project, file_path=file, limit=limit)
336
+ if not changes:
337
+ console.print("[yellow]No code modifications or output actions recorded yet.[/yellow]")
338
+ return
339
+ from code_meter.ui.tables import render_code_changes_table
340
+ render_code_changes_table(changes, console=console)
341
+
342
+
343
+ @app.command("diffs")
344
+ def diffs_cmd(
345
+ session: Optional[str] = typer.Option(None, "--session", help="Filter by session ID"),
346
+ file: Optional[str] = typer.Option(None, "--file", help="Filter by file path"),
347
+ limit: int = typer.Option(50, "--limit", "-n", help="Maximum number of diffs to display"),
348
+ ):
349
+ """Display formatted code diff summaries saved in database."""
350
+ _perform_scan(quiet=True)
351
+ _, repo, _, _, _ = _get_services()
352
+ changes = repo.query_code_changes(session_id=session, file_path=file, limit=limit)
353
+ if not changes:
354
+ console.print("[yellow]No diff summaries recorded yet.[/yellow]")
355
+ return
356
+ from code_meter.ui.tables import render_code_changes_table
357
+ render_code_changes_table(changes, console=console)
358
+
359
+
298
360
  @app.command("config")
299
361
  def config_cmd():
300
362
  """Display current configuration file path and contents."""
@@ -309,6 +371,18 @@ def config_cmd():
309
371
  console.print(f"[bold white]Currency:[/bold white] {config.pricing.currency}")
310
372
 
311
373
 
374
+ @app.command("update-pricing")
375
+ def update_pricing_cmd():
376
+ """Fetch latest real-time token pricing for AI models from live catalog."""
377
+ _, _, pricing_engine, _, _ = _get_services()
378
+ console.print("[cyan]Fetching real-time model token pricing...[/cyan]")
379
+ count = pricing_engine.update_live_pricing()
380
+ if count > 0:
381
+ console.print(f"[bold green]Successfully updated real-time pricing for {count:,} models![/bold green]")
382
+ else:
383
+ console.print("[yellow]Could not update pricing or offline using fallback pricing.[/yellow]")
384
+
385
+
312
386
  @app.command("watch")
313
387
  def watch_cmd():
314
388
  """Monitor local session log files in real-time and auto-update metrics."""
@@ -317,9 +391,17 @@ def watch_cmd():
317
391
  def update_and_render():
318
392
  _perform_scan(quiet=True)
319
393
  records = repo.query_usage_events()
394
+ prompts = repo.query_prompts(limit=10)
395
+ code_changes = repo.query_code_changes(limit=10)
320
396
  summary = report_gen.generate_summary(records, filter_date_str=datetime.now(timezone.utc).strftime("%Y-%m-%d"))
321
397
  console.clear()
322
- render_dashboard(summary, title_suffix="Watch Mode (Live)", console=console)
398
+ render_dashboard(
399
+ summary=summary,
400
+ prompts=prompts,
401
+ code_changes=code_changes,
402
+ title_suffix="Watch Mode (Live)",
403
+ console=console,
404
+ )
323
405
 
324
406
  target_dir = providers[0].claude_dir if providers else config.get_claude_dir()
325
407
  run_watch_loop(
@@ -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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-meter
3
- Version: 0.1.0
3
+ Version: 0.2.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
 
@@ -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
File without changes