cctally 1.104.0 → 1.106.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.
- package/CHANGELOG.md +70 -0
- package/README.md +6 -6
- package/bin/_cctally_alerts.py +46 -13
- package/bin/_cctally_cache.py +193 -71
- package/bin/_cctally_core.py +151 -20
- package/bin/_cctally_dashboard.py +703 -297
- package/bin/_cctally_dashboard_conversation.py +438 -6
- package/bin/_cctally_dashboard_envelope.py +96 -10
- package/bin/_cctally_dashboard_perf.py +93 -0
- package/bin/_cctally_dashboard_share.py +7 -1
- package/bin/_cctally_dashboard_sources.py +988 -94
- package/bin/_cctally_db.py +297 -8
- package/bin/_cctally_diagnosis_sources.py +699 -186
- package/bin/_cctally_doctor.py +62 -23
- package/bin/_cctally_five_hour.py +53 -6
- package/bin/_cctally_forecast.py +205 -42
- package/bin/_cctally_journal.py +515 -67
- package/bin/_cctally_milestone_history.py +141 -58
- package/bin/_cctally_milestones.py +72 -0
- package/bin/_cctally_parser.py +10 -6
- package/bin/_cctally_percent_breakdown.py +26 -20
- package/bin/_cctally_project.py +5 -3
- package/bin/_cctally_quota.py +27 -2
- package/bin/_cctally_quota_model.py +596 -62
- package/bin/_cctally_record.py +1373 -353
- package/bin/_cctally_reporting.py +19 -0
- package/bin/_cctally_share.py +21 -3
- package/bin/_cctally_statusline.py +15 -11
- package/bin/_cctally_tui.py +509 -112
- package/bin/_cctally_weekrefs.py +322 -161
- package/bin/_lib_codex_conversation_query.py +258 -12
- package/bin/_lib_codex_hooks.py +26 -0
- package/bin/_lib_conversation_query.py +145 -15
- package/bin/_lib_credit.py +9 -2
- package/bin/_lib_credit_identity.py +159 -0
- package/bin/_lib_credit_selection.py +342 -0
- package/bin/_lib_dashboard_json.py +105 -0
- package/bin/_lib_dashboard_sources.py +16 -4
- package/bin/_lib_diagnosis.py +21 -2
- package/bin/_lib_doctor.py +65 -9
- package/bin/_lib_ingest_frontier.py +935 -0
- package/bin/_lib_meter_rate_change.py +95 -29
- package/bin/_lib_perf.py +22 -0
- package/bin/_lib_pricing.py +30 -3
- package/bin/_lib_quota_copy.py +30 -4
- package/bin/_lib_quota_model.py +249 -56
- package/bin/_lib_rate_change_delivery.py +119 -0
- package/bin/_lib_record.py +82 -0
- package/bin/_lib_rederive.py +8 -0
- package/bin/_lib_render.py +72 -4
- package/bin/_lib_retained_size.py +187 -0
- package/bin/_lib_snapshot_cache.py +517 -27
- package/bin/_lib_statusline.py +3 -29
- package/bin/_lib_subscription_weeks.py +141 -54
- package/bin/_lib_tick_stats.py +28 -4
- package/bin/_lib_view_models.py +128 -22
- package/bin/cctally +7 -0
- package/dashboard/static/assets/ConversationsView-Dst33C0n.js +72 -0
- package/dashboard/static/assets/DoctorModal-5rvkyksy.js +1 -0
- package/dashboard/static/assets/ModalRoot-BJH02VzE.js +1 -0
- package/dashboard/static/assets/ProjectsDrillPanel-CsR1bjJu.js +1 -0
- package/dashboard/static/assets/SourceDetailModal-BnTIwt-R.js +1 -0
- package/dashboard/static/assets/UpdateModal-D_RfssnW.js +7 -0
- package/dashboard/static/assets/index-rzXw99Hy.js +13 -0
- package/dashboard/static/assets/index-u3wLfjt2.css +1 -0
- package/dashboard/static/assets/outlineNavigation-C7J9lhGB.js +9 -0
- package/dashboard/static/assets/useKeymap-B3KCc_c7.js +1 -0
- package/dashboard/static/dashboard.html +3 -2
- package/package.json +6 -1
- package/dashboard/static/assets/index-D19TO7Mg.js +0 -97
- package/dashboard/static/assets/index-klO46NcU.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.106.0] - 2026-09-03
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- `cctally record-credit --force` replaces the single credit `--at` names instead of clearing the whole week, and refuses when `--at` names none. Recording a second credit in one week needs no `--force`: rerun with a different `--at`.
|
|
12
|
+
- A week Anthropic credited in place renders as one row on its own boundaries, instead of two rows split at the credit. A credit that also moved the week's declared end is marked, but still renders on the windows the API stated.
|
|
13
|
+
- The dashboard's Current Week header and modal name the week's own boundaries on a credited week, instead of the window that starts at the credit.
|
|
14
|
+
- A credited week's row is marked `+` in `cctally report` and `cctally weekly`, so a low `Used %` beside a week's worth of spend has a visible explanation.
|
|
15
|
+
- The dashboard's Weekly panel and its week detail mark a credited week, and the week detail, the Current Week card and its modal name why `$/1%` is withheld instead of the em-dash or `$0.000` you cannot tell from a week with no usage.
|
|
16
|
+
- The dashboard's `$/1%` tables — the Weekly modal's week list, the `$/1% Trend` panel and its modal — say `No climb` for a credited week whose ratio is withheld, instead of the em-dash you cannot tell from a week with no usage.
|
|
17
|
+
- `$/1%` for a credited week is measured from the credit forward, over the climb since it. With no climb yet the figure is withheld and names why. Uncredited weeks are unchanged.
|
|
18
|
+
- The dashboard's Current Week card, the TUI header and a shared Current Week recap report a credited week's whole spend, matching the week they name and the Weekly card beside them. Only `$/1%` is measured from the credit forward.
|
|
19
|
+
- `cctally milestone` history lists a credited week once instead of once per credit, and its detail separates the week's percent ladders with a row naming each credit and the level it dropped from.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- A conversation open in the viewer updates its own cost as its turns stream, for both Claude and Codex. The live tail advanced the transcript but not the accounting the cost is read from.
|
|
23
|
+
- A Codex conversation's cost no longer reads `$0.00` long after its turns finish. The dashboard now re-scans each provider at least every two minutes, so accounting stays current even when that provider's activity hooks never fire.
|
|
24
|
+
- Upgrading an existing installation to v1.105.0 left every background sync failing and the dashboard stuck on `server sync error`. The two columns that release added now reach an existing database, not only a freshly created one.
|
|
25
|
+
- After Anthropic zeroes your weekly counter, every 7d surface lowers on the next status-line tick. A reading captured in the same hour no longer holds the week at its pre-credit percentage, and no manual command is needed.
|
|
26
|
+
- A goodwill credit to a non-zero level records that level's own percent milestone. Before, the ladder for the new period opened one percent late, and `cctally percent-breakdown` was missing its first row.
|
|
27
|
+
- `cctally percent-breakdown`, the TUI's milestone panel and the milestone writer agree on which credit a percent belongs to, including after a database rebuild and for a credit recorded with `cctally record-credit`.
|
|
28
|
+
- A percent milestone crossed after a credit reports the cost spent since that credit. After a credit recorded with `cctally record-credit` it reported the whole week's spend, which made the first post-credit `$/1%` figure far too high.
|
|
29
|
+
- `cctally record-credit` no longer deletes genuine usage recorded between the moment you assert with `--at` and the moment you run the command.
|
|
30
|
+
- On an install with more than one Claude account, a reset detected for one account no longer records a credit for another, and no longer discards the first account's pending detection.
|
|
31
|
+
- `cctally forecast` measures its rate from the latest credit rather than across it, including one you recorded yourself. `cctally budget` keeps summing the whole week, because the money was spent inside one unchanged window.
|
|
32
|
+
- `cctally doctor`'s post-credit milestone check now sees a week credited with `cctally record-credit`, which it silently skipped before.
|
|
33
|
+
- `cctally record-credit` accepts a second credit inside the same hour as the first. Only a credit at the identical instant is still refused, because that is indistinguishable from running the command twice.
|
|
34
|
+
- An install upgrading from a version that predates the append-only journal now gets the current weekly-credit table shape, instead of reporting the new database version while missing the columns a credit needs.
|
|
35
|
+
- `cctally record-credit --force` no longer leaves a replaced credit's percent milestones behind after a database rebuild, and `cctally record-usage` no longer leaves a removed stale reading's five-hour milestones pointing at it.
|
|
36
|
+
- `cctally` no longer records a second, automatically-shaped credit for a large credit you already recorded with `cctally record-credit`.
|
|
37
|
+
- A stale pre-credit reading replayed by the status line after a credit is removed on the next tick that contradicts it, instead of holding every 7d surface at the old percentage and provoking a second, phantom credit.
|
|
38
|
+
- On an install with more than one Claude account, `cctally record-credit` no longer names another account's credit when refusing, and no longer finishes another account's interrupted credit as though it were yours.
|
|
39
|
+
|
|
40
|
+
## [1.105.0] - 2026-09-02
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- `cctally` prices Claude Fable 5.1 and Claude Mythos 5.1, including their cache reads at $0.25 per million tokens, so usage on either model is costed instead of contributing nothing with only a warning.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- `cctally quota` counts Claude Fable 5.1 and Claude Mythos 5.1 as part of the Fable 5 and Mythos 5 families, so a week containing that usage no longer has its weekly-quota fit withheld as an unsupported model mix.
|
|
47
|
+
- `cctally quota` refits your stored calibration once after this upgrade, because the set of model names it recognises has changed.
|
|
48
|
+
- `cctally quota` excludes fast-mode requests that Anthropic bills to usage credits, and widens its family-composition support floor to the corrected empirical bound.
|
|
49
|
+
- The dashboard's Forecast basis and `cctally statusline` use one vocabulary; the status line remains a meter-only hot path, while Forecast can name its calibrated model.
|
|
50
|
+
- The dashboard's Conversation viewer opens long histories from a bounded, integrity-checked outline ticket, hydrates the exact account-scoped outline automatically, and skips unchanged transcript estates between updates.
|
|
51
|
+
- `cctally dashboard` compresses and caches content-hashed assets and loads conversations and detail views only when opened, cutting cold JavaScript transfer while warm reloads reuse unchanged code.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- `cctally dashboard` shares a progressive outline's first-chunk build, cancels abandoned transfers, and rejects an oversized outline before allocating its encoded body.
|
|
55
|
+
- `cctally dashboard` applies failed or over-limit background memory checks as soon as they finish, including while sync is paused, and its soak gate now distinguishes measured growth from noisy flat samples.
|
|
56
|
+
- `cctally dashboard` keeps production-size cache admission checks inside the whole-process memory ceiling without shrinking or evicting the retained caches.
|
|
57
|
+
- `cctally dashboard` stops loading a previous conversation's outline after you switch sessions, so the new reader stays current and avoids duplicate outline requests.
|
|
58
|
+
- Concurrent dashboard Explain requests share identical in-flight work and admit only one diagnosis process tree at a time, so multiple tabs or LAN clients no longer multiply provider workers and transient memory.
|
|
59
|
+
- `cctally dashboard` bounds retained source, snapshot, conversation, ingest, and SSE work; invalidates conversation caches after same-path database replacement; keeps the newest slow-tab update; and releases owners without slowing refreshes.
|
|
60
|
+
- `cctally quota` now records a metering-rate change it detected earlier but could not store at the time, so a busy or rebuilding database no longer costs you that entry in your rate-change history permanently.
|
|
61
|
+
- `cctally quota` delivers a metering-rate change notification that an interrupted write recorded but never announced, and remembers which ones it has announced, so upgrading never replays your whole rate-change history at once.
|
|
62
|
+
- `cctally quota` retries a metering-rate notification that was recorded without an account attribution, including on a run narrowed with `--account`, so an install that later adds a second Claude account no longer loses that announcement.
|
|
63
|
+
- A metering-rate change notification names the account whose rate changed, so two accounts changing rate no longer produce two identical popups. An install with one account, or none, sees exactly the notification it saw before.
|
|
64
|
+
- `cctally explain` and the dashboard's Explain modal build current, baseline and conversation-derived evidence within their documented warm and cold latency budgets without caching a stale report or slowing dashboard updates.
|
|
65
|
+
- `cctally doctor` reports `not assessed` rather than `no change detected` when no quota calibration has been fitted, so a withheld fit no longer reads as a confirmed all-clear about your metering rate.
|
|
66
|
+
- `cctally quota` records the durable metering-rate history whenever its detector confirms a change, even when the fitted budget is withheld because this week's composition sits outside the calibration's support.
|
|
67
|
+
- The metering-rate notification says the calibration was withheld, and names why, instead of promising a fitted budget it cannot supply.
|
|
68
|
+
- `cctally doctor` stops telling you to run `cctally quota` for a fitted budget when the calibration behind the change has none, and names the withholding instead.
|
|
69
|
+
- The dashboard's rate-change toast points at the evidence for a metering-rate change rather than promising a fitted budget.
|
|
70
|
+
- `cctally dashboard` skips both provider file estates on caught-up ticks, falls back to a full pass when hook activity cannot be recorded, and reports the main refresh loop's measured CPU duty.
|
|
71
|
+
- `cctally dashboard` reuses unchanged Codex accounting and account-card populations across quota and one-file updates, shortening active refreshes without changing cadence or freshness.
|
|
72
|
+
- `cctally quota` withholds malformed retained timestamps without a traceback, filters offset-bearing credit times by their instant, and emits every JSON timestamp with a trailing `Z`.
|
|
73
|
+
- Historical forecast replays ignore mid-week reset events until their recorded detection instant, so a future event can no longer erase the samples that were available at the replayed time.
|
|
74
|
+
- Weekly projected-pace alerts use the same model-backed value as `cctally forecast` when a quota calibration applies, instead of silently stopping for calibrated users.
|
|
75
|
+
- The dashboard's Forecast modal keeps an extreme projection label inside the card instead of clipping it at the 110% track edge.
|
|
76
|
+
- `cctally record-usage` no longer fabricates a percent milestone from a stale pre-credit reading after Anthropic zeroes your weekly counter, so a fresh week's milestone ladder no longer opens at a percentage you never reached.
|
|
77
|
+
|
|
8
78
|
## [1.104.0] - 2026-08-29
|
|
9
79
|
|
|
10
80
|
### Added
|
package/README.md
CHANGED
|
@@ -30,15 +30,15 @@ Your Claude Code plan meters you with a percentage that creeps up all week. ccta
|
|
|
30
30
|
</p>
|
|
31
31
|
|
|
32
32
|
<!-- cctally:latest-stable:begin -->
|
|
33
|
-
**Latest stable: v1.
|
|
33
|
+
**Latest stable: v1.104.0** (2026-08-29)
|
|
34
34
|
|
|
35
|
-
Highlights from the `v1.
|
|
35
|
+
Highlights from the `v1.101.0` to `v1.104.0` stable upgrade:
|
|
36
36
|
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
37
|
+
- `cctally quota` reports how much of your weekly quota your usage consumes and whether Anthropic has changed the metering rate, from a budget fitted to your own history rather than a shipped constant. See `docs/commands/quota.md`.
|
|
38
|
+
- `cctally quota --json` publishes the fit, the current week's consumption and projection, the composition support radii and the detector's own bound under `schemaVersion` 1.
|
|
39
|
+
- `cctally quota --reset-calibration` discards the stored calibration for one account, and `--since` and `--watch-from` narrow or override the window the analysis uses.
|
|
40
40
|
|
|
41
|
-
[See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.
|
|
41
|
+
[See every change in this stable upgrade](https://github.com/omrikais/cctally/releases/tag/v1.104.0)
|
|
42
42
|
<!-- cctally:latest-stable:end -->
|
|
43
43
|
|
|
44
44
|
## Quick start
|
package/bin/_cctally_alerts.py
CHANGED
|
@@ -149,8 +149,9 @@ def _alert_text_meter_rate_change(payload: dict, tz) -> tuple[str, str, str]:
|
|
|
149
149
|
is no threshold to name, which is the whole reason this family is not an
|
|
150
150
|
`AXIS_REGISTRY` member.
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
`docs/alerts-gotchas.md` (#661 S2 Stage C review, F7b
|
|
152
|
+
THREE deliberate divergences from the threshold builders, all recorded
|
|
153
|
+
in `docs/alerts-gotchas.md` (#661 S2 Stage C review, F7b; the third
|
|
154
|
+
added by #688):
|
|
154
155
|
|
|
155
156
|
* The body does not end with `_with_next_step`. That helper derives its
|
|
156
157
|
affordance through `alert_next_step_command`, which branches on
|
|
@@ -159,7 +160,20 @@ def _alert_text_meter_rate_change(payload: dict, tz) -> tuple[str, str, str]:
|
|
|
159
160
|
directly instead.
|
|
160
161
|
* This builder and `_alert_text_quota` above it live in this glue module
|
|
161
162
|
rather than in the pure `_lib_alerts_payload` kernel, which holds the
|
|
162
|
-
other
|
|
163
|
+
other six. That is the placement both non-registry families use.
|
|
164
|
+
* The BODY varies on a payload field, which no threshold builder does.
|
|
165
|
+
#688 records a transition whenever the detector qualifies, including
|
|
166
|
+
when the command's own verdict is withheld for a reason that bears on
|
|
167
|
+
predicting forward, so a recorded transition may sit beside a verdict
|
|
168
|
+
`cctally quota` refuses. Sending that user to a fitted budget that
|
|
169
|
+
does not exist is a false instruction, so the body names the
|
|
170
|
+
withholding instead and points at the evidence.
|
|
171
|
+
|
|
172
|
+
NOT a fourth divergence (#697): the standard R8 `[<label>] ` title prefix
|
|
173
|
+
is applied DOWNSTREAM by `_dispatch_alert_notification`, which resolves the
|
|
174
|
+
account registry from this payload's own `provider` field rather than from
|
|
175
|
+
`_AXIS_VENDOR`. The prefix is shared behaviour every axis takes, so this
|
|
176
|
+
builder keeps its signature and its return and never learns about accounts.
|
|
163
177
|
|
|
164
178
|
`effective_from` IS routed through `format_display_dt`. It is a clock
|
|
165
179
|
instant, not a calendar day, so the carve-out `_alert_text_weekly`
|
|
@@ -183,10 +197,13 @@ def _alert_text_meter_rate_change(payload: dict, tz) -> tuple[str, str, str]:
|
|
|
183
197
|
raw, tz, fmt="%Y-%m-%d", suffix=False) if raw else ""
|
|
184
198
|
except ValueError:
|
|
185
199
|
effective = ""
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
f"
|
|
189
|
-
|
|
200
|
+
withheld = payload.get("withholding_status")
|
|
201
|
+
if withheld:
|
|
202
|
+
tail = (f"The calibration was withheld ({withheld}), so no fitted "
|
|
203
|
+
f"budget is available. Run `cctally quota` for the evidence.")
|
|
204
|
+
else:
|
|
205
|
+
tail = "Run `cctally quota` for the fitted budget and its evidence."
|
|
206
|
+
body = f"Effective {effective}. {tail}" if effective else tail
|
|
190
207
|
return f"cctally - {provider} metering rate changed", subtitle, body
|
|
191
208
|
|
|
192
209
|
|
|
@@ -227,7 +244,8 @@ _AXIS_VENDOR = {
|
|
|
227
244
|
}
|
|
228
245
|
|
|
229
246
|
|
|
230
|
-
def _alert_label_prefix(axis: str, account_key: "str | None"
|
|
247
|
+
def _alert_label_prefix(axis: str, account_key: "str | None",
|
|
248
|
+
provider: "object | None" = None) -> str:
|
|
231
249
|
"""Return ``"[<label>] "`` for an account-specific crossing when the vendor
|
|
232
250
|
has >1 real account (R8), else ``""``. Best-effort + never-raise: a missing
|
|
233
251
|
DB / read error / vendor-wide (`*`) row yields no prefix, so a ≤1-real-account
|
|
@@ -236,13 +254,26 @@ def _alert_label_prefix(axis: str, account_key: "str | None") -> str:
|
|
|
236
254
|
Delegates the R8 gate + label precedence to the single-definition helpers
|
|
237
255
|
``_cctally_account.real_account_count`` / ``account_label`` (P2-CQ1) so the
|
|
238
256
|
">1 real account" trigger and the key->label map have exactly one home; this
|
|
239
|
-
wrapper only opens the RO connection and stays best-effort/never-raise.
|
|
257
|
+
wrapper only opens the RO connection and stays best-effort/never-raise.
|
|
258
|
+
|
|
259
|
+
#697: the VENDOR comes from the payload's own ``provider`` when it carries
|
|
260
|
+
one, and from ``_AXIS_VENDOR`` otherwise. The metering-rate-change family
|
|
261
|
+
is the only payload that carries the key, and it is authoritative there —
|
|
262
|
+
the family's visible title is derived from that same field, so a static map
|
|
263
|
+
entry could send a Codex title to Claude's registry with nothing reporting
|
|
264
|
+
the disagreement. The lookup is normalized because ``real_account_count``
|
|
265
|
+
matches ``accounts.provider`` exactly, and an unmatched provider degrades
|
|
266
|
+
silently to no prefix. The whole resolution sits INSIDE the guard so that a
|
|
267
|
+
provider whose ``__str__`` raises cannot escape the never-raise contract.
|
|
268
|
+
"""
|
|
240
269
|
if not account_key or account_key == _lib_accounts.VENDOR_WIDE:
|
|
241
270
|
return ""
|
|
242
|
-
vendor = _AXIS_VENDOR.get(axis)
|
|
243
|
-
if vendor is None:
|
|
244
|
-
return ""
|
|
245
271
|
try:
|
|
272
|
+
vendor = str(provider).strip().lower() if provider is not None else ""
|
|
273
|
+
if not vendor:
|
|
274
|
+
vendor = _AXIS_VENDOR.get(axis)
|
|
275
|
+
if vendor is None:
|
|
276
|
+
return ""
|
|
246
277
|
import sqlite3 as _sq
|
|
247
278
|
import _cctally_account
|
|
248
279
|
db_path = _cctally_core.DB_PATH
|
|
@@ -355,8 +386,10 @@ def _dispatch_alert_notification(
|
|
|
355
386
|
# R8 label prefix (#341): an account-specific crossing gains a `[<label>] `
|
|
356
387
|
# title prefix ONLY when the vendor has >1 real account. Byte-identical at
|
|
357
388
|
# <=1 real account (empty prefix). Applied to the notification title only.
|
|
389
|
+
# #697: the vendor whose registry is counted comes from the payload's own
|
|
390
|
+
# `provider` when it carries one, and from `_AXIS_VENDOR` otherwise.
|
|
358
391
|
account_key = payload.get("account_key")
|
|
359
|
-
title = _alert_label_prefix(axis, account_key) + title
|
|
392
|
+
title = _alert_label_prefix(axis, account_key, payload.get("provider")) + title
|
|
360
393
|
|
|
361
394
|
# Severity (3-tier) drives both the notify-send urgency token and the
|
|
362
395
|
# trailing log column. A missing threshold (defensive — shouldn't happen for
|