codex-stats 1.4.2__tar.gz → 1.4.3__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 (26) hide show
  1. {codex_stats-1.4.2/src/codex_stats.egg-info → codex_stats-1.4.3}/PKG-INFO +2 -2
  2. {codex_stats-1.4.2 → codex_stats-1.4.3}/README.md +1 -1
  3. {codex_stats-1.4.2 → codex_stats-1.4.3}/pyproject.toml +1 -1
  4. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/__init__.py +1 -1
  5. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/display.py +113 -41
  6. {codex_stats-1.4.2 → codex_stats-1.4.3/src/codex_stats.egg-info}/PKG-INFO +2 -2
  7. {codex_stats-1.4.2 → codex_stats-1.4.3}/tests/test_cli.py +1 -1
  8. {codex_stats-1.4.2 → codex_stats-1.4.3}/tests/test_metrics.py +4 -2
  9. {codex_stats-1.4.2 → codex_stats-1.4.3}/LICENSE +0 -0
  10. {codex_stats-1.4.2 → codex_stats-1.4.3}/setup.cfg +0 -0
  11. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/__main__.py +0 -0
  12. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/cli.py +0 -0
  13. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/completions.py +0 -0
  14. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/config.py +0 -0
  15. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/ingest.py +0 -0
  16. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/metrics.py +0 -0
  17. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/models.py +0 -0
  18. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/otel.py +0 -0
  19. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/transfer.py +0 -0
  20. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats/watch_state.py +0 -0
  21. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats.egg-info/SOURCES.txt +0 -0
  22. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats.egg-info/dependency_links.txt +0 -0
  23. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats.egg-info/entry_points.txt +0 -0
  24. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats.egg-info/requires.txt +0 -0
  25. {codex_stats-1.4.2 → codex_stats-1.4.3}/src/codex_stats.egg-info/top_level.txt +0 -0
  26. {codex_stats-1.4.2 → codex_stats-1.4.3}/tests/test_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-stats
3
- Version: 1.4.2
3
+ Version: 1.4.3
4
4
  Summary: Local AI usage observability CLI for Codex sessions.
5
5
  Author: Vivek
6
6
  License: MIT
@@ -110,7 +110,7 @@ python3 -m pip install codex-stats
110
110
  - `codex-stats report weekly --format html`
111
111
  Generate a standalone HTML report for sharing, including inline charts.
112
112
  - `codex-stats report weekly --format svg`
113
- Generate a bundle of smaller standalone SVG assets in the current working directory, including a summary card plus graph-focused assets.
113
+ Generate a bundle of four smaller standalone SVG assets in the current working directory: summary, cost, focus, and project snapshots.
114
114
  - `codex-stats report weekly --project backend-api`
115
115
  Generate a weekly report for one project.
116
116
  - `codex-stats report weekly --format markdown --output weekly-report.md`
@@ -83,7 +83,7 @@ python3 -m pip install codex-stats
83
83
  - `codex-stats report weekly --format html`
84
84
  Generate a standalone HTML report for sharing, including inline charts.
85
85
  - `codex-stats report weekly --format svg`
86
- Generate a bundle of smaller standalone SVG assets in the current working directory, including a summary card plus graph-focused assets.
86
+ Generate a bundle of four smaller standalone SVG assets in the current working directory: summary, cost, focus, and project snapshots.
87
87
  - `codex-stats report weekly --project backend-api`
88
88
  Generate a weekly report for one project.
89
89
  - `codex-stats report weekly --format markdown --output weekly-report.md`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codex-stats"
