codex-stats 1.6.2__tar.gz → 1.7.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 (27) hide show
  1. {codex_stats-1.6.2/src/codex_stats.egg-info → codex_stats-1.7.0}/PKG-INFO +42 -14
  2. {codex_stats-1.6.2 → codex_stats-1.7.0}/README.md +41 -13
  3. {codex_stats-1.6.2 → codex_stats-1.7.0}/pyproject.toml +1 -1
  4. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/__init__.py +1 -1
  5. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/cli.py +122 -20
  6. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/config.py +20 -1
  7. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/display.py +335 -60
  8. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/ingest.py +1 -0
  9. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/metrics.py +63 -1
  10. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/models.py +72 -3
  11. codex_stats-1.7.0/src/codex_stats/sources.py +440 -0
  12. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/transfer.py +3 -0
  13. {codex_stats-1.6.2 → codex_stats-1.7.0/src/codex_stats.egg-info}/PKG-INFO +42 -14
  14. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats.egg-info/SOURCES.txt +1 -0
  15. {codex_stats-1.6.2 → codex_stats-1.7.0}/tests/test_cli.py +6 -0
  16. {codex_stats-1.6.2 → codex_stats-1.7.0}/tests/test_config.py +21 -0
  17. {codex_stats-1.6.2 → codex_stats-1.7.0}/tests/test_metrics.py +168 -6
  18. {codex_stats-1.6.2 → codex_stats-1.7.0}/LICENSE +0 -0
  19. {codex_stats-1.6.2 → codex_stats-1.7.0}/setup.cfg +0 -0
  20. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/__main__.py +0 -0
  21. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/completions.py +0 -0
  22. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/otel.py +0 -0
  23. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats/watch_state.py +0 -0
  24. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats.egg-info/dependency_links.txt +0 -0
  25. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats.egg-info/entry_points.txt +0 -0
  26. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats.egg-info/requires.txt +0 -0
  27. {codex_stats-1.6.2 → codex_stats-1.7.0}/src/codex_stats.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-stats
3
- Version: 1.6.2
3
+ Version: 1.7.0
4
4
  Summary: Local AI usage observability CLI for Codex sessions.
5
5
  Author: Vivek
6
6
  License: MIT
@@ -27,18 +27,36 @@ Dynamic: license-file
27
27
 
28
28
  # codex-stats
29
29
 
30
- `codex-stats` is a local analytics tool for Codex usage.
30
+ `codex-stats` is a local analytics tool for coding agents.
31
31
 
32
- It reads your local Codex state from `~/.codex` and surfaces:
32
+ It reads local session data from every coding assistant installed on the machine and surfaces:
33
33
 
34
- - a browser dashboard with day, week, month, and all-time tabs
34
+ - a browser dashboard with an **Overview** tab for every tool combined, plus one tab per tool (Codex, OpenCode, Claude Code, Hermes)
35
+ - day, week, month, and all-time windows inside every tab
35
36
  - model and project breakdowns
36
37
  - recent session history
