codex-usage-tracking 0.3.0__py3-none-any.whl

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 (50) hide show
  1. codex_usage_tracker/__init__.py +7 -0
  2. codex_usage_tracker/__main__.py +6 -0
  3. codex_usage_tracker/allowance.py +759 -0
  4. codex_usage_tracker/api_payloads.py +90 -0
  5. codex_usage_tracker/cli.py +1326 -0
  6. codex_usage_tracker/context.py +410 -0
  7. codex_usage_tracker/costing.py +176 -0
  8. codex_usage_tracker/dashboard.py +389 -0
  9. codex_usage_tracker/diagnostics.py +624 -0
  10. codex_usage_tracker/formatting.py +225 -0
  11. codex_usage_tracker/json_contracts.py +350 -0
  12. codex_usage_tracker/mcp_server.py +371 -0
  13. codex_usage_tracker/models.py +92 -0
  14. codex_usage_tracker/parser.py +491 -0
  15. codex_usage_tracker/paths.py +18 -0
  16. codex_usage_tracker/plugin_data/__init__.py +1 -0
  17. codex_usage_tracker/plugin_data/assets/icon.svg +8 -0
  18. codex_usage_tracker/plugin_data/dashboard/dashboard.css +954 -0
  19. codex_usage_tracker/plugin_data/dashboard/dashboard.js +1833 -0
  20. codex_usage_tracker/plugin_data/dashboard/dashboard_data.js +155 -0
  21. codex_usage_tracker/plugin_data/dashboard/dashboard_format.js +132 -0
  22. codex_usage_tracker/plugin_data/dashboard/dashboard_state.js +157 -0
  23. codex_usage_tracker/plugin_data/dashboard/dashboard_template.html +141 -0
  24. codex_usage_tracker/plugin_data/docs/assets/dashboard-calls.png +0 -0
  25. codex_usage_tracker/plugin_data/docs/assets/dashboard-details.png +0 -0
  26. codex_usage_tracker/plugin_data/docs/assets/dashboard-insights.png +0 -0
  27. codex_usage_tracker/plugin_data/docs/assets/dashboard-threads.png +0 -0
  28. codex_usage_tracker/plugin_data/docs/dashboard-guide.html +136 -0
  29. codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json +69 -0
  30. codex_usage_tracker/plugin_data/skills/codex-usage-api/SKILL.md +62 -0
  31. codex_usage_tracker/plugin_data/skills/codex-usage-tracker/SKILL.md +47 -0
  32. codex_usage_tracker/plugin_installer.py +312 -0
  33. codex_usage_tracker/pricing.py +57 -0
  34. codex_usage_tracker/pricing_config.py +223 -0
  35. codex_usage_tracker/pricing_estimates.py +44 -0
  36. codex_usage_tracker/pricing_openai.py +253 -0
  37. codex_usage_tracker/projects.py +347 -0
  38. codex_usage_tracker/recommendations.py +270 -0
  39. codex_usage_tracker/reports.py +637 -0
  40. codex_usage_tracker/schema.py +71 -0
  41. codex_usage_tracker/server.py +400 -0
  42. codex_usage_tracker/store.py +666 -0
  43. codex_usage_tracker/support.py +147 -0
  44. codex_usage_tracker/threads.py +183 -0
  45. codex_usage_tracking-0.3.0.dist-info/METADATA +278 -0
  46. codex_usage_tracking-0.3.0.dist-info/RECORD +50 -0
  47. codex_usage_tracking-0.3.0.dist-info/WHEEL +5 -0
  48. codex_usage_tracking-0.3.0.dist-info/entry_points.txt +2 -0
  49. codex_usage_tracking-0.3.0.dist-info/licenses/LICENSE +21 -0
  50. codex_usage_tracking-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,147 @@