7
- version = "1.4.2"
7
+ version = "1.4.3"
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.4.2"
3
+ __version__ = "1.4.3"
@@ -792,49 +792,11 @@ def format_report_svg_assets(report: ReportData, daily_points: list[DailyPoint]
792
792
  title = f"Codex Stats {report.period.title()} Report"
793
793
  if report.project_name:
794
794
  title = f"{title}: {report.project_name}"
795
- daily_points = daily_points or []
796
795
  assets = {
797
796
  "summary-card": _format_summary_card_svg(report, title),
798
- "daily-tokens": _wrap_chart_svg(
799
- title="Daily Token Trend",
800
- subtitle=title,
801
- chart_svg=_svg_line_chart(
802
- [(point.day[5:], float(point.total_tokens)) for point in daily_points],
803
- stroke="#0f766e",
804
- fill="rgba(15, 118, 110, 0.14)",
805
- value_formatter=lambda value: f"{int(value):,}",
806
- ),
807
- ),
808
- "daily-cost": _wrap_chart_svg(
809
- title="Daily Cost Trend",
810
- subtitle=title,
811
- chart_svg=_svg_line_chart(
812
- [(point.day[5:], float(point.estimated_cost_usd)) for point in daily_points],
813
- stroke="#b45309",
814
- fill="rgba(180, 83, 9, 0.14)",
815
- value_formatter=lambda value: f"${value:.2f}",
816
- ),
817
- ),
818
- "project-share": _wrap_chart_svg(
819
- title="Project Share",
820
- subtitle=title,
821
- chart_svg=_svg_bar_chart(
822
- [(entry.name, float(entry.total_tokens)) for entry in report.projects[:5]],
823
- bar_color="#0f766e",
824
- value_formatter=lambda value: f"{int(value):,}",
825
- empty_label="No project breakdown for this scoped report.",
826
- ),
827
- ),
828
- "top-sessions": _wrap_chart_svg(
829
- title="Top Sessions",
830
- subtitle=title,
831
- chart_svg=_svg_bar_chart(
832
- [(f"{entry.project_name} / {entry.model or 'unknown'}", float(entry.total_tokens)) for entry in report.top_sessions[:5]],
833
- bar_color="#b45309",
834
- value_formatter=lambda value: f"{int(value):,}",
835
- empty_label="No top session data available.",
836
- ),
837
- ),
797
+ "cost-card": _format_cost_card_svg(report, title),
798
+ "focus-card": _format_focus_card_svg(report, title),
799
+ "projects-card": _format_projects_card_svg(report, title),
838
800
  }
839
801
  return assets
840
802
 
@@ -1004,6 +966,116 @@ def _format_summary_card_svg(report: ReportData, title: str) -> str:
1004
966
  </svg>"""
1005
967
 
1006
968
 
969
+ def _format_cost_card_svg(report: ReportData, title: str) -> str:
970
+ return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-label="{escape(title)} cost card">
971
+ <rect width="1200" height="630" fill="#f7efe2"/>
972
+ <rect x="40" y="36" width="1120" height="558" rx="32" fill="#fffaf2" stroke="rgba(66,48,31,0.10)"/>
973
+ <text x="76" y="92" font-size="18" letter-spacing="3" fill="#b45309">CODEX STATS</text>
974
+ <text x="76" y="150" font-size="56" font-weight="700" fill="#1f1a17">Cost Snapshot</text>
975
+ <text x="76" y="194" font-size="24" fill="#6d645d">{escape(title)}</text>
976
+
977
+ <rect x="76" y="244" width="254" height="154" rx="28" fill="#b45309"/>
978
+ <text x="104" y="286" font-size="16" letter-spacing="2" fill="#ffedd5">TODAY</text>
979
+ <text x="104" y="348" font-size="48" font-weight="700" fill="#ffffff">${report.costs.today_cost_usd:.2f}</text>
980
+ <text x="104" y="378" font-size="20" fill="#ffedd5">Current day estimate</text>
981
+
982
+ <rect x="348" y="244" width="254" height="154" rx="28" fill="#fffaf2" stroke="rgba(66,48,31,0.10)"/>
983
+ <text x="376" y="286" font-size="16" letter-spacing="2" fill="#6d645d">WEEK</text>
984
+ <text x="376" y="348" font-size="48" font-weight="700" fill="#1f1a17">${report.costs.week_cost_usd:.2f}</text>
985
+ <text x="376" y="378" font-size="20" fill="#6d645d">Rolling weekly total</text>
986
+
987
+ <rect x="620" y="244" width="254" height="154" rx="28" fill="#fffaf2" stroke="rgba(66,48,31,0.10)"/>
988
+ <text x="648" y="286" font-size="16" letter-spacing="2" fill="#6d645d">MONTH</text>
989
+ <text x="648" y="348" font-size="48" font-weight="700" fill="#1f1a17">${report.costs.month_cost_usd:.2f}</text>
990
+ <text x="648" y="378" font-size="20" fill="#6d645d">Rolling monthly total</text>
991
+
992
+ <rect x="892" y="244" width="236" height="154" rx="28" fill="#115e59"/>
993
+ <text x="920" y="286" font-size="16" letter-spacing="2" fill="#d7faf5">PROJECTED</text>
994
+ <text x="920" y="348" font-size="44" font-weight="700" fill="#ffffff">${report.costs.projected_monthly_cost_usd:.2f}</text>
995
+ <text x="920" y="378" font-size="20" fill="#d7faf5">Monthly run rate</text>
996
+
997
+ <rect x="76" y="436" width="1052" height="112" rx="28" fill="#fffaf2" stroke="rgba(66,48,31,0.10)"/>
998
+ <text x="104" y="474" font-size="18" letter-spacing="2" fill="#6d645d">HIGHEST SESSION</text>
999
+ <text x="104" y="522" font-size="38" font-weight="700" fill="#1f1a17">${report.costs.highest_session_cost_usd:.2f}</text>
1000
+ <text x="420" y="522" font-size="24" fill="#4b4036">Possible savings ${report.insights.possible_savings_usd:.2f} • Suggestion: {escape(report.insights.suggestion[:44])}</text>
1001
+
1002
+ <text x="1128" y="580" text-anchor="end" font-size="18" fill="#6d645d">Generated by codex-stats</text>
1003
+ </svg>"""
1004
+
1005
+
1006
+ def _format_focus_card_svg(report: ReportData, title: str) -> str:
1007
+ anomalies = report.insights.anomalies[:3] or ["No active anomalies"]
1008
+ recommendations = report.insights.recommendations[:3] or ["Usage looks healthy."]
1009
+ anomaly_lines = "".join(
1010
+ f'<text x="90" y="{244 + index * 46}" font-size="24" fill="#8a4b0f">• {escape(item)}</text>'
1011
+ for index, item in enumerate(anomalies)
1012
+ )
1013
+ recommendation_lines = "".join(
1014
+ f'<text x="620" y="{244 + index * 46}" font-size="24" fill="#1f1a17">• {escape(item)}</text>'
1015
+ for index, item in enumerate(recommendations)
1016
+ )
1017
+ return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-label="{escape(title)} focus card">
1018
+ <rect width="1200" height="630" fill="#f2ece3"/>
1019
+ <rect x="40" y="36" width="1120" height="558" rx="32" fill="#fffaf2" stroke="rgba(66,48,31,0.10)"/>
1020
+ <text x="76" y="92" font-size="18" letter-spacing="3" fill="#0f766e">CODEX STATS</text>
1021
+ <text x="76" y="150" font-size="56" font-weight="700" fill="#1f1a17">Focus Card</text>
1022
+ <text x="76" y="194" font-size="24" fill="#6d645d">{escape(title)}</text>
1023
+
1024
+ <rect x="76" y="216" width="492" height="306" rx="28" fill="rgba(180,83,9,0.08)" stroke="rgba(180,83,9,0.18)"/>
1025
+ <text x="90" y="252" font-size="20" letter-spacing="2" fill="#8a4b0f">WHAT LOOKS OFF</text>
1026
+ {anomaly_lines}
1027
+
1028
+ <rect x="604" y="216" width="524" height="306" rx="28" fill="rgba(15,118,110,0.08)" stroke="rgba(15,118,110,0.18)"/>
1029
+ <text x="620" y="252" font-size="20" letter-spacing="2" fill="#115e59">WHAT TO DO NEXT</text>
1030
+ {recommendation_lines}
1031
+
1032
+ <rect x="76" y="540" width="1052" height="40" rx="20" fill="#fffaf2"/>
1033
+ <text x="602" y="566" text-anchor="middle" font-size="20" fill="#4b4036">Avg/request {report.summary.average_tokens_per_request:,.0f} • Cache ratio {escape(_fmt_percent(report.summary.cache_ratio))} • Large sessions {report.insights.large_session_count}</text>
1034
+ </svg>"""
1035
+
1036
+
1037
+ def _format_projects_card_svg(report: ReportData, title: str) -> str:
1038
+ project_rows = report.projects[:4]
1039
+ if not project_rows:
1040
+ return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-label="{escape(title)} projects card">
1041
+ <rect width="1200" height="630" fill="#f5eee4"/>
1042
+ <rect x="40" y="36" width="1120" height="558" rx="32" fill="#fffaf2" stroke="rgba(66,48,31,0.10)"/>
1043
+ <text x="76" y="92" font-size="18" letter-spacing="3" fill="#0f766e">CODEX STATS</text>
1044
+ <text x="76" y="150" font-size="56" font-weight="700" fill="#1f1a17">Project Snapshot</text>
1045
+ <text x="76" y="194" font-size="24" fill="#6d645d">{escape(title)}</text>
1046
+ <text x="76" y="320" font-size="30" fill="#4b4036">No project breakdown is available for this scoped report.</text>
1047
+ </svg>"""
1048
+ max_tokens = max(entry.total_tokens for entry in project_rows) or 1
1049
+ rows = []
1050
+ for index, entry in enumerate(project_rows):
1051
+ y = 236 + index * 86
1052
+ width = 520 * (entry.total_tokens / max_tokens)
1053
+ rows.append(
1054
+ f'<text x="90" y="{y}" font-size="26" fill="#1f1a17">{escape(entry.name)}</text>'
1055
+ f'<rect x="90" y="{y + 18}" width="520" height="18" rx="9" fill="rgba(72,53,36,0.08)" />'
1056
+ f'<rect x="90" y="{y + 18}" width="{width:.1f}" height="18" rx="9" fill="#0f766e" />'
1057
+ f'<text x="636" y="{y + 34}" font-size="22" fill="#4b4036">{entry.total_tokens:,} tokens</text>'
1058
+ f'<text x="868" y="{y + 34}" font-size="22" fill="#4b4036">{entry.requests} req</text>'
1059
+ f'<text x="1014" y="{y + 34}" font-size="22" fill="#4b4036">${entry.estimated_cost_usd:.2f}</text>'
1060
+ )
1061
+ top_sessions = report.top_sessions[:2]
1062
+ session_lines = "".join(
1063
+ f'<text x="700" y="{260 + index * 42}" font-size="24" fill="#4b4036">• {escape(entry.project_name)} / {escape(entry.model or "unknown")} {entry.total_tokens:,}</text>'
1064
+ for index, entry in enumerate(top_sessions)
1065
+ ) or '<text x="700" y="260" font-size="24" fill="#4b4036">No top sessions available.</text>'
1066
+ return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-label="{escape(title)} projects card">
1067
+ <rect width="1200" height="630" fill="#f5eee4"/>
1068
+ <rect x="40" y="36" width="1120" height="558" rx="32" fill="#fffaf2" stroke="rgba(66,48,31,0.10)"/>
1069
+ <text x="76" y="92" font-size="18" letter-spacing="3" fill="#0f766e">CODEX STATS</text>
1070
+ <text x="76" y="150" font-size="56" font-weight="700" fill="#1f1a17">Project Snapshot</text>
1071
+ <text x="76" y="194" font-size="24" fill="#6d645d">{escape(title)}</text>
1072
+ {''.join(rows)}
1073
+ <text x="700" y="224" font-size="20" letter-spacing="2" fill="#8a4b0f">TOP SESSIONS</text>
1074
+ {session_lines}
1075
+ <text x="1128" y="580" text-anchor="end" font-size="18" fill="#6d645d">Generated by codex-stats</text>
1076
+ </svg>"""
1077
+
1078
+
1007
1079
  def _wrap_chart_svg(title: str, subtitle: str, chart_svg: str) -> str:
1008
1080
  chart_body = chart_svg.strip()
1009
1081
  return f"""<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="720" viewBox="0 0 1200 720" role="img" aria-label="{escape(title)}">
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-stats
3
- Version: 1.4.2
3
+ Version: 1.4.3
4
4
  Summary: Local AI usage observability CLI for Codex sessions.
5
5
  Author: Vivek
6
6
  License: MIT
@@ -110,7 +110,7 @@ python3 -m pip install codex-stats
110
110
  - `codex-stats report weekly --format html`
111
111
  Generate a standalone HTML report for sharing, including inline charts.
112
112
  - `codex-stats report weekly --format svg`
113
- Generate a bundle of smaller standalone SVG assets in the current working directory, including a summary card plus graph-focused assets.
113
+ Generate a bundle of four smaller standalone SVG assets in the current working directory: summary, cost, focus, and project snapshots.
114
114
  - `codex-stats report weekly --project backend-api`
115
115
  Generate a weekly report for one project.
116
116
  - `codex-stats report weekly --format markdown --output weekly-report.md`
@@ -205,7 +205,7 @@ class CliTestCase(unittest.TestCase):
205
205
  self.assertIn("report.html", str(output_path))
206
206
  with tempfile.TemporaryDirectory() as tmpdir:
207
207
  svg_dir = _write_report_svg_assets(
208
- {"summary-card": "<svg></svg>", "daily-tokens": "<svg></svg>"},
208
+ {"summary-card": "<svg></svg>", "cost-card": "<svg></svg>"},
209
209
  tmpdir,
210
210
  base_name="codex-stats-weekly",
211
211
  )
@@ -465,8 +465,10 @@ class MetricsTestCase(unittest.TestCase):
465
465
  self.assertIn("Compact share card for release notes, README embeds, and social posts.", svg)
466
466
  self.assertIn("ANOMALIES", svg)
467
467
  self.assertIn("Generated by codex-stats", svg)
468
- self.assertEqual(set(assets), {"summary-card", "daily-tokens", "daily-cost", "project-share", "top-sessions"})
469
- self.assertIn("Daily Token Trend", assets["daily-tokens"])
468
+ self.assertEqual(set(assets), {"summary-card", "cost-card", "focus-card", "projects-card"})
469
+ self.assertIn("Cost Snapshot", assets["cost-card"])
470
+ self.assertIn("Focus Card", assets["focus-card"])
471
+ self.assertIn("Project Snapshot", assets["projects-card"])
470
472
 
471
473
 
472
474
  if __name__ == "__main__":
File without changes
File without changes