37
- - estimated token-based cost
38
+ - estimated token-based cost (or each tool's own recorded cost when available)
39
+ - per-tool cost overrides and a stacked per-tool token trend on the Overview
38
40
  - anomaly-aware usage insights and recommendations
39
41
  - export for cross-device snapshots
40
42
  - shareable JPG cards and browser PDF export from the dashboard
41
43
 
44
+ ## Data Sources
45
+
46
+ Each source is detected automatically and skipped when no data is present:
47
+
48
+ | Tool | Location | Notes |
49
+ | --- | --- | --- |
50
+ | Codex | `~/.codex` | `state_5.sqlite` + rollout JSONL files |
51
+ | OpenCode | `~/.local/share/opencode/opencode.db` | recorded cost + tokens per session |
52
+ | Claude Code | `~/.claude/projects/**/*.jsonl` | per-project transcripts |
53
+ | Hermes | `~/.hermes/state.db` | recorded cost + tokens per session |
54
+
55
+ Use these environment variables to point at non-default locations (also used for test isolation):
56
+
57
+ - `CODEX_HOME` (Codex), `CODEX_STATS_OPENCODE_HOME` (OpenCode),
58
+ `CODEX_STATS_CLAUDE_PROJECTS_DIR` (Claude), `CODEX_STATS_HERMES_HOME` (Hermes)
59
+
42
60
  ## Install
43
61
 
44
62
  ```bash
@@ -59,6 +77,8 @@ python3 -m pip install codex-stats
59
77
  Write the dashboard HTML to a fixed path.
60
78
  - `codex-stats --output codex-stats-dashboard.html --no-open`
61
79
  Write the dashboard HTML without opening the browser.
80
+ - `codex-stats --source codex --source opencode`
81
+ Restrict the dashboard to the given tool scopes (repeat the flag to pick multiple).
62
82
  - `codex-stats export codex-stats-export.json`
63
83
  Export normalized local stats to JSON.
64
84
  - `codex-stats export codex-stats-export.json --since 30d`
@@ -66,23 +86,31 @@ python3 -m pip install codex-stats
66
86
 
67
87
  Inside the dashboard, use the action bar to:
68
88
 
69
- - switch between `Day`, `Week`, `Month`, and `All Time`
70
- - print the active tab to PDF
89
+ - switch tools with the **Overview / Codex / OpenCode / Claude Code / Hermes** tab row
90
+ - switch between `Day`, `Week`, `Month`, and `All Time` inside the active tool
91
+ - print the active tool and window to PDF
71
92
  - download shareable JPG cards for summary, cost, focus, and project share
72
93
 
73
94
  ## How It Works
74
95
 
75
- `codex-stats` does not proxy or intercept Codex API traffic.
96
+ `codex-stats` does not proxy or intercept API traffic. It reads local artifacts:
97
+ Codex `state_5.sqlite` and rollout files, the OpenCode database, Claude Code project
98
+ transcripts, and the Hermes database, then normalizes everything into one session model.
76
99
 
77
- It reads local Codex artifacts, including:
100
+ ## Notes
78
101
 
79
- - `state_5.sqlite` for session metadata
80
- - rollout JSONL files for request and token snapshots
102
+ - Costs are estimates by default. When a tool records its own cost (OpenCode, Hermes),
103
+ that value wins for the session. Per-tool rates can be set in `~/.config/codex-stats/config.toml`:
81
104
 
82
- ## Notes
105
+ ```toml
106
+ [pricing.source_usd_per_1k_tokens]
107
+ codex = 0.01
108
+ opencode = 0.01
109
+ claude = 0.015
110
+ hermes = 0.008
111
+ ```
83
112
 
84
- - Costs are estimates, not billing values.
85
- - Output depends on local Codex file formats remaining compatible.
113
+ - Output depends on local file formats remaining compatible.
86
114
  - `export --since Nd` limits snapshots to a rolling window before sharing.
87
115
 
88
116
  ## Roadmap
@@ -1,17 +1,35 @@
1
1
  # codex-stats
2
2
 
3
- `codex-stats` is a local analytics tool for Codex usage.
3
+ `codex-stats` is a local analytics tool for coding agents.
4
4
 
5
- It reads your local Codex state from `~/.codex` and surfaces:
5
+ It reads local session data from every coding assistant installed on the machine and surfaces:
6
6
 
7
- - a browser dashboard with day, week, month, and all-time tabs
7
+ - a browser dashboard with an **Overview** tab for every tool combined, plus one tab per tool (Codex, OpenCode, Claude Code, Hermes)
8
+ - day, week, month, and all-time windows inside every tab
8
9
  - model and project breakdowns
9
10
  - recent session history
10
- - estimated token-based cost
11
+ - estimated token-based cost (or each tool's own recorded cost when available)
12
+ - per-tool cost overrides and a stacked per-tool token trend on the Overview
11
13
  - anomaly-aware usage insights and recommendations
12
14
  - export for cross-device snapshots
13
15
  - shareable JPG cards and browser PDF export from the dashboard
14
16
 
17
+ ## Data Sources
18
+
19
+ Each source is detected automatically and skipped when no data is present:
20
+
21
+ | Tool | Location | Notes |
22
+ | --- | --- | --- |
23
+ | Codex | `~/.codex` | `state_5.sqlite` + rollout JSONL files |
24
+ | OpenCode | `~/.local/share/opencode/opencode.db` | recorded cost + tokens per session |
25
+ | Claude Code | `~/.claude/projects/**/*.jsonl` | per-project transcripts |
26
+ | Hermes | `~/.hermes/state.db` | recorded cost + tokens per session |
27
+
28
+ Use these environment variables to point at non-default locations (also used for test isolation):
29
+
30
+ - `CODEX_HOME` (Codex), `CODEX_STATS_OPENCODE_HOME` (OpenCode),
31
+ `CODEX_STATS_CLAUDE_PROJECTS_DIR` (Claude), `CODEX_STATS_HERMES_HOME` (Hermes)
32
+
15
33
  ## Install
16
34
 
17
35
  ```bash
@@ -32,6 +50,8 @@ python3 -m pip install codex-stats
32
50
  Write the dashboard HTML to a fixed path.
33
51
  - `codex-stats --output codex-stats-dashboard.html --no-open`
34
52
  Write the dashboard HTML without opening the browser.
53
+ - `codex-stats --source codex --source opencode`
54
+ Restrict the dashboard to the given tool scopes (repeat the flag to pick multiple).
35
55
  - `codex-stats export codex-stats-export.json`
36
56
  Export normalized local stats to JSON.
37
57
  - `codex-stats export codex-stats-export.json --since 30d`
@@ -39,23 +59,31 @@ python3 -m pip install codex-stats
39
59
 
40
60
  Inside the dashboard, use the action bar to:
41
61
 
42
- - switch between `Day`, `Week`, `Month`, and `All Time`
43
- - print the active tab to PDF
62
+ - switch tools with the **Overview / Codex / OpenCode / Claude Code / Hermes** tab row
63
+ - switch between `Day`, `Week`, `Month`, and `All Time` inside the active tool
64
+ - print the active tool and window to PDF
44
65
  - download shareable JPG cards for summary, cost, focus, and project share
45
66
 
46
67
  ## How It Works
47
68
 
48
- `codex-stats` does not proxy or intercept Codex API traffic.
69
+ `codex-stats` does not proxy or intercept API traffic. It reads local artifacts:
70
+ Codex `state_5.sqlite` and rollout files, the OpenCode database, Claude Code project
71
+ transcripts, and the Hermes database, then normalizes everything into one session model.
49
72
 
50
- It reads local Codex artifacts, including:
73
+ ## Notes
51
74
 
52
- - `state_5.sqlite` for session metadata
53
- - rollout JSONL files for request and token snapshots
75
+ - Costs are estimates by default. When a tool records its own cost (OpenCode, Hermes),
76
+ that value wins for the session. Per-tool rates can be set in `~/.config/codex-stats/config.toml`:
54
77
 
55
- ## Notes
78
+ ```toml
79
+ [pricing.source_usd_per_1k_tokens]
80
+ codex = 0.01
81
+ opencode = 0.01
82
+ claude = 0.015
83
+ hermes = 0.008
84
+ ```
56
85
 
57
- - Costs are estimates, not billing values.
58
- - Output depends on local Codex file formats remaining compatible.
86
+ - Output depends on local file formats remaining compatible.
59
87
  - `export --since Nd` limits snapshots to a rolling window before sharing.
60
88
 
61
89
  ## Roadmap
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codex-stats"
7
- version = "1.6.2"
7
+ version = "1.7.0"
8
8
  description = "Local AI usage observability CLI for Codex sessions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "1.6.2"
3
+ __version__ = "1.7.0"
@@ -21,12 +21,19 @@ from .metrics import (
21
21
  summarize_insights_from_details,
22
22
  summarize_project_drilldowns_from_details,
23
23
  summarize_projects_from_details,
24
+ summarize_source_breakdown_from_details,
25
+ summarize_source_daily_from_details,
24
26
  summarize_takeaways,
25
27
  summarize_top_sessions_from_details,
26
28
  summarize_work_rhythm,
27
29
  )
28
- from .ingest import iter_session_details
29
- from .models import DashboardData, DashboardWindow, SessionDetails
30
+ from .models import (
31
+ DashboardData,
32
+ DashboardScope,
33
+ DashboardWindow,
34
+ SessionDetails,
35
+ )
36
+ from .sources import Source, iter_sources
30
37
  from .transfer import write_export
31
38
 
32
39
 
@@ -41,6 +48,13 @@ def build_parser() -> argparse.ArgumentParser:
41
48
  help="Write dashboard HTML to a file instead of a temp file.",
42
49
  )
