cctally 1.82.0 → 1.83.0

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 (52) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +52 -74
  3. package/bin/_cctally_alerts.py +8 -1
  4. package/bin/_cctally_cache.py +963 -149
  5. package/bin/_cctally_config.py +43 -4
  6. package/bin/_cctally_core.py +933 -759
  7. package/bin/_cctally_dashboard.py +157 -47
  8. package/bin/_cctally_dashboard_cache_report.py +13 -6
  9. package/bin/_cctally_dashboard_conversation.py +1 -0
  10. package/bin/_cctally_dashboard_envelope.py +186 -8
  11. package/bin/_cctally_dashboard_share.py +60 -20
  12. package/bin/_cctally_dashboard_sources.py +427 -128
  13. package/bin/_cctally_db.py +605 -128
  14. package/bin/_cctally_doctor.py +413 -28
  15. package/bin/_cctally_five_hour.py +12 -5
  16. package/bin/_cctally_journal.py +2050 -156
  17. package/bin/_cctally_journal_repair.py +519 -0
  18. package/bin/_cctally_milestone_history.py +142 -56
  19. package/bin/_cctally_milestones.py +179 -111
  20. package/bin/_cctally_parser.py +42 -0
  21. package/bin/_cctally_project.py +24 -18
  22. package/bin/_cctally_quota.py +139 -25
  23. package/bin/_cctally_record.py +279 -108
  24. package/bin/_cctally_rederive.py +1052 -0
  25. package/bin/_cctally_reporting.py +58 -53
  26. package/bin/_cctally_setup.py +1 -0
  27. package/bin/_cctally_source_analytics.py +4 -1
  28. package/bin/_cctally_statusline.py +11 -11
  29. package/bin/_cctally_store.py +1039 -31
  30. package/bin/_cctally_sync_week.py +17 -8
  31. package/bin/_cctally_tui.py +421 -54
  32. package/bin/_cctally_update.py +133 -8
  33. package/bin/_cctally_weekrefs.py +14 -0
  34. package/bin/_lib_aggregators.py +10 -6
  35. package/bin/_lib_cache_report.py +101 -9
  36. package/bin/_lib_codex_pools.py +82 -0
  37. package/bin/_lib_conversation_query.py +126 -33
  38. package/bin/_lib_dashboard_sources.py +126 -1
  39. package/bin/_lib_diff_kernel.py +28 -15
  40. package/bin/_lib_doctor.py +342 -4
  41. package/bin/_lib_journal.py +924 -2
  42. package/bin/_lib_jsonl.py +43 -14
  43. package/bin/_lib_pricing.py +140 -21
  44. package/bin/_lib_readme_refresh.py +401 -0
  45. package/bin/_lib_rederive.py +395 -0
  46. package/bin/_lib_share.py +58 -2
  47. package/bin/cctally +56 -8
  48. package/dashboard/static/assets/{index-DJP4gEB7.js → index-3bgCMVHb.js} +52 -52
  49. package/dashboard/static/assets/index-D27EIHEI.css +1 -0
  50. package/dashboard/static/dashboard.html +2 -2
  51. package/package.json +6 -1
  52. package/dashboard/static/assets/index-Dk1nplOz.css +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,76 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.83.0] - 2026-07-28
