cctally 1.20.3 → 1.21.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 +10 -0
- package/bin/_lib_pricing.py +6 -0
- package/bin/cctally +41 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.21.0] - 2026-05-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Claude Opus 4.8 pricing.** `claude-opus-4-8` is now in the embedded `CLAUDE_MODEL_PRICING` table at the standard Opus 4.x rates ($5/MTok input · $6.25/MTok 5-minute cache write · $0.50/MTok cache read · $25/MTok output — identical to Opus 4.5/4.6/4.7), so every cost-computing command (`daily`, `weekly`, `session`, `blocks`, `report`, …) prices Opus 4.8 sessions correctly instead of warning "unknown model, treating cost as $0". The 1M-context variant `claude-opus-4-8[1m]` is added to `CLAUDE_MODEL_CONTEXT_WINDOWS` so `cctally statusline`'s 🧠 context-% segment measures against the real 1,000,000-token window rather than silently falling through to the 200K "opus" family default. Only the bare model id is added (matching how Opus 4.6/4.7 appear in real session JSONL); no dated `claude-opus-4-8-YYYYMMDD` twin is included since Anthropic's published id for this model is the bare alias.
|
|
12
|
+
|
|
13
|
+
## [1.20.4] - 2026-05-28
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- **Hardened the v1.20.3 canonical-anchor bypass in `_select_non_overlapping_recorded_windows`: provenance is now passed explicitly from the loader instead of being inferred from merged weight.** v1.20.3 identified canonical anchors as `items[*].weight >= 1000`, treating the loader's `+1000` overlay as a provenance proxy. But the merged weight is `raw_snapshot_count + (1000 if canonical else 0)`, so a raw-only bucket can also reach 1000+ under bulk-imported history or any future polling-frequency change to `record-usage`. Two such raw-only buckets <5h apart would both force-restore through the bypass and render as overlapping recorded blocks. The selector now takes an explicit `canonical_anchors` keyword set, populated by `_load_recorded_five_hour_windows` from `set(canonical_intervals.keys()) - truncated_anchors` — the loader already knows which anchors came from `five_hour_blocks`, so there's no need to re-derive provenance from weight. The DP still runs over the full item set (canonical + raw) so non-canonical phantoms adjacent to a canonical anchor in a different floor bucket continue to lose by weight comparison; the force-restore step only puts back items the caller marked canonical, never any raw-only bucket regardless of its weight. `_CANONICAL_WEIGHT_THRESHOLD = 1000` stays as the loader's overlay value (still names what dominates the DP arithmetic). No user-visible behavior change under current production data — the v1.20.3 fix already worked correctly for every real input shape we've observed — but the contract is no longer fragile to changes in polling cadence or future bulk-import surfaces. Regression: new `test_select_non_overlapping_recorded_windows_high_weight_raw_does_not_bypass` covers two raw-only items at weights 1500/1200; pre-fix returned both, post-fix DP arbitrates to one. (#116 review follow-up)
|
|
17
|
+
|
|
8
18
|
## [1.20.3] - 2026-05-28
|
|
9
19
|
|
|
10
20
|
### Fixed
|
package/bin/_lib_pricing.py
CHANGED
|
@@ -194,6 +194,12 @@ CLAUDE_MODEL_PRICING: dict[str, dict[str, Any]] = {
|
|
|
194
194
|
"cache_creation_input_token_cost": 6.25e-06,
|
|
195
195
|
"cache_read_input_token_cost": 5e-07,
|
|
196
196
|
},
|
|
197
|
+
"claude-opus-4-8": {
|
|
198
|
+
"input_cost_per_token": 5e-06,
|
|
199
|
+
"output_cost_per_token": 2.5e-05,
|
|
200
|
+
"cache_creation_input_token_cost": 6.25e-06,
|
|
201
|
+
"cache_read_input_token_cost": 5e-07,
|
|
202
|
+
},
|
|
197
203
|
"claude-sonnet-4-20250514": {
|
|
198
204
|
"input_cost_per_token": 3e-06,
|
|
199
205
|
"output_cost_per_token": 1.5e-05,
|
package/bin/cctally
CHANGED
|
@@ -281,6 +281,7 @@ _short_model_name = _lib_pricing._short_model_name
|
|
|
281
281
|
# Unknown model id → segment renders `🧠 N/A` + one-shot stderr warn.
|
|
282
282
|
CLAUDE_MODEL_CONTEXT_WINDOWS = {
|
|
283
283
|
# 1M-token variants (explicit IDs override the family default).
|
|
284
|
+
"claude-opus-4-8[1m]": 1_000_000,
|
|
284
285
|
"claude-opus-4-7[1m]": 1_000_000,
|
|
285
286
|
"claude-sonnet-4-5[1m]": 1_000_000,
|
|
286
287
|
# Default 200K for every other Sonnet/Opus/Haiku family member.
|
|
@@ -3951,17 +3952,23 @@ def _resolve_block_selector(
|
|
|
3951
3952
|
return dict(row) if row else None
|
|
3952
3953
|
|
|
3953
3954
|
|
|
3954
|
-
#
|
|
3955
|
-
#
|
|
3956
|
-
#
|
|
3957
|
-
#
|
|
3958
|
-
#
|
|
3959
|
-
#
|
|
3955
|
+
# Weight overlay applied per canonical (``five_hour_blocks``) row by
|
|
3956
|
+
# ``_load_recorded_five_hour_windows``: ``counts[snapped] += _CANONICAL_WEIGHT_THRESHOLD``.
|
|
3957
|
+
# Gives canonical anchors dominant weight inside the
|
|
3958
|
+
# ``_select_non_overlapping_recorded_windows`` DP, so any non-canonical
|
|
3959
|
+
# phantom adjacent to a canonical anchor loses on weight comparison. NOT
|
|
3960
|
+
# used as a provenance check — the selector takes an explicit
|
|
3961
|
+
# ``canonical_anchors`` set from the loader for the force-restore bypass
|
|
3962
|
+
# (issue #116 review follow-up: raw-only buckets with bulk-imported /
|
|
3963
|
+
# high-frequency snapshot histories can also accumulate >= 1000 weight,
|
|
3964
|
+
# so the threshold conflates provenance with support count).
|
|
3960
3965
|
_CANONICAL_WEIGHT_THRESHOLD = 1000
|
|
3961
3966
|
|
|
3962
3967
|
|
|
3963
3968
|
def _select_non_overlapping_recorded_windows(
|
|
3964
3969
|
items: list[tuple[dt.datetime, int]],
|
|
3970
|
+
*,
|
|
3971
|
+
canonical_anchors: set[dt.datetime] | None = None,
|
|
3965
3972
|
) -> list[dt.datetime]:
|
|
3966
3973
|
"""Pick the max-weight subset of recorded ``R`` values that respect
|
|
3967
3974
|
the 5h non-overlap constraint, with canonical anchors guaranteed
|
|
@@ -3978,9 +3985,8 @@ def _select_non_overlapping_recorded_windows(
|
|
|
3978
3985
|
snapshots: the subset that maximizes total support wins. Tie-break
|
|
3979
3986
|
in the take branch favors including more ``R`` values.
|
|
3980
3987
|
|
|
3981
|
-
Canonical bypass (issue #116): any
|
|
3982
|
-
|
|
3983
|
-
``five_hour_blocks`` rollup (the caller overlays at +1000).
|
|
3988
|
+
Canonical bypass (issue #116): any ``R`` passed in ``canonical_anchors``
|
|
3989
|
+
came from the authoritative ``five_hour_blocks`` rollup.
|
|
3984
3990
|
``maybe_update_five_hour_block`` already deduped via
|
|
3985
3991
|
``_canonical_5h_window_key`` pre-insert, so two canonical rows are
|
|
3986
3992
|
by definition non-overlapping physically — they only appear "in
|
|
@@ -3992,11 +3998,18 @@ def _select_non_overlapping_recorded_windows(
|
|
|
3992
3998
|
a genuinely-adjacent block pair). The DP still runs over the full
|
|
3993
3999
|
item set so non-canonical phantoms next to a canonical anchor get
|
|
3994
4000
|
dropped by weight comparison; the canonical-bypass only force-
|
|
3995
|
-
restores
|
|
3996
|
-
only phantom
|
|
4001
|
+
restores anchors the caller marked canonical, never adds back a
|
|
4002
|
+
raw-only phantom (even one whose raw weight ≥ ``_CANONICAL_WEIGHT_THRESHOLD``
|
|
4003
|
+
— the v1.20.3 fix used weight as a provenance proxy, which the
|
|
4004
|
+
review correctly flagged as conflating support count with provenance).
|
|
3997
4005
|
|
|
3998
4006
|
Args:
|
|
3999
4007
|
items: ``(R, support_count)`` pairs.
|
|
4008
|
+
canonical_anchors: explicit set of ``R`` values sourced from
|
|
4009
|
+
``five_hour_blocks``. Any present in ``items`` is guaranteed to
|
|
4010
|
+
appear in the result, even if the DP dropped it on the 5h
|
|
4011
|
+
non-overlap constraint. ``None`` / empty set = pure DP behavior
|
|
4012
|
+
(no bypass).
|
|
4000
4013
|
|
|
4001
4014
|
Returns:
|
|
4002
4015
|
Sorted ascending list of selected ``R`` values.
|
|
@@ -4039,11 +4052,14 @@ def _select_non_overlapping_recorded_windows(
|
|
|
4039
4052
|
else:
|
|
4040
4053
|
i -= 1
|
|
4041
4054
|
chosen.reverse()
|
|
4042
|
-
# Canonical bypass: force-restore any canonical
|
|
4043
|
-
#
|
|
4044
|
-
|
|
4045
|
-
if
|
|
4046
|
-
|
|
4055
|
+
# Canonical bypass: force-restore any canonical anchor the DP dropped
|
|
4056
|
+
# (issue #116). Intersect with items' keys so a caller passing anchors
|
|
4057
|
+
# outside the item set can't corrupt the result.
|
|
4058
|
+
if canonical_anchors:
|
|
4059
|
+
items_keys = {R for R, _ in items_sorted}
|
|
4060
|
+
present_canonical = canonical_anchors & items_keys
|
|
4061
|
+
if present_canonical and not present_canonical.issubset(chosen):
|
|
4062
|
+
return sorted(set(chosen) | present_canonical)
|
|
4047
4063
|
return chosen
|
|
4048
4064
|
|
|
4049
4065
|
|
|
@@ -4334,8 +4350,16 @@ def _load_recorded_five_hour_windows(
|
|
|
4334
4350
|
non_truncated_items = [
|
|
4335
4351
|
(a, w) for a, w in counts.items() if a not in truncated_anchors
|
|
4336
4352
|
]
|
|
4353
|
+
# Pass canonical provenance explicitly: every key currently in
|
|
4354
|
+
# canonical_intervals came from a `five_hour_blocks` row (raw-only
|
|
4355
|
+
# buckets never land in this map). Subtract truncated_anchors because
|
|
4356
|
+
# those bypass the DP via the separate merge below — keeping them
|
|
4357
|
+
# out of canonical_anchors here is a no-op for correctness but
|
|
4358
|
+
# mirrors the same scope as non_truncated_items for clarity.
|
|
4359
|
+
canonical_anchors_for_dp = set(canonical_intervals.keys()) - truncated_anchors
|
|
4337
4360
|
selected_non_truncated = _select_non_overlapping_recorded_windows(
|
|
4338
|
-
non_truncated_items
|
|
4361
|
+
non_truncated_items,
|
|
4362
|
+
canonical_anchors=canonical_anchors_for_dp,
|
|
4339
4363
|
)
|
|
4340
4364
|
# Merge truncated anchors back in, sorted ascending. Their non-
|
|
4341
4365
|
# overlap with the surrounding canonical blocks is guaranteed by
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"description": "Claude Code usage tracker and local dashboard for Pro/Max subscription limits - weekly cost-per-percent trend, quota forecasts, threshold alerts. ccusage-compatible.",
|
|
5
5
|
"homepage": "https://github.com/omrikais/cctally",
|
|
6
6
|
"repository": {
|