43
50
  parser.add_argument("--no-open", action="store_true", help="Write the dashboard without opening the browser.")
51
+ parser.add_argument(
52
+ "--source",
53
+ action="append",
54
+ dest="sources",
55
+ metavar="SOURCE",
56
+ help="Only include the given tool scope (codex, opencode, claude, hermes). Repeatable: --source codex --source opencode.",
57
+ )
44
58
  subparsers = parser.add_subparsers(dest="command")
45
59
 
46
60
  export_parser = subparsers.add_parser("export", help="Export normalized local stats to JSON.")
@@ -63,7 +77,7 @@ def main(argv: list[str] | None = None) -> int:
63
77
  parser.print_help()
64
78
  return 1
65
79
 
66
- dashboard = _build_dashboard(paths)
80
+ dashboard = _build_dashboard(paths, sources=args.sources)
67
81
  output_path = _write_dashboard_output(format_dashboard_html(dashboard), args.dashboard_output)
68
82
  print(f"Wrote dashboard to {output_path}")
69
83
  if not args.no_open:
@@ -72,62 +86,118 @@ def main(argv: list[str] | None = None) -> int:
72
86
  return 0
73
87
 
74
88
 
75
- def _build_dashboard(paths: Paths, now: datetime | None = None) -> DashboardData:
89
+ def _build_dashboard(paths: Paths, now: datetime | None = None, sources: list[str] | None = None) -> DashboardData:
76
90
  current_time = now or datetime.now().astimezone()