1
+ """Privacy-preserving support bundle generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import platform
7
+ import sys
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from codex_usage_tracker import __version__
13
+ from codex_usage_tracker.allowance import load_allowance_config
14
+ from codex_usage_tracker.diagnostics import run_doctor
15
+ from codex_usage_tracker.paths import (
16
+ DEFAULT_ALLOWANCE_PATH,
17
+ DEFAULT_CODEX_HOME,
18
+ DEFAULT_DB_PATH,
19
+ DEFAULT_PRICING_PATH,
20
+ DEFAULT_PROJECTS_PATH,
21
+ DEFAULT_RATE_CARD_PATH,
22
+ DEFAULT_THRESHOLDS_PATH,
23
+ )
24
+ from codex_usage_tracker.pricing import load_pricing_config
25
+ from codex_usage_tracker.projects import (
26
+ load_project_config,
27
+ project_privacy_metadata,
28
+ validate_privacy_mode,
29
+ )
30
+ from codex_usage_tracker.recommendations import load_threshold_config
31
+ from codex_usage_tracker.store import refresh_metadata, schema_state
32
+
33
+
34
+ def build_support_bundle(
35
+ *,
36
+ output_path: Path,
37
+ codex_home: Path = DEFAULT_CODEX_HOME,
38
+ db_path: Path = DEFAULT_DB_PATH,
39
+ pricing_path: Path = DEFAULT_PRICING_PATH,
40
+ allowance_path: Path = DEFAULT_ALLOWANCE_PATH,
41
+ rate_card_path: Path = DEFAULT_RATE_CARD_PATH,
42
+ thresholds_path: Path = DEFAULT_THRESHOLDS_PATH,
43
+ projects_path: Path = DEFAULT_PROJECTS_PATH,
44
+ privacy_mode: str = "normal",
45
+ ) -> Path:
46
+ """Write a local diagnostic bundle without raw logs or transcript content."""
47
+
48
+ output_path = output_path.expanduser()
49
+ output_path.parent.mkdir(parents=True, exist_ok=True)
50
+ payload = support_bundle_payload(
51
+ codex_home=codex_home,
52
+ db_path=db_path,
53
+ pricing_path=pricing_path,
54
+ allowance_path=allowance_path,
55
+ rate_card_path=rate_card_path,
56
+ thresholds_path=thresholds_path,
57
+ projects_path=projects_path,
58
+ privacy_mode=privacy_mode,
59
+ )
60
+ output_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
61
+ return output_path
62
+
63
+
64
+ def support_bundle_payload(
65
+ *,
66
+ codex_home: Path = DEFAULT_CODEX_HOME,
67
+ db_path: Path = DEFAULT_DB_PATH,
68
+ pricing_path: Path = DEFAULT_PRICING_PATH,
69
+ allowance_path: Path = DEFAULT_ALLOWANCE_PATH,
70
+ rate_card_path: Path = DEFAULT_RATE_CARD_PATH,
71
+ thresholds_path: Path = DEFAULT_THRESHOLDS_PATH,
72
+ projects_path: Path = DEFAULT_PROJECTS_PATH,
73
+ privacy_mode: str = "normal",
74
+ ) -> dict[str, Any]:
75
+ """Return support diagnostics safe to attach to a GitHub issue."""
76
+
77
+ privacy_mode = validate_privacy_mode(privacy_mode)
78
+ pricing = load_pricing_config(pricing_path)
79
+ allowance = load_allowance_config(allowance_path, rate_card_path=rate_card_path)
80
+ thresholds = load_threshold_config(thresholds_path)
81
+ projects = load_project_config(projects_path)
82
+ return {
83
+ "bundle_version": 1,
84
+ "generated_at": datetime.now(timezone.utc)
85
+ .replace(microsecond=0)
86
+ .isoformat()
87
+ .replace("+00:00", "Z"),
88
+ "privacy": {
89
+ "contains_raw_logs": False,
90
+ "contains_prompts": False,
91
+ "contains_assistant_messages": False,
92
+ "contains_tool_output": False,
93
+ "project_metadata": project_privacy_metadata(privacy_mode),
94
+ },
95
+ "package": {
96
+ "name": "codex-usage-tracker",
97
+ "version": __version__,
98
+ "python": sys.version.split()[0],
99
+ "platform": platform.platform(),
100
+ },
101
+ "paths": {
102
+ "codex_home_exists": codex_home.expanduser().exists(),
103
+ "sessions_dir_exists": (codex_home.expanduser() / "sessions").exists(),
104
+ "db_path": str(db_path.expanduser()),
105
+ "pricing_path": str(pricing_path.expanduser()),
106
+ "allowance_path": str(allowance_path.expanduser()),
107
+ "rate_card_path": str(rate_card_path.expanduser()),
108
+ "thresholds_path": str(thresholds_path.expanduser()),
109
+ "projects_path": str(projects_path.expanduser()),
110
+ },
111
+ "database": schema_state(db_path),
112
+ "refresh": refresh_metadata(db_path),
113
+ "pricing": {
114
+ "loaded": pricing.loaded,
115
+ "error": pricing.error,
116
+ "model_count": len(pricing.models),
117
+ "source": pricing.source,
118
+ },
119
+ "allowance": {
120
+ "loaded": allowance.loaded,
121
+ "error": allowance.error,
122
+ "window_count": len(allowance.windows),
123
+ "source": allowance.source,
124
+ "rate_card_loaded": allowance.rate_card_loaded,
125
+ "rate_card_error": allowance.rate_card_error,
126
+ "credit_rate_count": len(allowance.credit_rates),
127
+ "alias_count": len(allowance.aliases),
128
+ },
129
+ "thresholds": {
130
+ "loaded": thresholds.loaded,
131
+ "error": thresholds.error,
132
+ "keys": sorted(thresholds.thresholds),
133
+ },
134
+ "projects": {
135
+ "loaded": projects.loaded,
136
+ "error": projects.error,
137
+ "alias_count": len(projects.aliases),
138
+ "ignored_path_count": len(projects.ignored_paths),
139
+ "tag_group_count": len(projects.tags),
140
+ },
141
+ "doctor": run_doctor(
142
+ codex_home=codex_home,
143
+ db_path=db_path,
144
+ pricing_path=pricing_path,
145
+ suggest_repair=True,
146
+ ),
147
+ }
@@ -0,0 +1,183 @@
1
+ """Thread attachment inference for aggregate dashboard rows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+
11
+ @dataclass
12
+ class ParentCandidate:
13
+ key: str
14
+ label: str
15
+ session_id: str
16
+ cwd: str | None
17
+ first: str
18
+ latest: str
19
+
20
+
21
+ def annotate_thread_attachments(rows: list[dict[str, Any]]) -> list[dict[str, Any]]:
22
+ """Return copied rows with shared thread attachment metadata."""
23
+
24
+ candidates = _build_parent_candidates(rows)
25
+ return [_with_thread_attachment(row, candidates) for row in rows]
26
+
27
+
28
+ def _with_thread_attachment(
29
+ row: dict[str, Any],
30
+ candidates: dict[str, dict[str, Any]],
31
+ ) -> dict[str, Any]:
32
+ copy = dict(row)
33
+ attachment = _resolve_thread_attachment(row, candidates)
34
+ copy["thread_attachment_key"] = attachment["key"]
35
+ copy["thread_attachment_label"] = attachment["label"]
36
+ copy["thread_attachment_relation"] = attachment["relation"]
37
+ copy["thread_attachment_parent_session_id"] = attachment.get("parent_session_id")
38
+ return copy
39
+
40
+
41
+ def _build_parent_candidates(rows: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
42
+ by_session: dict[str, ParentCandidate] = {}
43
+ by_cwd: dict[str, list[ParentCandidate]] = {}
44
+ for row in rows:
45
+ thread_name = _optional_str(row.get("thread_name"))
46
+ session_id = _optional_str(row.get("session_id"))
47
+ if not thread_name or not session_id or row.get("thread_source") == "subagent":
48
+ continue
49
+ key = f"thread:{thread_name}"
50
+ event_timestamp = _optional_str(row.get("event_timestamp")) or ""
51
+ candidate = by_session.setdefault(
52
+ session_id,
53
+ ParentCandidate(
54
+ key=key,
55
+ label=thread_name,
56
+ session_id=session_id,
57
+ cwd=_optional_str(row.get("cwd")),
58
+ first=event_timestamp,
59
+ latest=event_timestamp,
60
+ ),
61
+ )
62
+ if event_timestamp < candidate.first:
63
+ candidate.first = event_timestamp
64
+ if event_timestamp > candidate.latest:
65
+ candidate.latest = event_timestamp
66
+
67
+ for candidate in by_session.values():
68
+ if not candidate.cwd:
69
+ continue
70
+ by_cwd.setdefault(candidate.cwd, []).append(candidate)
71
+ return {"by_session": by_session, "by_cwd": by_cwd}
72
+
73
+
74
+ def _resolve_thread_attachment(
75
+ row: dict[str, Any],
76
+ candidates: dict[str, dict[str, Any]],
77
+ ) -> dict[str, str | None]:
78
+ thread_name = _optional_str(row.get("thread_name"))
79
+ if thread_name:
80
+ return {"key": f"thread:{thread_name}", "label": thread_name, "relation": "direct"}
81
+
82
+ parent_session_id = _optional_str(row.get("parent_session_id"))
83
+ parent_thread_name = _resolved_parent_thread_name(row)
84
+ if parent_session_id and parent_thread_name:
85
+ return {
86
+ "key": f"thread:{parent_thread_name}",
87
+ "label": parent_thread_name,
88
+ "relation": "explicit parent thread",
89
+ "parent_session_id": parent_session_id,
90
+ }
91
+
92
+ by_session: dict[str, ParentCandidate] = candidates["by_session"]
93
+ if parent_session_id and parent_session_id in by_session:
94
+ parent = by_session[parent_session_id]
95
+ return {
96
+ "key": parent.key,
97
+ "label": parent.label,
98
+ "relation": "explicit parent",
99
+ "parent_session_id": parent_session_id,
100
+ }
101
+
102
+ if parent_session_id:
103
+ return {
104
+ "key": f"session:{parent_session_id}",
105
+ "label": f"Parent {parent_session_id}",
106
+ "relation": "explicit parent",
107
+ "parent_session_id": parent_session_id,
108
+ }
109
+
110
+ if _is_auto_review(row):
111
+ cwd = _optional_str(row.get("cwd"))
112
+ by_cwd: dict[str, list[ParentCandidate]] = candidates["by_cwd"]
113
+ cwd_candidates = by_cwd.get(cwd or "", [])
114
+ if cwd_candidates:
115
+ nearest = min(cwd_candidates, key=lambda candidate: _candidate_distance(row, candidate))
116
+ return {
117
+ "key": nearest.key,
118
+ "label": nearest.label,
119
+ "relation": "inferred by cwd/time",
120
+ }
121
+ return {
122
+ "key": f"auto:{cwd or _optional_str(row.get('session_id')) or 'unknown'}",
123
+ "label": f"Auto-review: {_basename_path(cwd)}",
124
+ "relation": "unmatched auto-review",
125
+ }
126
+
127
+ session_id = _optional_str(row.get("session_id")) or "unknown"
128
+ return {
129
+ "key": f"session:{session_id}",
130
+ "label": session_id if session_id != "unknown" else "Unknown thread",
131
+ "relation": "unmatched subagent" if _is_subagent(row) else "session",
132
+ }
133
+
134
+
135
+ def _resolved_parent_thread_name(row: dict[str, Any]) -> str:
136
+ return (
137
+ _optional_str(row.get("resolved_parent_thread_name"))
138
+ or _optional_str(row.get("parent_thread_name"))
139
+ or ""
140
+ )
141
+
142
+
143
+ def _is_auto_review(row: dict[str, Any]) -> bool:
144
+ return row.get("model") == "codex-auto-review" or row.get("subagent_type") == "guardian"
145
+
146
+
147
+ def _is_subagent(row: dict[str, Any]) -> bool:
148
+ return (
149
+ row.get("thread_source") == "subagent"
150
+ or bool(row.get("subagent_type"))
151
+ or bool(row.get("parent_session_id"))
152
+ )
153
+
154
+
155
+ def _candidate_distance(row: dict[str, Any], candidate: ParentCandidate) -> float:
156
+ event_time = _timestamp(row.get("event_timestamp"))
157
+ first = _timestamp(candidate.first)
158
+ latest = _timestamp(candidate.latest)
159
+ if event_time is None or first is None or latest is None:
160
+ return 0
161
+ if first <= event_time <= latest:
162
+ return 0
163
+ return min(abs(event_time - first), abs(event_time - latest))
164
+
165
+
166
+ def _timestamp(value: object) -> float | None:
167
+ if not isinstance(value, str) or not value:
168
+ return None
169
+ try:
170
+ normalized = value.replace("Z", "+00:00")
171
+ return datetime.fromisoformat(normalized).timestamp()
172
+ except ValueError:
173
+ return None
174
+
175
+
176
+ def _basename_path(path: str | None) -> str:
177
+ if not path:
178
+ return "Unknown project"
179
+ return Path(path).name or path
180
+
181
+
182
+ def _optional_str(value: object) -> str | None:
183
+ return value if isinstance(value, str) and value else None
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: codex-usage-tracking
3
+ Version: 0.3.0
4
+ Summary: Unofficial local Codex plugin and dashboard for investigating aggregate token usage, costs, caching, and thread patterns.
5
+ Author: Douglas Monsky
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/douglasmonsky/codex-usage-tracker
8
+ Project-URL: Repository, https://github.com/douglasmonsky/codex-usage-tracker
9
+ Project-URL: Issues, https://github.com/douglasmonsky/codex-usage-tracker/issues
10
+ Project-URL: Changelog, https://github.com/douglasmonsky/codex-usage-tracker/blob/main/CHANGELOG.md
11
+ Keywords: codex,mcp,tokens,usage,dashboard,openai,prompt-caching,cost-analysis
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Framework :: Pytest
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: System :: Monitoring
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: mcp>=1.2.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: build>=1.2; extra == "dev"
30
+ Requires-Dist: mypy>=1.10; extra == "dev"
31
+ Requires-Dist: pytest>=8.0; extra == "dev"
32
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.8; extra == "dev"
34
+ Requires-Dist: tomli>=2.0; python_version < "3.11" and extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # Codex Usage Tracker
38
+
39
+ <p align="center">
40
+ <a href="docs/assets/plugin-prompts.png"><img src="docs/assets/plugin-prompts.png?v=short-prompts" alt="Codex Usage Tracker companion prompts for opening the dashboard, finding the heaviest thread, and showing a thread leaderboard." width="49%"></a>
41
+ <a href="docs/assets/dashboard-calls.png"><img src="docs/assets/dashboard-calls-preview.png?v=usage-dashboard" alt="Codex Usage Tracker dashboard showing filters, usage totals, call rows, and call details." width="49%"></a>
42
+ </p>
43
+
44
+ Local-first dashboard, Codex plugin, and companion skill for understanding where your Codex tokens and usage credits are going.
45
+
46
+ [![CI](https://github.com/douglasmonsky/codex-usage-tracker/actions/workflows/ci.yml/badge.svg)](https://github.com/douglasmonsky/codex-usage-tracker/actions/workflows/ci.yml)
47
+ ![Python 3.10-3.13](https://img.shields.io/badge/python-3.10--3.13-blue)
48
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
49
+
50
+ > **Unofficial project:** Codex Usage Tracker is an independent open-source project. It is not made by, affiliated with, endorsed by, sponsored by, or supported by OpenAI. OpenAI and Codex are trademarks of OpenAI; this project only reads local log files from your machine.
51
+
52
+ Codex Usage Tracker reads the JSONL logs already written by Codex, indexes aggregate usage counters into SQLite, and gives you a dashboard, CLI, and MCP tools for investigating real usage patterns. It keeps prompts, assistant messages, tool output, pasted secrets, and raw transcript content out of SQLite, CSV exports, and generated dashboard HTML.
53
+
54
+ Built for developers using Codex locally who want to know which threads, models, subagents, and long chats are driving usage without uploading logs anywhere.
55
+
56
+ After install, you get a localhost dashboard, a local SQLite aggregate index, CLI reports, MCP tools, and a companion Codex skill for asking questions like "what drove my usage this week?"
57
+
58
+ ## Quick Install
59
+
60
+ ```bash
61
+ python -m pip install --user pipx
62
+ python -m pipx ensurepath
63
+ pipx install codex-usage-tracking
64
+ codex-usage-tracker setup
65
+ codex-usage-tracker serve-dashboard --open
66
+ ```
67
+
68
+ Use your normal Python launcher for your platform: `python3` is common on macOS/Linux, and `py` may be preferable on Windows. On macOS with Homebrew, `brew install pipx` is also fine.
69
+
70
+ Package naming: the PyPI distribution is `codex-usage-tracking`; the installed command is `codex-usage-tracker`; the GitHub repository remains `douglasmonsky/codex-usage-tracker`. The `codex-usage-tracker` PyPI name is not this project, so avoid similarly named packages when following these docs.
71
+
72
+ Development or pre-release source install:
73
+
74
+ ```bash
75
+ pipx install "git+https://github.com/douglasmonsky/codex-usage-tracker.git"
76
+ ```
77
+
78
+ `setup` installs or refreshes the local Codex plugin wrapper, initializes local config templates when needed, refreshes the aggregate index, runs `codex-usage-tracker doctor`, and tells you whether Codex needs a restart for plugin discovery.
79
+
80
+ Want Codex to do it for you? Paste: `Install codex-usage-tracking with pipx, run codex-usage-tracker setup, and open the Codex Usage Tracker dashboard. Use https://github.com/douglasmonsky/codex-usage-tracker only if the PyPI install is unavailable.`
81
+
82
+ After plugin discovery, Codex can use the companion usage skill to refresh local aggregates, call the MCP tools, and explain usage patterns conversationally. Examples: [MCP And Codex Skills](docs/mcp.md).
83
+
84
+ <p align="center">
85
+ <a href="docs/assets/plugin-thread-leaderboard.png"><img src="docs/assets/plugin-thread-leaderboard.png?v=thread-leaderboard" alt="Synthetic Codex chat preview showing the companion skill ranking threads by token usage after refreshing the local aggregate index." width="86%"></a>
86
+ </p>
87
+
88
+ If you only want plugin registration after installing the package:
89
+
90
+ ```bash
91
+ codex-usage-tracker install-plugin
92
+ ```
93
+
94
+ More install paths: [Install Guide](docs/install.md).
95
+
96
+ ## Platform Support
97
+
98
+ The core app is not macOS-only. The CLI, SQLite index, dashboard generator, and localhost server are Python-based and CI-tested on Ubuntu for Python 3.10-3.13. It defaults to `~/.codex` for local Codex logs and `~/.codex-usage-tracker` for tracker data; pass `--codex-home` or `--db` when your local layout differs. Codex plugin discovery depends on Codex's local plugin directories on your machine, so run `codex-usage-tracker doctor` after setup if plugin registration does not appear in Codex.
99
+
100
+ ## Dashboard Preview
101
+
102
+ The Calls table is the main investigation surface: filter, sort, inspect details, and export the exact aggregate rows you are looking at.
103
+
104
+ ![Calls view showing filters, totals, the model-call table, and the details panel.](docs/assets/dashboard-calls.png?v=aa16502)
105
+
106
+ Threads view groups related calls so long chats, subagents, and auto-review passes are easier to reason about as one work session.
107
+
108
+ ![Threads view with one expanded thread and its calls in chronological order.](docs/assets/dashboard-threads.png?v=3cd7338)
109
+
110
+ The details panel keeps the primary cost, cache, context, allowance, and pricing signals visible before raw identifiers.
111
+
112
+ ![Details panel showing aggregate fields for the selected usage row.](docs/assets/dashboard-details.png?v=84cf6dd)
113
+
114
+ Insights still gives a fast triage layer for costly threads, low cache reuse, context bloat, and pricing gaps.
115
+
116
+ ![Insights view with ranked Needs Attention cards, investigation presets, and top threads by attention score.](docs/assets/dashboard-insights.png?v=4a40e4f)
117
+
118
+ The dashboard screenshots use synthetic aggregate fixture data, and the companion prompt and chat previews are synthetic. They do not contain prompts from local logs, assistant responses, tool output, real thread names, real usage totals, or real Codex session content. See the [Dashboard Guide](docs/dashboard-guide.md) for the full walkthrough.
119
+
120
+ If this helped you track Codex usage, starring the repo helps others find it. Issues and feature requests are welcome.
121
+
122
+ ## Why This Exists
123
+
124
+ Codex can quietly burn usage through long-running chats, low cache reuse, reasoning spikes, spawned subagents, and auto-review passes. This tool turns the aggregate counters already on your machine into an insight-first dashboard and scriptable local APIs.
125
+
126
+ Use it to answer:
127
+
128
+ - Which threads used the most tokens, estimated cost, or Codex credits?
129
+ - Are long chats bloating because of accumulated context?
130
+ - Which model or reasoning effort is driving usage?
131
+ - Are subagents or auto-review passes adding unexpected cost?
132
+ - Which calls have low cache reuse, high context pressure, reasoning spikes, or pricing gaps?
133
+ - Which projects, project tags, or active directories are consuming the most usage?
134
+ - What should Codex inspect next using the companion usage skill?
135
+
136
+ ## Long Chats Can Bloat Fast
137
+
138
+ Prompt caching helps, but cached input is not the same as no input. Long threads can accumulate a large cached context, and each new turn may still include cached input plus fresh uncached input, output tokens, reasoning output, and tool-related context.
139
+
140
+ The dashboard makes that pattern visible with:
141
+
142
+ - `Cached input`
143
+ - `Uncached input`
144
+ - `Session cumulative`
145
+ - `Context use`
146
+ - `Cache ratio`
147
+
148
+ Practical takeaway: when old context is no longer useful, starting a fresh thread can be more efficient than dragging a large cached history forward. That is not a rule for every task, but it is one of the clearest usage patterns the tracker is designed to reveal.
149
+
150
+ ## First Useful Workflow
151
+
152
+ ```bash
153
+ codex-usage-tracker update-pricing
154
+ codex-usage-tracker update-rate-card
155
+ codex-usage-tracker setup
156
+ codex-usage-tracker serve-dashboard --open
157
+ ```
158
+
159
+ Then:
160
+
161
+ 1. Leave `Live` enabled while working, or click `Refresh` after a Codex run finishes.
162
+ 2. Start in `Insights` and scan the `Needs Attention` cards.
163
+ 3. Use `Time` presets or calendar fields to focus on today, this week, the last 7 days, this month, or a custom range.
164
+ 4. Use investigation presets for highest-cost threads, highest-credit calls, context bloat, cache misses, pricing gaps, or estimated-price review.
165
+ 5. Open `Threads` to see how a conversation grew and whether subagent or auto-review work attached to it.
166
+ 6. Hover or click rows to inspect aggregate fields in `Call Details`.
167
+ 7. Use `Load context` only when aggregate fields are not enough; context is fetched on demand from the local source JSONL and is not saved into SQLite or the dashboard.
168
+
169
+ Optional allowance context:
170
+
171
+ ```bash
172
+ codex-usage-tracker parse-allowance "5h 79% 6:50 PM Weekly 33% Jun 7"
173
+ ```
174
+
175
+ The tracker cannot read your logged-in ChatGPT plan or live remaining usage automatically. Allowance values are only as accurate as the values you manually copy from Codex Settings, `/status`, or another trusted usage display. Details: [Pricing, Credits, And Allowance](docs/pricing-and-credits.md).
176
+
177
+ ## What It Includes
178
+
179
+ - Local SQLite index at `~/.codex-usage-tracker/usage.sqlite3`.
180
+ - Static dashboard generation plus localhost live refresh.
181
+ - `Insights`, `Calls`, and `Threads` dashboard views.
182
+ - Active-only dashboards by default, with an explicit `All history` toggle for archived sessions.
183
+ - CLI summaries, queries, CSV export, dashboard generation, doctor checks, and support bundles.
184
+ - MCP tools for Codex sessions that want to query local usage data.
185
+ - Companion Codex skills for operational setup and conversational usage analysis.
186
+ - Optional local pricing, Codex credit, allowance, threshold, project alias, and privacy-mode configuration.
187
+
188
+ ## Common Commands
189
+
190
+ ```bash
191
+ codex-usage-tracker summary --preset last-7-days
192
+ codex-usage-tracker query --since 2026-06-01 --min-credits 1
193
+ codex-usage-tracker session <session-id>
194
+ codex-usage-tracker export --output usage.csv
195
+ codex-usage-tracker dashboard --open
196
+ codex-usage-tracker support-bundle --output ~/.codex-usage-tracker/support-bundle.json
197
+ ```
198
+
199
+ Full command reference: [CLI Reference](docs/cli-reference.md).
200
+
201
+ ## Data Privacy
202
+
203
+ The tracker stores aggregate metrics only: session ids, timestamps, local source paths, thread labels, cwd/project metadata, model labels, reasoning effort, token counters, pricing/credit annotations, and derived ratios.
204
+
205
+ It does **not** store prompts, assistant messages, tool output, pasted secrets, raw transcript snippets, or raw context in SQLite, CSV exports, generated dashboard HTML, or synthetic screenshots.
206
+
207
+ On-demand context loading reads a single original local JSONL file only after an explicit row action, redacts common secret patterns, caps returned text size, and can be disabled with:
208
+
209
+ ```bash
210
+ codex-usage-tracker serve-dashboard --no-context-api --open
211
+ ```
212
+
213
+ For shared artifacts, use:
214
+
215
+ ```bash
216
+ codex-usage-tracker --privacy-mode redacted dashboard --open
217
+ codex-usage-tracker --privacy-mode strict export --output usage-redacted.csv
218
+ ```
219
+
220
+ Full model: [Privacy Guide](docs/privacy.md).
221
+
222
+ ## Documentation
223
+
224
+ - [Install Guide](docs/install.md)
225
+ - [Dashboard Guide](docs/dashboard-guide.md)
226
+ - [CLI Reference](docs/cli-reference.md)
227
+ - [Pricing, Credits, And Allowance](docs/pricing-and-credits.md)
228
+ - [MCP And Codex Skills](docs/mcp.md)
229
+ - [Privacy Guide](docs/privacy.md)
230
+ - [Architecture](docs/architecture.md)
231
+ - [CLI And MCP JSON Schemas](docs/cli-json-schemas.md)
232
+ - [Development And Release](docs/development.md)
233
+
234
+ ## Codex-Assisted Install
235
+
236
+ Open a Codex session on your machine and paste this:
237
+
238
+ ```text
239
+ Install and configure Codex Usage Tracker.
240
+ Install the PyPI distribution codex-usage-tracking with pipx. The installed command should be codex-usage-tracker. If PyPI is unavailable, fall back to pipx install "git+https://github.com/douglasmonsky/codex-usage-tracker.git".
241
+ If pipx is missing, install it with the platform's Python launcher or use a local virtual environment.
242
+ After installation, run codex-usage-tracker setup and serve-dashboard --open.
243
+ Verify the dashboard opens locally and tell me the dashboard URL plus whether I need to restart Codex for plugin discovery.
244
+ ```
245
+
246
+ This is optional. The normal shell install above is the fastest trusted path for most users.
247
+
248
+ ## Current Limitations
249
+
250
+ - This is a sidecar dashboard and plugin, not a native Codex chat overlay.
251
+ - Token counts come from Codex's logged counters; the tracker does not re-tokenize prompts.
252
+ - Pricing and Codex credit estimates depend on local rate data and confidence labels.
253
+ - Remaining 5-hour and weekly allowance is not read automatically from the logged-in account.
254
+ - Local Codex logs may not include usage from other ChatGPT agentic surfaces that share the same allowance.
255
+ - Parent-child thread relationships are only as good as the metadata Codex logs; inferred auto-review attachments are labeled as inferred.
256
+
257
+ ## Roadmap
258
+
259
+ - Improve the `Set limits` flow with a paste/import experience for 5-hour and weekly allowance snapshots.
260
+ - Track allowance snapshot history so local Codex credits can be compared against visible remaining-usage changes over time.
261
+ - Clarify top-card token accounting by showing output tokens and reasoning output as a subset instead of implying all token cards add together.
262
+ - Add more insight presets for cache drift, context growth, subagent-heavy workflows, and pricing/credit confidence gaps.
263
+ - Keep the allowance provider boundary ready for an official usage or allowance API if one becomes available.
264
+ - Continue reducing setup friction for pipx installs, local plugin discovery, and Codex companion skill usage.
265
+
266
+ ## Development
267
+
268
+ ```bash
269
+ git clone https://github.com/douglasmonsky/codex-usage-tracker.git
270
+ cd codex-usage-tracker
271
+ python3 -m venv .venv
272
+ . .venv/bin/activate
273
+ python -m pip install --upgrade pip
274
+ python -m pip install ".[dev]"
275
+ python -m pytest
276
+ ```
277
+
278
+ Run the full local CI gate before pushing to `main`. See [Development And Release](docs/development.md).
@@ -0,0 +1,50 @@
1
+ codex_usage_tracker/__init__.py,sha256=driPgid_I0KYzwnxFFgmjyNGHHEQjDtsOPtQ5JA_CrA,164
2
+ codex_usage_tracker/__main__.py,sha256=SlF-z8X7I77rSoH5VMuUIJ4XrJyw1syPLFmn5EJd2XU,175
3
+ codex_usage_tracker/allowance.py,sha256=fHSGgUAv4FL3_mb6VL5bWGvxbGLI3MJooB_m_hNK9V8,27320
4
+ codex_usage_tracker/api_payloads.py,sha256=WIImXpx-UiJ-b_LBovskFJO3PohVwqYA6NRyaNQqcpA,2902
5
+ codex_usage_tracker/cli.py,sha256=46_PfH5qq3GoSGMqtp8JrOGfgpqLLC8PhfrG_71jHmA,49082
6
+ codex_usage_tracker/context.py,sha256=MwmVtKEsJrnQdsKDbN6kVPEFPaLfKWGeE4TH1D0EpT8,13758
7
+ codex_usage_tracker/costing.py,sha256=QCh2Snkh6hq4yEPiCFLVEeKoEhmYZU2mMYPMph9aL34,6331
8
+ codex_usage_tracker/dashboard.py,sha256=HTdU8ezh_UQKXcwGF_E2xrBFzpMPbUzRCq94pJGBdWo,14286
9
+ codex_usage_tracker/diagnostics.py,sha256=9ifW12VmgAPmi3uZQCTSlX42qppwYPJwL4EOZ67P8hY,22403
10
+ codex_usage_tracker/formatting.py,sha256=VCY51TiPWwZQhG-ujZhL8hww5gfMifNFWFOofuWmx1Q,8414
11
+ codex_usage_tracker/json_contracts.py,sha256=3LjDiFN3NdiHcgcdoTJjwcaXHkP9dicBIQ2t04TZauU,11010
12
+ codex_usage_tracker/mcp_server.py,sha256=oTn9goNyadj6EnWpYfpAsWcOQ732cUR6a82tmYXs_wc,10989
13
+ codex_usage_tracker/models.py,sha256=zkAvI9NRk1zGbnKHHSsRIi3LdvG3kD4XaIyS2Fs9crA,2592
14
+ codex_usage_tracker/parser.py,sha256=uqmmiMScN2Foisiz4pF5TBZKciiMddn7luKcmigzeuU,17838
15
+ codex_usage_tracker/paths.py,sha256=rQkhEz7xPA0RQ8-PB08PELA0RGG9dsXCddBtLi4804w,787
16
+ codex_usage_tracker/plugin_installer.py,sha256=FIcPp3J7kqqih4Tcr7R0ibYOXTi0zsP3aKSCFg0W2sE,11175
17
+ codex_usage_tracker/pricing.py,sha256=ACwVwLlnoMwAC9NGmFB2Eins1NqNCE47tAGnh2ONpDY,1534
18
+ codex_usage_tracker/pricing_config.py,sha256=uXEzr9TZm7Z6OAxT23GyTTv4yul7eDOxSv21RGEn12Y,7694
19
+ codex_usage_tracker/pricing_estimates.py,sha256=mPr9NPn-vYvdL8004sP8XWPWbDKsOereNqyQvarfMUM,1812
20
+ codex_usage_tracker/pricing_openai.py,sha256=62-yaKwLAbPgmGhAFaOYCTJT9BeEBWT7_58tyQr1cxg,8704
21
+ codex_usage_tracker/projects.py,sha256=dcZP_PAjZQL-7cfCXsBMVc5gU8G6WrrIR-gjmZ_1RU4,11379
22
+ codex_usage_tracker/recommendations.py,sha256=I-RP-fWeDM_9jXb0IfPONA72TeOFshZJ5_Ui9rMSCFY,10322
23
+ codex_usage_tracker/reports.py,sha256=srnDGet1uBHD3USmPpGwJb0lO1kArUiGvDFOfE6Sfz8,22113
24
+ codex_usage_tracker/schema.py,sha256=JbPa9yWmU00zEJLlPoA4_9VhUZTbTi99cu15_w6RGiw,3301
25
+ codex_usage_tracker/server.py,sha256=AaUZuxtE5Gyb1HGBSlibWiqWqwsJTOy2VANm8VMIwCk,14184
26
+ codex_usage_tracker/store.py,sha256=AgPnaZzX92nXBn098WIL73CIYAtjBsa9m7IYKbwf020,21991
27
+ codex_usage_tracker/support.py,sha256=kXJqQcag9B78dU11xi4noUtcAyIYDUCtLt8XJZH4lBA,5328
28
+ codex_usage_tracker/threads.py,sha256=foUrk2yjKPDf34F4Yi1z2Z-QAYchv5FbmaJe1OIRPdA,6221
29
+ codex_usage_tracker/plugin_data/__init__.py,sha256=4rhgPrlPIwA-kq8KMWtCK1HuXLbX4fPVxnQVSxy4uPM,68
30
+ codex_usage_tracker/plugin_data/assets/icon.svg,sha256=3IvSTf-YjMiIkIACwFf4L0C2o7u7_-_P7TUUWDcaH_w,495
31
+ codex_usage_tracker/plugin_data/dashboard/dashboard.css,sha256=gJgUg8V3T4Y_jXKR4EE_sEl8wMJSdPoFE-cF0JFhwN0,23315
32
+ codex_usage_tracker/plugin_data/dashboard/dashboard.js,sha256=WlDNt3xsKYAxpUd-KM0snFJ2f8CpwTvizMIeJGaMb8c,92015
33
+ codex_usage_tracker/plugin_data/dashboard/dashboard_data.js,sha256=iEbc_PRF833nJJWIL_PxLhFS67vg819pIqjxbIoXeRc,5485
34
+ codex_usage_tracker/plugin_data/dashboard/dashboard_format.js,sha256=MedNScGC40pMmj66zpj1xjOhhswnjQW-Dj_mu7lLsxk,3647
35
+ codex_usage_tracker/plugin_data/dashboard/dashboard_state.js,sha256=nYhOOzgNH8jb_c1WGx0KiR6M3RjcItZcC9dn37wBHRk,5361
36
+ codex_usage_tracker/plugin_data/dashboard/dashboard_template.html,sha256=nxZ3zqhaQZW7RtWAkUawXa_mEeV-fbwQUqadC5jGhw8,9411
37
+ codex_usage_tracker/plugin_data/docs/dashboard-guide.html,sha256=sEt989fLQbiZkV-eftL42XnJI2OJukv3uWJgvmYAgYM,12036
38
+ codex_usage_tracker/plugin_data/docs/assets/dashboard-calls.png,sha256=Ugv8SRXe8k2BMIqAJpcr78pd_vbtpwypTiOzyB1CLmQ,464477
39
+ codex_usage_tracker/plugin_data/docs/assets/dashboard-details.png,sha256=WwvSJe_BMQrtKviYkWMfetLSaL3-U4na-mh8mtmiVPs,222365
40
+ codex_usage_tracker/plugin_data/docs/assets/dashboard-insights.png,sha256=WK-uoN7KBFyxJAogd7QlY7ujRTIZ-PuLMRAqWvbc4F8,503417
41
+ codex_usage_tracker/plugin_data/docs/assets/dashboard-threads.png,sha256=U6w0Wck7HF1P3FF0j1jvtr8jDOknxGMIlACluxIK32A,516351
42
+ codex_usage_tracker/plugin_data/rate_cards/codex-credit-rates.json,sha256=WXdQnRYxF_LTEU_Q_VTyhoCBVtl_tzzIL7cOtOUZHTE,2430
43
+ codex_usage_tracker/plugin_data/skills/codex-usage-api/SKILL.md,sha256=5B68AWUwdkiwq2xE80NcfeMu4tFaW0YHilPGdX2Co70,6042
44
+ codex_usage_tracker/plugin_data/skills/codex-usage-tracker/SKILL.md,sha256=z0EnS89rCvon3UPQvQoeGDDgUkKU0tzvBxk5cb0D0Rk,5391
45
+ codex_usage_tracking-0.3.0.dist-info/licenses/LICENSE,sha256=h3B6Fkc8eTKue65OV5sIaDCE5bHPzdNXxS_jj6pEPJA,1071
46
+ codex_usage_tracking-0.3.0.dist-info/METADATA,sha256=gQmhMLIe-lgSM7oX8A-oP_LCirEL8PgKfT4DN9qV__g,15371
47
+ codex_usage_tracking-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
48
+ codex_usage_tracking-0.3.0.dist-info/entry_points.txt,sha256=Oa_PY_cOQ3T6X-QWv-CohVrrKt0J-yaiqz8pJAU8Nt8,69
49
+ codex_usage_tracking-0.3.0.dist-info/top_level.txt,sha256=zzAyBF23-O3U5CX-Hsu9KqzmRZpevTlpt4FPKb34mjU,20
50
+ codex_usage_tracking-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codex-usage-tracker = codex_usage_tracker.cli:main