9
+
10
+ ### Added
11
+ - New preview-first `cctally db journal-repair` command records an exact append-only operator audit for structurally invalid correction batches, safely rebuilds the disposable index, recovers idempotently after interruption, and keeps acknowledged omissions visible as warnings. (#402, #378)
12
+ - Dashboard source envelopes now publish independently derived `hero`, `quota`, and `sessions` freshness beside provider-generation freshness, and the dashboard routes each client surface to its own axis, so an idle quota window can age without making unrelated accounting or Sessions data stale. (#396)
13
+ - The append-only journal now has a crash-safe correction protocol: completed manifest-checked batches can replace or tombstone a previously recorded decision, and live replay plus a fresh stats rebuild agree on the highest effective revision without rewriting history or replaying alerts. (#372)
14
+ - Claude usage history can now be replayed through current derivation rules into a deterministic, side-effect-free correction plan, including account-scoped reset, credit, milestone, and cache-cost decisions. Missing retained inputs stop the plan with a precise reason, and applying a plan through the journal correction protocol makes the next preview a no-op. (#372)
15
+ - New preview-first `cctally db rederive --family claude-usage` command audits and corrects wrong journaled Claude-usage decisions with one append-only correction batch and an atomic stats-index rebuild. `--yes` applies; interrupted batches resume safely, repeated application is a no-op, retained source gaps fail before mutation, and replay never fires historical alerts. (#372)
16
+ - `doctor` reports a `journal.writer_guard` leg surfacing unsanctioned stats.db writes. (#386)
17
+
18
+ ### Changed
19
+ - stats.db auto-heal, `db rebuild --db stats`, and `db repair --db stats` now decline while any process still holds the database open, rather than replacing files underneath live readers. The usual holder is a running dashboard, so stop it first if a repair declines. (#386)
20
+ - Claude dollar and token totals are now explicitly documented as transcript-derived lower bounds: Claude Code bills title-generation and prompt-suggestion/side-query calls whose usable model and token fields are absent from normal transcripts, so cctally does not invent rows or apply a universal uplift. Retained main, resumed, and subagent assistant usage continues to be priced without double-counting `usage.iterations[]`. (#368, #377)
21
+
22
+ ### Fixed
23
+ - Claude responses retained as effective fast mode now use the correct rate across reporting, Projects, cache diagnostics, shares, statusline, and dashboard/conversation totals. Current Opus 5/4.8 fast rows use $10/$50 per MTok; historical retained Opus 4.6/4.7 fast rows keep $30/$150 pricing, while standard, missing, fallback, malformed, and recorded-cost rows remain unchanged. Recomputed views correct on the next read or sync without rewriting durable weekly, block, or milestone facts. (#413, #369)
24
+ - Percent milestones now preserve the first retained crossing's capture time, cost, logical snapshot references, and alert latch across stats-index replacement and replay. The stats cursor records the prefix atomically materialized beside its public position, so a cursor-only checkpoint can no longer skip an already-durable `pm:` event and let a later observation create a different revision-0 fact or fire the same alert twice. Upgrading rebuilds the disposable stats index at epoch 1004. (#410, #383)
25
+ - Codex quota history now identifies exactly one live physical cycle when two native identities reset near the same boundary, preventing overlapping account weeks, and the dashboard history cap retains the freshest independent model-pool fact without including it in account quota aggregates. (#412, #375)
26
+ - Dashboard current-cycle labels now render Claude's full week range once and distinguish a clipped Codex history range from its later nominal reset, preserving both provider facts without an apparent contradiction. (#412, #375)
27
+ - Weekly cost snapshots derived while recording a usage milestone now keep the triggering observation's exact subscription window, account, and capture-time cost boundary. Replaying an old observation after a later week anchor or later cache growth can no longer change its `wcs:` payload under the same journal identity, and a reset timestamp that rounds across UTC midnight now keeps usage, cost, and milestone facts on one canonical week key; explicit operator `sync-week` behavior is unchanged. (#410, #383)
28
+ - Disabling quota alerts now records a durable journal disarm so rebuilding and re-enabling above a threshold cannot dispatch historical alerts. Doctor now distinguishes a rebuildable stats-index mismatch from one with no journal data, warns when the live data directory is confirmed inside file-level backup/sync, and keeps dashboard health gathers subprocess-free. The remote full-suite watcher now documents and pins bare `--watch` as the only continuation form, while SQLite reader paths close their connections cleanly under `ResourceWarning` escalation. (#411, #340)
29
+ - Cache auto-heal now quarantines and rebuilds `cache.db` only when its locked forensics probe confirms corruption; transient classified triggers against an integrity-clean or unprobeable family preserve every file and record their precise origin for diagnosis. (#387, #404)
30
+ - Account-budget label, email, and key-prefix writes attempted during `stats.db` maintenance now report the maintenance hold and ask the user to retry, instead of incorrectly claiming that no accounts have been observed. The writer still accepts a raw 32-hex account key without opening the registry. (#391)
31
+ - Dashboard source refreshes no longer hold a long-lived `stats.db` read transaction that defeats SQLite's WAL autocheckpoint during concurrent usage recording, preventing the WAL from growing linearly while the dashboard is active. (#393)
32
+ - The dashboard now detects corruption that first appears in a real `stats.db` query after the lightweight opener probe, closes its live stats handle, performs one journal-backed auto-heal, and retries from the rebuilt index. If healing is unsafe or unavailable, the server still binds and shows a truthful stats repair action instead of blaming `cache.db`; cache-only and mixed failures keep deterministic database attribution. (#385, #407)
33
+ - The installed-build stats-writer guard now throttles violations across processes and rotates its diagnostic log at 1 MiB, while `doctor` reads only a bounded tail, preventing a multi-process violation storm from creating or loading an unbounded log. (#389)
34
+ - Dashboard startup verification no longer reads ambient Claude or Codex session data, eliminating an environment-dependent first-touch sync failure in the parity test. (#405, #348, #371, #392)
35
+ - Structurally invalid journal correction batches no longer prevent `stats.db` from rebuilding or the dashboard from starting. Every affected batch is omitted as a whole, valid corrections still apply, and `db rebuild --json` plus `doctor` identify the exact batch, violation kind, and stable fingerprint without rewriting history. (#402, #378)
36
+ - Current-week shared reports now keep provider spend visible while clearly
37
+ labeling stale hero-cycle evidence in Markdown, HTML, and SVG. Claude and
38
+ Codex disclosures stay separate in All reports, and a freshness-only change
39
+ now marks an existing composer section outdated without invalidating
40
+ unrelated panels or legacy source-less Claude shares. (#400, #361)
41
+ - Stable promotions now make the target GitHub Release describe the full upgrade from the previous promoted stable, including intermediate beta cuts. Notes are composed from the target tag's CHANGELOG, verified before the Latest flag moves, resume without duplicate edits, and refuse manual drift or an ambiguous concurrent baseline. (#355, #403)
42
+ - The dashboard's default 1440px bento now gives Recent Sessions titles two uniform readable lines instead of showing roughly 13 characters, while keeping Cost inside the card and preserving the mobile layout. (#364, #401)
43
+ - Closed five-hour blocks are now frozen as one deterministic journal fact at the first retained successor or expiry observation. A crash/retry, late-arriving session row, or later cache growth can no longer rewrite the block's totals, percentages, timestamps, model/project breakdowns, or create a second conflicting close event; open blocks remain live and mutable. (#399, #380)
44
+ - A stale migration-failure banner now clears after `cache.db` recovery or rebuild durably stamps that migration applied, while genuine unresolved migration failures remain visible. (#398, #352)
45
+ - Bare npm updates on the beta channel now resolve the live effective beta target before choosing an exact version or deciding the install is already current, so a fresh registry release cannot be hidden by the 24-hour cached check. Dry-runs preview the same fresh target without writing state; refresh failures retain the last-known-good target with an actionable warning; explicit version pins, stable npm, and Homebrew behavior are unchanged. The dashboard now resolves this target server-side after the user clicks Update and refreshes the running modal before installing it, rather than trusting a stale version supplied by an already-open browser. (#342, #397)
46
+ - `cctally cache-sync --rebuild` no longer appears to hang forever after compact Claude/Codex ingest completes. The separate transcript/search rebuild now reports provider, phase, elapsed time, and file progress; each provider runs in an isolated process with a 30-minute no-progress ceiling that active files refresh. A genuinely stuck transcript phase exits non-zero with a precise retry command while preserving committed accounting and leaving partial transcript state integrity-clean and retry-safe. (#395)
47
+ - A completed journal correction now automatically rebuilds the derived stats index on the next ingest when replacement is safe, then retries once without skipping later observations or replaying alerts. If a dashboard or another reader still holds the database open, cctally leaves the index untouched and names both the holder remedy and the manual rebuild command. (#394)
48
+ - A legacy interrupted `stats.db` rebuild that left an empty or missing index now self-heals from the append-only journal on the next CLI or dashboard open. Recovery serializes racing openers, preserves the maintenance lock and unrelated files, and removes only the matching stale rebuild scratch family. (#388)
49
+ - An interrupted `stats.db` rebuild no longer removes the current index before its replacement is ready, so commands never reopen an absent or empty database after a killed rebuild; retry safely converges from the journal. (#388)
50
+ - `db vacuum --db stats` serialized on the cache maintenance lock instead of the stats one, so a full rewrite of the stats database could run at the same time as a stats ingest, auto-heal, or rebuild. (#386)
51
+ - `db checkpoint --db stats`, `db skip`, and `db unskip` took no lock at all on stats.db, and first-open work — schema creation, migrations, backfills, and the journal cutover — ran unserialized no matter which command triggered it. (#386)
52
+ - stats.db file replacement (auto-heal, epoch rebuild, `db rebuild`) now verifies the database family has drained and blocks new readers from slipping in during cutover instead of replacing files under live SQLite handles. (#386)
53
+ - A stats.db repair or rebuild running in the background no longer parks every other cctally command indefinitely; they now report that maintenance is in progress and exit instead of hanging. (#386)
54
+ - Codex session names on the dashboard now follow the transcript privacy gate: LAN viewers without transcript access see a dash in both Codex and All views, while authorized local viewers keep the bounded task name. Shared snapshots no longer publish prompt-derived labels. (#382)
55
+ - Multi-account reset and credit detection now compares each Claude account only with its own prior usage, preventing one account's percentage drop from being misread as another account's reset or goodwill credit. (#372)
56
+ - Codex: a separate model pool (GPT-5.3-Codex-Spark) is no longer filed as account-level weekly quota, which had created a phantom cycle that truncated the real one and skewed the weekly card, cycle index, quota summary, forecast card and 5-hour blocks. (#373)
57
+ - Codex: the cycle modal's week navigation no longer disables itself when the newest retained cycle is not the current one. (#373)
58
+ - Codex: cycle detail now renders its retained per-percent milestones and 5h blocks, which had been empty for every cycle since multi-account support landed. (#373)
59
+ - Codex: opening a cycle now shows the same date range and current/historic state the cycle list shows; a cycle cut short by an early reset had opened with its full seven-day range and claimed to be the current one. (#373)
60
+ - The hero modal's week and block navigation arrows now meet the 24x24 minimum tap-target size on a phone, with no change to how they look. (#373)
61
+ - The dashboard starts again after upgrading on a machine whose history contains a rare recording conflict. A crash at exactly the wrong moment could leave two differently-worded versions of the same recorded fact in the history file, and because that file is append-only the duplicate could never be removed — so every attempt to rebuild the local index refused outright and the dashboard exited instead of opening. The rebuild now completes, keeps the first version it recorded, and tells you which facts were ambiguous instead of stopping. (#374)
62
+ - Recording a fact that would create such a conflict is now prevented rather than written: the check runs before the write instead of after it, and the affected row is brought back into agreement with what was already recorded. A rare duplicate recording also no longer re-runs on every refresh. (#374)
63
+ - `cctally doctor` reports ambiguous recorded facts as a warning naming each one. `cctally db rebuild --db stats` lists the same groups and exits successfully, and `cctally db rederive --family claude-usage` resolves them for good. (#374)
64
+ - Claude cost no longer undercounts 1-hour prompt-cache writes. Anthropic charges twice the base input rate for a 1-hour cache write and 1.25x for a 5-minute one, but every cache write was billed at the 5-minute rate — and Claude Code writes its main-session cache with the 1-hour TTL, so the cache line was reported about a third too low. Figures recomputed from your session history (`daily`, `monthly`, `weekly`, `session`, `blocks`, `range-cost`, `cache-report`, `diff`, `budget`, `forecast`, and the dashboard) correct themselves on the next sync; already-recorded weekly, block and milestone figures keep the value recorded at the time. One sync after upgrading re-reads your whole session history and will be noticeably slower than usual. (#195)
65
+
66
+ ## [1.82.1] - 2026-07-24
67
+
68
+ ### Changed
69
+ - The public README is a fresh, shorter screenshot-led tour, and it now refreshes itself on every stable release: promoting a release regenerates the screenshots against that exact version and updates a "Latest stable" highlights block on the GitHub page automatically. (#354)
70
+
71
+ ### Fixed
72
+ - The dashboard's Recent Sessions card shows session names again. Splitting transcript storage into its own database dropped the name lookup, so every row in the Session column had rendered a dash since then. Names come back from the stored conversation index, and a transcript store that is missing, locked, or rebuilding simply leaves the dash in place instead of holding up the rest of the dashboard. (#362)
73
+ - The All tab's Recent Sessions rows now show Claude session names too, matching the Codex rows beside them; previously only Codex rows were named there. Names still appear only for a local viewer, exactly as on the Claude tab. (#363)
74
+ - The Codex dashboard tab no longer blanks `SPENT THIS WEEK` (and `$/1% used`, `$/1% vs last week`, and the week label) after Codex has been idle for an hour. Codex only reports its quota while you are using it, so the weekly reading goes stale on an idle machine — the spend was never lost, just hidden. It now stays on screen, with the Snapshot chip naming the reading as stale and showing its age. (#350)
75
+ - Forecasts still pause on stale Codex quota evidence, on the dashboard and in a shared report: `Forecast @ reset` and a shared forecast's `Projected` column show `—` rather than projecting from a reading that may be an hour old. (#350)
76
+ - The All tab keeps withholding its combined total while a provider's quota evidence is stale, but now says that is the reason instead of reporting a generic degraded state. (#350)
77
+
8
78
  ## [1.82.0] - 2026-07-24
9
79
 
10
80
  ### Added
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <strong>Local-first usage tracker for Claude Code on Pro/Max - live dashboard, conversation viewer, weekly cost-per-percent trend, quota forecast, and threshold alerts. ccusage-compatible.</strong>
9
+ <strong>Understand Claude Code and Codex spend: a local dashboard, conversation viewer, and CLI reports for your subscription quota.</strong>
10
10
  </p>
11
11
 
12
12
  <p align="center">
@@ -16,54 +16,65 @@
16
16
  <a href="https://github.com/omrikais/cctally/stargazers"><img src="https://img.shields.io/github/stars/omrikais/cctally.svg" alt="GitHub stars"></a>
17
17
  </p>
18
18
 
19
- Claude Code's Pro/Max plans meter you on a weekly quota, but the only signal you get is a percentage that creeps up until you cap. `cctally` turns that percentage into something you can act on: it reads your local session logs, computes cost in-process, and shows you **how much each percent of quota is actually costing you, whether you're on track to cap this week, and exactly where the spend is going** - across a live web dashboard, a read-only conversation viewer, and a full set of CLI reports. Everything runs locally against your own `~/.claude` data. No account, no API key, no cloud processing — cctally never uploads your session data, and its only telemetry is an anonymous, opt-out [install count](#privacy--telemetry).
19
+ Your Claude Code plan meters you with a percentage that creeps up all week. cctally reads your local session logs and turns that percentage into dollars: what each percent of quota costs you, whether you are on track to cap before the reset, and where the spend is going. It does the same for OpenAI's Codex CLI. Everything runs on your own machine, against your own data. No account, no API key, and nothing is uploaded.
20
+
21
+ > **Claude cost coverage:** Claude dollar and token totals are
22
+ > [transcript-derived lower bounds](docs/claude-cost-coverage.md), not exact
23
+ > `/usage` billing totals. Claude Code can bill title-generation and
24
+ > prompt-suggestion/side-query requests without retaining usable model/token
25
+ > fields. cctally does not guess the missing amount. Codex accounting uses a
26
+ > different retained source and is unaffected.
20
27
 
21
28
  <p align="center">
22
29
  <img src="docs/img/dashboard-desktop.png" alt="cctally dashboard, desktop view" width="900">
23
30
  </p>
24
31
 
32
+ <!-- cctally:latest-stable:begin -->
33
+ **Latest stable: v1.82.1** (2026-07-24)
34
+
35
+ - The public README is a fresh, shorter screenshot-led tour, and it now refreshes itself on every stable release: promoting a release regenerates the screenshots against that exact version and updates a "Latest stable" highlights block on the GitHub page automatically.
36
+ - The dashboard's Recent Sessions card shows session names again. Splitting transcript storage into its own database dropped the name lookup, so every row in the Session column had rendered a dash since then. Names come back from the stored conversation index, and a transcript store that is missing, locked, or rebuilding simply leaves the dash in place instead of holding up the rest of the dashboard.
37
+ - The All tab's Recent Sessions rows now show Claude session names too, matching the Codex rows beside them; previously only Codex rows were named there. Names still appear only for a local viewer, exactly as on the Claude tab.
38
+ <!-- cctally:latest-stable:end -->
39
+
25
40
  ## Quick start
26
41
 
27
- **Requirements:** Python 3.11+, macOS or Linux, Claude Code installed and run at least once.
42
+ Requirements: Python 3.11+, macOS or Linux, Claude Code installed and run at least once.
28
43
 
29
44
  ```bash
30
45
  # Homebrew (macOS / Linux)
31
46
  brew install omrikais/cctally/cctally && cctally setup
32
47
 
33
- # or npm
48
+ # or npm
34
49
  npm install -g cctally && cctally setup
35
50
 
36
- # or from source
51
+ # or from source
37
52
  git clone https://github.com/omrikais/cctally && cd cctally && ./bin/cctally setup
38
53
  ```
39
54
 
40
- The reporting commands work right away — you don't need `cctally setup` first. Straight after an install, `npx cctally daily` (or, once it's global, `cctally daily`) reads your existing `~/.claude` session logs directly and prints a cost table; `cctally dashboard` and `cctally tui` work the same way. `cctally setup` is what adds *auto-recording* the live status-line quota percentage and the hooks that keep data flowing as you work — so run it once you want continuous tracking rather than on-demand reports.
41
-
42
- `cctally setup` symlinks the binaries into `~/.local/bin/`, adds three additive hooks to `~/.claude/settings.json` (it never overwrites existing entries), and bootstraps the local SQLite cache — on a large session history that first-run sync now shows live progress instead of sitting silent. If `~/.local/bin/` isn't on your `PATH`, the script prints the line to add. The npm install needs Python 3 on `PATH` - if setup reports "python3 not found", install it (`brew install python` on macOS) and re-run.
55
+ The reporting commands work immediately on your existing logs, before any setup. Running `cctally setup` once adds the hooks that record your quota percentage continuously as you work.
43
56
 
44
57
  ```bash
45
- cctally setup --status # verify hooks + symlinks
46
- cctally daily # cost-by-day - your first table
58
+ cctally daily # cost by day: your first table
47
59
  cctally dashboard # opens http://127.0.0.1:8789
60
+ cctally setup --status # verify the install
48
61
  ```
49
62
 
50
- For status-line integration and tuning, see [docs/installation.md](docs/installation.md) and [docs/configuration.md](docs/configuration.md).
51
-
52
- **Beta channel (opt-in).** Every release ships to a beta channel first; the maintainer promotes the ones that prove out to stable, which is the default. To ride along with the newest builds as they land, opt in with `cctally config set update.channel beta` (npm or source installs — Homebrew tracks stable). `cctally update` then installs the exact latest beta version, and `cctally config set update.channel stable` flips you back cleanly with no silent downgrade. See [docs/commands/update.md](docs/commands/update.md#beta-channel).
63
+ Install details (symlinks, PATH, Python version) live in [docs/installation.md](docs/installation.md). Every release ships to an opt-in beta channel first; see [docs/commands/update.md](docs/commands/update.md#beta-channel).
53
64
 
54
65
  ## The live dashboard
55
66
 
56
- `cctally dashboard` serves a web app at `localhost:8789` that updates live as you work - no refresh, no polling. Eleven panels cover the whole picture: **current week**, **forecast**, **$/1% trend**, **recent sessions**, **weekly**, **monthly**, **5-hour blocks**, **daily heatmap**, **projects**, **cache report**, and **recent alerts**. Any panel expands into a focused view, sessions are filterable and searchable, and a settings drawer tunes alerts and display options on the fly. It runs only on your own machine by default; one flag opens it to the other devices on your network when you want that.
67
+ `cctally dashboard` serves a local web app that updates live as you work, with no refresh and no polling. Panels cover the current week, the forecast, the cost trend, sessions, 5-hour blocks, projects, and alerts. Any panel expands into a focused view, sessions are searchable, and every report can be exported as shareable Markdown, HTML, or SVG with project names anonymized by default. It stays on your machine unless you choose to open it to your network.
57
68
 
58
69
  <table>
59
70
  <tr>
60
71
  <td>
61
72
  <img src="docs/img/dashboard-modal.png" alt="Dashboard with trend modal open">
62
- <br><em>Any panel expands into a focused view - here, twelve weeks of cost per percent.</em>
73
+ <br><em>Any panel expands into a focused view: here, twelve weeks of cost per percent.</em>
63
74
  </td>
64
75
  <td>
65
76
  <img src="docs/img/dashboard-warn.png" alt="Dashboard in warn state">
66
- <br><em>When the forecast projects a cap before the weekly reset, the modal goes amber.</em>
77
+ <br><em>When the forecast projects a cap before the weekly reset, the modal turns amber.</em>
67
78
  </td>
68
79
  </tr>
69
80
  <tr>
@@ -76,16 +87,12 @@ For status-line integration and tuning, see [docs/installation.md](docs/installa
76
87
 
77
88
  See [docs/commands/dashboard.md](docs/commands/dashboard.md).
78
89
 
79
- ## Conversation viewer
80
-
81
- The dashboard's **Conversations** tab is a read-only reader for your Claude Code transcripts - your local session history, rendered the way you'd actually want to review it. A searchable rail lists every conversation with its project, branch, model chips, and cost; the reader shows the full turn-by-turn flow with thinking blocks, tool calls (with diffs and command output), and a per-turn cost-and-token breakdown. Parallel **subagent threads** render as their own nested threads, an outline pane jumps you to any turn, and an in-conversation find bar plus a faceted full-text search (prompts, assistant text, tools, thinking) make a month of sessions navigable. The open conversation **live-tails** as you work, updating within a second.
90
+ ## Conversation viewer
82
91
 
83
- It only reads your transcripts - it never changes them - and everything stays on your own machine. Nothing is uploaded, and the viewer is reachable only from your computer unless you choose to share the dashboard with your network.
92
+ The dashboard's Conversations tab is a read-only reader for your Claude Code transcripts. A searchable rail lists every conversation with its project, branch, models, and cost; the reader shows the full turn-by-turn flow with thinking blocks, tool calls, and per-turn cost. Subagent runs render as nested threads, and the open conversation live-tails as you work. It never modifies your transcripts, and it never leaves your machine.
84
93
 
85
94
  <p align="center">
86
95
  <img src="docs/img/conversation-reader.png" alt="Conversation viewer: rail, threaded reader, and outline" width="900">
87
- <br>
88
- <em>Rail, threaded reader with tool cards and a subagent thread, and the jump-to outline.</em>
89
96
  </p>
90
97
 
91
98
  <p align="center">
@@ -96,97 +103,68 @@ It only reads your transcripts - it never changes them - and everything stays on
96
103
 
97
104
  ## Cost per 1% of quota
98
105
 
99
- The signature view. `cctally report` reframes each subscription week's spend as **dollars per percent of quota used**, so you can see your spending efficiency trend week over week - and whether this week is pulling above or below the line - instead of staring at a raw percentage. Persisted to SQLite, so the comparison survives across runs.
106
+ The signature view. `cctally report` reframes each week's spend as dollars per percent of quota used, so you can watch your spending efficiency trend week over week instead of staring at a raw percentage.
100
107
 
101
108
  <p align="center">
102
- <img src="docs/img/cli-report.svg" alt="cctally report: $ per 1% weekly trend">
103
- <br>
104
- <em>Weekly cost as dollars per percent of quota, with the delta against the prior week.</em>
109
+ <img src="docs/img/cli-report.svg" alt="cctally report: dollars per 1% weekly trend">
105
110
  </p>
106
111
 
107
112
  See [docs/commands/report.md](docs/commands/report.md).
108
113
 
109
- ## Forecast & budget
114
+ ## Forecast, budget, and alerts
110
115
 
111
- `cctally forecast` projects where your weekly percentage lands at the next reset - using both a week-average and a recent-24h rate - and tells you the daily $/% budget you'd need to stay under the 100% and 90% ceilings. When the data is thin it says so (`LOW CONF`) rather than guessing.
116
+ `cctally forecast` projects where your weekly percentage lands at the next reset and tells you the daily budget that keeps you under the cap. `cctally budget` tracks a dollar target per provider over a calendar period. Native desktop notifications fire the moment you cross a percent, 5-hour, or budget threshold, so a runaway week cannot sneak up on you.
112
117
 
113
118
  <p align="center">
114
119
  <img src="docs/img/cli-forecast.svg" alt="cctally forecast: will I cap this week?">
115
- <br>
116
- <em>Projected percent at the weekly reset, plus the daily budget to stay under the cap.</em>
117
120
  </p>
118
121
 
119
- For dollar budgets, `cctally budget` tracks an equivalent-vs-actual spend target over a configurable calendar period - per vendor, for both Claude and Codex - with a pace projection and an `ok`/`warn`/`over` verdict. See [docs/commands/forecast.md](docs/commands/forecast.md) and [docs/commands/budget.md](docs/commands/budget.md).
122
+ See [docs/commands/forecast.md](docs/commands/forecast.md), [docs/commands/budget.md](docs/commands/budget.md), and [docs/commands/alerts.md](docs/commands/alerts.md).
120
123
 
121
- ## Threshold alerts
124
+ ## 5-hour blocks
122
125
 
123
- Get a native desktop notification the moment you cross a percent milestone, so a runaway week can't sneak up on you. Alerts fire on weekly percent, 5-hour blocks, and budget thresholds, with three severity levels (info / warn / critical) and native popups on macOS and Linux - or run a command of your own. `cctally alerts test` confirms it's wired up. See [docs/commands/alerts.md](docs/commands/alerts.md).
124
-
125
- ## 5-hour analytics
126
-
127
- Claude Code's quota also runs on rolling 5-hour windows. `cctally blocks` and `cctally five-hour-blocks` break usage down per window - anchored to the real API resets where a recorded reset covers the window (`blocks` marks its heuristic fallback rows with `~`), not a re-sizable guess - with model and project rollups and cross-reset flags, and `cctally five-hour-breakdown` drills into the per-percent milestones inside a single block.
126
+ Claude Code's quota also runs on rolling 5-hour windows. `cctally blocks` and `cctally five-hour-blocks` break usage down per window, anchored to the real API resets, with model and project rollups.
128
127
 
129
128
  <p align="center">
130
129
  <img src="docs/img/cli-five-hour-blocks.svg" alt="cctally five-hour-blocks: 5h analytics with model breakdown">
131
- <br>
132
- <em>Each 5-hour window, with rollup totals and 7-day drift.</em>
133
130
  </p>
134
131
 
135
132
  See [docs/commands/blocks.md](docs/commands/blocks.md) and [docs/commands/five-hour-blocks.md](docs/commands/five-hour-blocks.md).
136
133
 
137
- ## Live terminal UI
134
+ ## Codex
138
135
 
139
- Prefer to stay in the terminal, or working over SSH? `cctally tui` shows the same live data as a refreshing terminal dashboard. It's the one feature that needs the optional `rich` library; every other command runs on a plain Python install with nothing else to set up.
136
+ If you also use OpenAI's Codex CLI, cctally tracks it with the same depth. `cctally codex daily`, `monthly`, and `session` are drop-in replacements for the ccusage codex commands, reading from your local `~/.codex/sessions/`. `cctally codex weekly` adds a subscription week rollup, and `cctally codex quota` shows your native Codex rate limit windows.
140
137
 
141
138
  <p align="center">
142
- <img src="docs/img/cli-tui.svg" alt="cctally tui: live terminal dashboard">
143
- <br>
144
- <em>The same data in the terminal, refreshed live.</em>
139
+ <img src="docs/img/cli-codex-daily.svg" alt="cctally codex daily: Codex cost by day">
145
140
  </p>
146
141
 
147
- See [docs/commands/tui.md](docs/commands/tui.md).
148
-
149
- ## Shareable reports
150
-
151
- All eight reporting commands (`report`, `daily`, `monthly`, `weekly`, `forecast`, `project`, `five-hour-blocks`, `session`) can render to shareable Markdown, HTML, or SVG with `--format`, a light/dark `--theme`, and `--output` / `--copy` / `--open`. Project names anonymize by default (`--reveal-projects` opts in), so you can post a snapshot without leaking where you work. See [docs/commands/share.md](docs/commands/share.md).
152
-
153
- ## Codex parity
142
+ See [docs/commands/codex.md](docs/commands/codex.md) and [docs/commands/codex-quota.md](docs/commands/codex-quota.md).
154
143
 
155
- If you also use OpenAI's Codex CLI, `cctally codex daily / monthly / session` are drop-ins for `ccusage codex daily / monthly / session`, reading from `~/.codex/sessions/`. The flat `codex-*` forms (drop-ins for the standalone `ccusage-codex` binary) stay as aliases, and `cctally codex weekly` adds a subscription-week rollup that upstream doesn't have. See [docs/commands/codex.md](docs/commands/codex.md).
144
+ ## Terminal UI
156
145
 
157
- ## Diagnostics & upkeep
146
+ Prefer to stay in the terminal, or working over SSH? `cctally tui` shows the same live data as a refreshing terminal dashboard. It is the one feature that needs the optional `rich` library; everything else runs on a plain Python install.
158
147
 
159
- `cctally doctor` gives you a read-only health check - install, hooks, sign-in, database, data freshness, pricing, and safety - and the same status shows up in the dashboard. `cctally pricing-check` warns you when the built-in model pricing is getting stale, `cctally db` manages the local database, and `cctally update` keeps your install current. The local cache is always safe to delete or rebuild.
160
-
161
- ## Privacy & telemetry
162
-
163
- cctally sends an anonymous, opt-out **install-count beat** — at most once a day — so the project can gauge how many people actually use it (npm and GitHub numbers are drowned in bots and mirrors). The entire payload is a one-way token that rotates every month (derived from a random local id that never leaves your machine and can't be recovered from the token), the cctally version, and a coarse OS family (`macos`/`linux`/`windows`/`other`). No identity, file paths, prompts, usage data, or stored IP — ever. The first beat is held for at least 24 hours after first run, so you always have a window to opt out first.
148
+ <p align="center">
149
+ <img src="docs/img/cli-tui.svg" alt="cctally tui: live terminal dashboard">
150
+ </p>
164
151
 
165
- Turn it off any time with `cctally telemetry off` (or `cctally config set telemetry.enabled false`, `CCTALLY_DISABLE_TELEMETRY=1`, or the community-standard `DO_NOT_TRACK=1`); it's also off automatically in dev checkouts. Run `cctally telemetry` to see the current state and exactly what would be sent. The full transparency page — token construction, retention, and an honest threat model — is [docs/telemetry.md](docs/telemetry.md).
152
+ See [docs/commands/tui.md](docs/commands/tui.md).
166
153
 
167
- ## A faster, local-first ccusage
154
+ ## ccusage compatibility
168
155
 
169
- `cctally` started as a local replacement for [`ccusage`](https://github.com/ryoppippi/ccusage) and stays compatible at the level of common flows: `cctally claude <cmd>` is a drop-in for `ccusage claude <cmd>` (and `cctally codex <cmd>` for `ccusage codex <cmd>`), with the flat forms (`cctally daily`, `cctally codex-daily`, …) kept as aliases. Paste your ccusage commands verbatim - then reach for the dashboard, forecast, trend, conversation viewer, and alerts that ccusage doesn't have.
156
+ cctally started as a local replacement for [ccusage](https://github.com/ryoppippi/ccusage) and stays drop-in compatible: `cctally claude <cmd>` and `cctally codex <cmd>` accept your ccusage commands verbatim. It is also fast: first table on 30 days of session data took about 2.6 seconds against about 31 seconds for ccusage (about 12x, measured 2026-05-05; methodology in [bench/README.md](https://github.com/omrikais/cctally/blob/main/bench/README.md)).
170
157
 
171
- It's also fast. Pricing is built in and computed in-process from a local SQLite cache, with no external tools to spawn. First table on 30 days of session data: **~2.6s (cctally) vs ~31s (ccusage)**, about 12× faster. Measured by `bench/cctally-vs-ccusage.sh` on macOS arm64, 2026-05-05; your numbers will vary.[^bench]
158
+ ## Privacy
172
159
 
173
- <!--
174
- Footnote target uses an absolute URL because GitHub's relative-link
175
- rewriter doesn't traverse into GFM footnote `<li>` content; on the
176
- repo home page (`/omrikais/cctally`, no trailing slash) the browser
177
- would resolve `bench/README.md` against the page URL and produce
178
- `/omrikais/bench/README.md` - a broken path. Regular paragraph links
179
- are unaffected.
180
- -->
181
- [^bench]: Methodology and reproduction: [`bench/README.md`](https://github.com/omrikais/cctally/blob/main/bench/README.md).
160
+ Everything runs locally against your own `~/.claude` and `~/.codex` data; session content is never uploaded. The only telemetry is an anonymous, opt-out install-count beat (a rotating one-way token, the version, and a coarse OS family, at most once a day). Turn it off any time with `cctally telemetry off`. The full transparency page is [docs/telemetry.md](docs/telemetry.md).
182
161
 
183
162
  ## Documentation
184
163
 
185
164
  - [Installation](docs/installation.md): symlinks, status-line wiring, Python version.
186
- - [Configuration](docs/configuration.md): `config.json` shape and week-start rules.
165
+ - [Configuration](docs/configuration.md): config.json shape and week-start rules.
187
166
  - [Architecture](docs/architecture.md): data flow, caches, week boundaries.
188
- - [Runtime data](docs/runtime-data.md): what lives in `~/.local/share/cctally/`.
189
- - [Telemetry](docs/telemetry.md): the anonymous install-count beat, in full — and how to opt out.
167
+ - [Telemetry](docs/telemetry.md): the anonymous install-count beat, in full.
190
168
  - [Command reference](docs/commands/): one page per subcommand.
191
169
 
192
170
  ## License
@@ -193,7 +193,14 @@ def _alert_label_prefix(axis: str, account_key: "str | None") -> str:
193
193
  db_path = _cctally_core.DB_PATH
194
194
  if not db_path.exists():
195
195
  return ""
196
- conn = _sq.connect(f"file:{db_path}?mode=ro", uri=True)
196
+ # #386: `mode=ro` is not exempt from the opener protocol — such a
197
+ # connection CREATES `-shm`/`-wal` when they are absent (measured). The
198
+ # enclosing `except Exception` degrades the label, never the alert.
199
+ import _cctally_store
200
+ conn = _cctally_store.stats_open_guarded(
201
+ db_path,
202
+ connect=lambda p: _sq.connect(f"file:{p}?mode=ro", uri=True),
203
+ )
197
204
  try:
198
205
  if _cctally_account.real_account_count(conn, vendor) <= 1:
199
206
  return ""