77
91
  pricing = load_pricing_config(paths)
78
- all_details = iter_session_details(paths)
92
+ all_sources = iter_sources(paths)
93
+ if sources:
94
+ requested = set(sources)
95
+ missing = sorted(requested - {source.key for source in all_sources})
96
+ if missing:
97
+ known = ", ".join(sorted(source.key for source in all_sources)) or "none"
98
+ raise ValueError(f"Unknown source(s): {', '.join(missing)}. Known sources: {known}")
99
+ selected_sources = [source for source in all_sources if source.key in requested]
100
+ else:
101
+ selected_sources = all_sources
102
+ source_details = {source.key: source.ingest() for source in selected_sources}
103
+ all_details = [detail for source in selected_sources for detail in source_details[source.key]]
104
+
105
+ overview = _build_scope(
106
+ key="overview",
107
+ label="Overview",
108
+ description="Every tool combined. Start here to see total activity, spend, and where the work happened.",
109
+ source="all",
110
+ details=all_details,
111
+ pricing=pricing,
112
+ now=current_time,
113
+ build_tool_breakdown=True,
114
+ )
115
+ tool_scopes = [
116
+ _build_scope(
117
+ key=source.key,
118
+ label=source.label,
119
+ description=source.description,
120
+ source=source.key,
121
+ details=source_details[source.key],
122
+ pricing=pricing,
123
+ now=current_time,
124
+ )
125
+ for source in selected_sources
126
+ ]
127
+ return DashboardData(generated_at=current_time, scopes=[overview, *tool_scopes])
79
128
 
80
- today_details = _details_for_last_days(all_details, 1, current_time)
81
- week_details = _details_for_last_days(all_details, 7, current_time)
82
- month_details = _details_for_last_days(all_details, 30, current_time)
83
129
 
130
+ def _build_scope(
131
+ *,
132
+ key: str,
133
+ label: str,
134
+ description: str,
135
+ source: str,
136
+ details: list[SessionDetails],
137
+ pricing: PricingConfig,
138
+ now: datetime,
139
+ build_tool_breakdown: bool = False,
140
+ ) -> DashboardScope:
141
+ today_details = _details_for_last_days(details, 1, now)
142
+ week_details = _details_for_last_days(details, 7, now)
143
+ month_details = _details_for_last_days(details, 30, now)
84
144
  windows = [
85
145
  _build_window(
86
146
  key="day",
87
147
  label="Day",
88
148
  description="Today’s usage with a direct comparison to yesterday.",
89
149
  current_details=today_details,
90
- previous_details=_details_for_previous_window(all_details, 1, current_time),
150
+ previous_details=_details_for_previous_window(details, 1, now),
91
151
  current_label="today",
92
152
  previous_label="yesterday",
93
153
  trend_days=1,
94
- all_details=all_details,
154
+ all_details=details,
95
155
  pricing=pricing,
96
- now=current_time,
156
+ now=now,
157
+ build_tool_breakdown=build_tool_breakdown,
97
158
  ),
98
159
  _build_window(
99
160
  key="week",
100
161
  label="Week",
101
162
  description="Rolling 7-day totals, recent trend, and the busiest sessions this week.",
102
163
  current_details=week_details,
103
- previous_details=_details_for_previous_window(all_details, 7, current_time),
164
+ previous_details=_details_for_previous_window(details, 7, now),
104
165
  current_label="last 7 days",
105
166
  previous_label="previous 7 days",
106
167
  trend_days=7,
107
- all_details=all_details,
168
+ all_details=details,
108
169
  pricing=pricing,
109
- now=current_time,
170
+ now=now,
171
+ build_tool_breakdown=build_tool_breakdown,
110
172
  ),
111
173
  _build_window(
112
174
  key="month",
113
175
  label="Month",
114
176
  description="Rolling 30-day totals with project concentration and cost pressure.",
115
177
  current_details=month_details,
116
- previous_details=_details_for_previous_window(all_details, 30, current_time),
178
+ previous_details=_details_for_previous_window(details, 30, now),
117
179
  current_label="last 30 days",
118
180
  previous_label="previous 30 days",
119
181
  trend_days=30,
120
- all_details=all_details,
182
+ all_details=details,
121
183
  pricing=pricing,
122
- now=current_time,
184
+ now=now,
185
+ build_tool_breakdown=build_tool_breakdown,
123
186
  ),
124
187
  _build_all_time_window(
125
- all_details=all_details,
188
+ all_details=details,
126
189
  pricing=pricing,
127
- now=current_time,
190
+ now=now,
191
+ build_tool_breakdown=build_tool_breakdown,
128
192
  ),
129
193
  ]
130
- return DashboardData(generated_at=current_time, windows=windows)
194
+ return DashboardScope(
195
+ key=key,
196
+ label=label,
197
+ description=description,
198
+ source=source,
199
+ windows=windows,
200
+ )
131
201
 
132
202
 
133
203
  def _build_window(
@@ -143,6 +213,7 @@ def _build_window(
143
213
  all_details: list[SessionDetails],
144
214
  pricing: PricingConfig,
145
215
  now: datetime,
216
+ build_tool_breakdown: bool = False,
146
217
  ) -> DashboardWindow:
147
218
  summary = summarize_details(current_label, current_details, pricing)
148
219
  comparison = summarize_compare_from_details(
@@ -189,6 +260,21 @@ def _build_window(
189
260
  pricing=pricing,
190
261
  limit=5,
191
262
  ),
263
+ tool_breakdown=(
264
+ summarize_source_breakdown_from_details(current_details, pricing)
265
+ if build_tool_breakdown
266
+ else None
267
+ ),
268
+ tool_daily_points=(
269
+ summarize_source_daily_from_details(
270
+ current_details,
271
+ days=max(trend_days, 1),
272
+ now=now,
273
+ pricing=pricing,
274
+ )
275
+ if build_tool_breakdown
276
+ else None
277
+ ),
192
278
  )
193
279
 
194
280
 
@@ -197,6 +283,7 @@ def _build_all_time_window(
197
283
  all_details: list[SessionDetails],
198
284
  pricing: PricingConfig,
199
285
  now: datetime,
286
+ build_tool_breakdown: bool = False,
200
287
  ) -> DashboardWindow:
201
288
  summary = summarize_details("all time", all_details, pricing)
202
289
  recent_details = _details_for_last_days(all_details, 30, now)
@@ -244,6 +331,21 @@ def _build_all_time_window(
244
331
  pricing=pricing,
245
332
  limit=5,
246
333
  ),
334
+ tool_breakdown=(
335
+ summarize_source_breakdown_from_details(all_details, pricing)
336
+ if build_tool_breakdown
337
+ else None
338
+ ),
339
+ tool_daily_points=(
340
+ summarize_source_daily_from_details(
341
+ all_details,
342
+ days=trend_days,
343
+ now=now,
344
+ pricing=pricing,
345
+ )
346
+ if build_tool_breakdown
347
+ else None
348
+ ),
247
349
  )
248
350
 
249
351
 
@@ -15,6 +15,13 @@ default_usd_per_1k_tokens = 0.01
15
15
  # gpt-5.4 = 0.02
16
16
  # gpt-5-mini = 0.005
17
17
 
18
+ [pricing.source_usd_per_1k_tokens]
19
+ # Per-tool overrides when a tool does not record its own cost.
20
+ # codex = 0.01
21
+ # opencode = 0.01
22
+ # claude = 0.015
23
+ # hermes = 0.008
24
+
18
25
  [display]
19
26
  color = "auto"
20
27
  history_limit = 10
@@ -51,12 +58,18 @@ class Paths:
51
58
  class PricingConfig:
52
59
  default_usd_per_1k_tokens: float = 0.01
53
60
  model_rates: dict[str, float] | None = None
61
+ source_rates: dict[str, float] | None = None
54
62
 
55
63
  def rate_for_model(self, model: str | None) -> float:
56
64
  if model and self.model_rates and model in self.model_rates:
57
65
  return self.model_rates[model]
58
66
  return self.default_usd_per_1k_tokens
59
67
 
68
+ def rate_for_source(self, source: str, model: str | None) -> float:
69
+ if source and self.source_rates and source in self.source_rates:
70
+ return self.source_rates[source]
71
+ return self.rate_for_model(model)
72
+
60
73
 
61
74
  @dataclass(frozen=True)
62
75
  class DisplayConfig:
@@ -79,6 +92,7 @@ def load_config(paths: Paths) -> AppConfig:
79
92
  pricing = payload.get("pricing", {})
80
93
  default_rate = float(pricing.get("default_usd_per_1k_tokens", 0.01))
81
94
  model_rates = _flatten_model_rates(pricing.get("model_usd_per_1k_tokens", {}))
95
+ source_rates = {str(k): float(v) for k, v in pricing.get("source_usd_per_1k_tokens", {}).items()}
82
96
  display = payload.get("display", {})
83
97
  color = str(display.get("color", "auto"))
84
98
  if color not in {"auto", "always", "never"}:
@@ -90,7 +104,11 @@ def load_config(paths: Paths) -> AppConfig:
90
104
  if compare_days <= 0:
91
105
  raise ValueError("display.compare_days must be greater than 0")
92
106
  return AppConfig(
93
- pricing=PricingConfig(default_usd_per_1k_tokens=default_rate, model_rates=model_rates),
107
+ pricing=PricingConfig(
108
+ default_usd_per_1k_tokens=default_rate,
109
+ model_rates=model_rates,
110
+ source_rates=source_rates,
111
+ ),
94
112
  display=DisplayConfig(color=color, history_limit=history_limit, compare_days=compare_days),
95
113
  )
96
114
 
@@ -110,6 +128,7 @@ def load_config_view(paths: Paths) -> ConfigView:
110
128
  exists=paths.config_file.exists(),
111
129
  pricing_default_usd_per_1k_tokens=app_config.pricing.default_usd_per_1k_tokens,
112
130
  pricing_model_overrides=app_config.pricing.model_rates or {},
131
+ pricing_source_overrides=app_config.pricing.source_rates or {},
113
132
  display=DisplayConfigView(
114
133
  color=app_config.display.color,
115
134
  history_limit=app_config.display.history_limit,