cctally 1.16.0 → 1.17.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 +8 -0
- package/bin/_lib_aggregators.py +10 -5
- package/bin/_lib_pricing.py +43 -1
- package/bin/_lib_view_models.py +8 -8
- package/bin/cctally +52 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.17.0] - 2026-05-27
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`--speed {auto,standard,fast}` on `cctally codex-daily`, `codex-monthly`, `codex-weekly`, and `codex-session` (and their `cctally codex <cmd>` subgroup forms)** — a drop-in for `ccusage codex --speed`. `auto` (the default) reads `service_tier` from `~/.codex/config.toml` and applies fast-tier pricing when it is `fast` or `priority`; `fast`/`standard` force the tier. Fast-tier multiplies the per-model Codex cost by a fixed factor (`gpt-5.5` ×2.5, all other models ×2.0). `--json` gains no new field; only the `costUSD` figures change. On the flat `codex-*` forms this is a cctally extension (the standalone `ccusage-codex` binary has no `--speed`); the subgroup form mirrors `ccusage codex`. (#86)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- **`cctally codex-*` now applies fast-tier (≥2×) Codex pricing by default when `~/.codex/config.toml` sets `service_tier = "fast"` or `"priority"`.** Previously cctally always priced Codex usage at the standard tier, under-reporting cost for workstations on the fast/priority tier (who pay the premium). With the new `--speed auto` default, those costs now match what was actually billed; pass `--speed standard` to force the old behavior. Users without a fast/priority `service_tier` see identical numbers. (#86)
|
|
15
|
+
|
|
8
16
|
## [1.16.0] - 2026-05-26
|
|
9
17
|
|
|
10
18
|
### Added
|
package/bin/_lib_aggregators.py
CHANGED
|
@@ -363,6 +363,7 @@ class ClaudeSessionUsage:
|
|
|
363
363
|
def _aggregate_codex_buckets(
|
|
364
364
|
entries: list[CodexEntry],
|
|
365
365
|
key_fn: Callable[[CodexEntry], str],
|
|
366
|
+
speed: str = "standard",
|
|
366
367
|
) -> list[CodexBucketUsage]:
|
|
367
368
|
"""Group CodexEntry list into per-bucket records sorted by key ascending.
|
|
368
369
|
|
|
@@ -386,6 +387,7 @@ def _aggregate_codex_buckets(
|
|
|
386
387
|
entry.cached_input_tokens,
|
|
387
388
|
entry.output_tokens,
|
|
388
389
|
entry.reasoning_output_tokens,
|
|
390
|
+
speed=speed,
|
|
389
391
|
)
|
|
390
392
|
|
|
391
393
|
bucket["input"] += entry.input_tokens
|
|
@@ -441,6 +443,7 @@ def _aggregate_codex_buckets(
|
|
|
441
443
|
|
|
442
444
|
def _aggregate_codex_daily(
|
|
443
445
|
entries: list[CodexEntry], *, tz_name: str | None = None,
|
|
446
|
+
speed: str = "standard",
|
|
444
447
|
) -> list[CodexBucketUsage]:
|
|
445
448
|
"""Daily grouping. Default: local tz. With ``tz_name``: that IANA zone."""
|
|
446
449
|
tz = _resolve_tz(tz_name)
|
|
@@ -448,11 +451,12 @@ def _aggregate_codex_daily(
|
|
|
448
451
|
key_fn = lambda e: e.timestamp.astimezone(tz).strftime("%Y-%m-%d") # noqa: E731
|
|
449
452
|
else:
|
|
450
453
|
key_fn = lambda e: e.timestamp.astimezone().strftime("%Y-%m-%d") # noqa: E731
|
|
451
|
-
return _aggregate_codex_buckets(entries, key_fn=key_fn)
|
|
454
|
+
return _aggregate_codex_buckets(entries, key_fn=key_fn, speed=speed)
|
|
452
455
|
|
|
453
456
|
|
|
454
457
|
def _aggregate_codex_monthly(
|
|
455
458
|
entries: list[CodexEntry], *, tz_name: str | None = None,
|
|
459
|
+
speed: str = "standard",
|
|
456
460
|
) -> list[CodexBucketUsage]:
|
|
457
461
|
"""Monthly grouping. Default: local tz. With ``tz_name``: that IANA zone."""
|
|
458
462
|
tz = _resolve_tz(tz_name)
|
|
@@ -460,13 +464,14 @@ def _aggregate_codex_monthly(
|
|
|
460
464
|
key_fn = lambda e: e.timestamp.astimezone(tz).strftime("%Y-%m") # noqa: E731
|
|
461
465
|
else:
|
|
462
466
|
key_fn = lambda e: e.timestamp.astimezone().strftime("%Y-%m") # noqa: E731
|
|
463
|
-
return _aggregate_codex_buckets(entries, key_fn=key_fn)
|
|
467
|
+
return _aggregate_codex_buckets(entries, key_fn=key_fn, speed=speed)
|
|
464
468
|
|
|
465
469
|
|
|
466
470
|
def _aggregate_codex_weekly(
|
|
467
471
|
entries: list[CodexEntry],
|
|
468
472
|
tz_name: str | None,
|
|
469
473
|
week_start_idx: int,
|
|
474
|
+
speed: str = "standard",
|
|
470
475
|
) -> list[CodexBucketUsage]:
|
|
471
476
|
"""Group Codex entries by calendar week.
|
|
472
477
|
|
|
@@ -485,7 +490,7 @@ def _aggregate_codex_weekly(
|
|
|
485
490
|
week_start = local_date - dt.timedelta(days=diff)
|
|
486
491
|
return week_start.isoformat()
|
|
487
492
|
|
|
488
|
-
return _aggregate_codex_buckets(entries, key_fn=_week_key)
|
|
493
|
+
return _aggregate_codex_buckets(entries, key_fn=_week_key, speed=speed)
|
|
489
494
|
|
|
490
495
|
|
|
491
496
|
def _session_path_parts(source_path: str) -> tuple[str, str, str]:
|
|
@@ -520,7 +525,7 @@ def _session_path_parts(source_path: str) -> tuple[str, str, str]:
|
|
|
520
525
|
return str(stem), stem.name, str(stem.parent)
|
|
521
526
|
|
|
522
527
|
|
|
523
|
-
def _aggregate_codex_sessions(entries: list[CodexEntry]) -> list[CodexSessionUsage]:
|
|
528
|
+
def _aggregate_codex_sessions(entries: list[CodexEntry], speed: str = "standard") -> list[CodexSessionUsage]:
|
|
524
529
|
"""Group by session file path (upstream-compatible).
|
|
525
530
|
|
|
526
531
|
Sessions are keyed by the full relative-path-without-.jsonl rather than
|
|
@@ -543,7 +548,7 @@ def _aggregate_codex_sessions(entries: list[CodexEntry]) -> list[CodexSessionUsa
|
|
|
543
548
|
})
|
|
544
549
|
cost = _calculate_codex_entry_cost(
|
|
545
550
|
entry.model, entry.input_tokens, entry.cached_input_tokens,
|
|
546
|
-
entry.output_tokens, entry.reasoning_output_tokens,
|
|
551
|
+
entry.output_tokens, entry.reasoning_output_tokens, speed=speed,
|
|
547
552
|
)
|
|
548
553
|
sess["input"] += entry.input_tokens
|
|
549
554
|
sess["cached_input"] += entry.cached_input_tokens
|
package/bin/_lib_pricing.py
CHANGED
|
@@ -346,6 +346,44 @@ _unknown_codex_model_warnings: set[str] = set()
|
|
|
346
346
|
# directly comparable.
|
|
347
347
|
CODEX_LEGACY_FALLBACK_MODEL = "gpt-5"
|
|
348
348
|
|
|
349
|
+
# Per-model fast-tier price multipliers, ported from ryoppippi/ccusage
|
|
350
|
+
# fast-multiplier-overrides.json ("exact" map — Codex/gpt entries only; the
|
|
351
|
+
# upstream claude-opus-* entries are for ccusage's Claude adapter and never
|
|
352
|
+
# price Codex models). Any fast-tier model NOT listed falls back to
|
|
353
|
+
# CODEX_FAST_MULTIPLIER_FALLBACK — upstream's `fast_multiplier == 1.0 → 2.0`
|
|
354
|
+
# rule in adapter/codex/report.rs:calculate_codex_model_cost.
|
|
355
|
+
CODEX_FAST_MULTIPLIER_OVERRIDES: dict[str, float] = {
|
|
356
|
+
"gpt-5.5": 2.5,
|
|
357
|
+
"gpt-5.4": 2.0,
|
|
358
|
+
"gpt-5.3-codex": 2.0,
|
|
359
|
+
}
|
|
360
|
+
CODEX_FAST_MULTIPLIER_FALLBACK = 2.0
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _codex_fast_multiplier(model: str) -> float:
|
|
364
|
+
"""Fast-tier price multiplier for a Codex model (standard tier = 1.0)."""
|
|
365
|
+
return CODEX_FAST_MULTIPLIER_OVERRIDES.get(model, CODEX_FAST_MULTIPLIER_FALLBACK)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _codex_config_requests_fast_service_tier(content: str) -> bool:
|
|
369
|
+
"""True iff any line sets ``service_tier = "fast"|"priority"``.
|
|
370
|
+
|
|
371
|
+
Naive line-scan ported from ryoppippi/ccusage adapter/codex/speed.rs
|
|
372
|
+
(NOT a TOML parse): strip the trailing ``#``-comment, split on the first
|
|
373
|
+
``=``, the key must be exactly ``service_tier``, and the quote-stripped
|
|
374
|
+
value must be ``fast`` or ``priority``. Matches a ``service_tier`` line in
|
|
375
|
+
ANY table; ignores ``service_tier_override`` and substrings like
|
|
376
|
+
``"breakfast"``.
|
|
377
|
+
"""
|
|
378
|
+
for line in content.splitlines():
|
|
379
|
+
setting = line.split("#", 1)[0].strip()
|
|
380
|
+
key, sep, value = setting.partition("=")
|
|
381
|
+
if not sep or key.strip() != "service_tier":
|
|
382
|
+
continue
|
|
383
|
+
if value.strip().strip("\"'") in ("fast", "priority"):
|
|
384
|
+
return True
|
|
385
|
+
return False
|
|
386
|
+
|
|
349
387
|
|
|
350
388
|
def _resolve_codex_pricing(model: str) -> tuple[dict[str, Any] | None, bool]:
|
|
351
389
|
"""Return (pricing_dict, is_fallback).
|
|
@@ -450,6 +488,7 @@ def _calculate_codex_entry_cost(
|
|
|
450
488
|
cached_input_tokens: int,
|
|
451
489
|
output_tokens: int,
|
|
452
490
|
reasoning_output_tokens: int,
|
|
491
|
+
speed: str = "standard",
|
|
453
492
|
) -> float:
|
|
454
493
|
"""Compute USD cost for one Codex `token_count` event.
|
|
455
494
|
|
|
@@ -505,7 +544,10 @@ def _calculate_codex_entry_cost(
|
|
|
505
544
|
"output_cost_per_token",
|
|
506
545
|
"output_cost_per_token_above_272k_tokens",
|
|
507
546
|
)
|
|
508
|
-
|
|
547
|
+
base = input_cost + cached_input_cost + output_cost
|
|
548
|
+
if speed == "fast":
|
|
549
|
+
base *= _codex_fast_multiplier(model)
|
|
550
|
+
return base
|
|
509
551
|
|
|
510
552
|
|
|
511
553
|
def _short_model_name(model: str) -> str:
|
package/bin/_lib_view_models.py
CHANGED
|
@@ -1703,7 +1703,7 @@ def _codex_period_start_from_month_bucket(buckets) -> "dt.datetime | None":
|
|
|
1703
1703
|
return None
|
|
1704
1704
|
|
|
1705
1705
|
|
|
1706
|
-
def build_codex_daily_view(entries, *, now_utc, tz_name=None):
|
|
1706
|
+
def build_codex_daily_view(entries, *, now_utc, tz_name=None, speed="standard"):
|
|
1707
1707
|
"""Build a ``CodexDailyView`` from a list of ``CodexEntry`` (issue #58).
|
|
1708
1708
|
|
|
1709
1709
|
Delegates bucketing to ``_aggregate_codex_daily`` (LiteLLM-snapshot
|
|
@@ -1712,7 +1712,7 @@ def build_codex_daily_view(entries, *, now_utc, tz_name=None):
|
|
|
1712
1712
|
(None → host-local fallback inside the aggregator).
|
|
1713
1713
|
"""
|
|
1714
1714
|
_agg = _load_lib("_lib_aggregators")
|
|
1715
|
-
buckets = _agg._aggregate_codex_daily(entries, tz_name=tz_name)
|
|
1715
|
+
buckets = _agg._aggregate_codex_daily(entries, tz_name=tz_name, speed=speed)
|
|
1716
1716
|
total_cost, total_tok = _codex_bucket_totals(buckets)
|
|
1717
1717
|
return CodexDailyView(
|
|
1718
1718
|
rows=tuple(buckets),
|
|
@@ -1724,7 +1724,7 @@ def build_codex_daily_view(entries, *, now_utc, tz_name=None):
|
|
|
1724
1724
|
)
|
|
1725
1725
|
|
|
1726
1726
|
|
|
1727
|
-
def build_codex_monthly_view(entries, *, now_utc, tz_name=None):
|
|
1727
|
+
def build_codex_monthly_view(entries, *, now_utc, tz_name=None, speed="standard"):
|
|
1728
1728
|
"""Build a ``CodexMonthlyView`` from a list of ``CodexEntry`` (issue #58).
|
|
1729
1729
|
|
|
1730
1730
|
Same wrap-the-kernel posture as ``build_codex_daily_view``; bucket
|
|
@@ -1732,7 +1732,7 @@ def build_codex_monthly_view(entries, *, now_utc, tz_name=None):
|
|
|
1732
1732
|
earliest visible month at UTC midnight.
|
|
1733
1733
|
"""
|
|
1734
1734
|
_agg = _load_lib("_lib_aggregators")
|
|
1735
|
-
buckets = _agg._aggregate_codex_monthly(entries, tz_name=tz_name)
|
|
1735
|
+
buckets = _agg._aggregate_codex_monthly(entries, tz_name=tz_name, speed=speed)
|
|
1736
1736
|
total_cost, total_tok = _codex_bucket_totals(buckets)
|
|
1737
1737
|
return CodexMonthlyView(
|
|
1738
1738
|
rows=tuple(buckets),
|
|
@@ -1745,7 +1745,7 @@ def build_codex_monthly_view(entries, *, now_utc, tz_name=None):
|
|
|
1745
1745
|
|
|
1746
1746
|
|
|
1747
1747
|
def build_codex_weekly_view(entries, *, now_utc, tz_name=None,
|
|
1748
|
-
week_start_idx=0):
|
|
1748
|
+
week_start_idx=0, speed="standard"):
|
|
1749
1749
|
"""Build a ``CodexWeeklyView`` from a list of ``CodexEntry`` (issue #58).
|
|
1750
1750
|
|
|
1751
1751
|
``week_start_idx`` is the resolved Mon=0..Sun=6 index the caller
|
|
@@ -1754,7 +1754,7 @@ def build_codex_weekly_view(entries, *, now_utc, tz_name=None,
|
|
|
1754
1754
|
timezone (matches ``_aggregate_codex_weekly`` contract).
|
|
1755
1755
|
"""
|
|
1756
1756
|
_agg = _load_lib("_lib_aggregators")
|
|
1757
|
-
buckets = _agg._aggregate_codex_weekly(entries, tz_name, week_start_idx)
|
|
1757
|
+
buckets = _agg._aggregate_codex_weekly(entries, tz_name, week_start_idx, speed=speed)
|
|
1758
1758
|
total_cost, total_tok = _codex_bucket_totals(buckets)
|
|
1759
1759
|
return CodexWeeklyView(
|
|
1760
1760
|
rows=tuple(buckets),
|
|
@@ -1766,7 +1766,7 @@ def build_codex_weekly_view(entries, *, now_utc, tz_name=None,
|
|
|
1766
1766
|
)
|
|
1767
1767
|
|
|
1768
1768
|
|
|
1769
|
-
def build_codex_session_view(entries, *, now_utc, tz_name=None):
|
|
1769
|
+
def build_codex_session_view(entries, *, now_utc, tz_name=None, speed="standard"):
|
|
1770
1770
|
"""Build a ``CodexSessionView`` from a list of ``CodexEntry`` (issue #58).
|
|
1771
1771
|
|
|
1772
1772
|
``rows`` order mirrors the aggregator: descending by
|
|
@@ -1779,7 +1779,7 @@ def build_codex_session_view(entries, *, now_utc, tz_name=None):
|
|
|
1779
1779
|
aggregator only tracks ``last`` per session). ``None`` on empty.
|
|
1780
1780
|
"""
|
|
1781
1781
|
_agg = _load_lib("_lib_aggregators")
|
|
1782
|
-
sessions = _agg._aggregate_codex_sessions(entries)
|
|
1782
|
+
sessions = _agg._aggregate_codex_sessions(entries, speed=speed)
|
|
1783
1783
|
total_cost = 0.0
|
|
1784
1784
|
total_tok = 0
|
|
1785
1785
|
earliest = None
|
package/bin/cctally
CHANGED
|
@@ -268,6 +268,10 @@ _resolve_model_pricing = _lib_pricing._resolve_model_pricing
|
|
|
268
268
|
_calculate_entry_cost = _lib_pricing._calculate_entry_cost
|
|
269
269
|
_warn_unknown_codex_model = _lib_pricing._warn_unknown_codex_model
|
|
270
270
|
_calculate_codex_entry_cost = _lib_pricing._calculate_codex_entry_cost
|
|
271
|
+
_codex_fast_multiplier = _lib_pricing._codex_fast_multiplier
|
|
272
|
+
CODEX_FAST_MULTIPLIER_OVERRIDES = _lib_pricing.CODEX_FAST_MULTIPLIER_OVERRIDES
|
|
273
|
+
CODEX_FAST_MULTIPLIER_FALLBACK = _lib_pricing.CODEX_FAST_MULTIPLIER_FALLBACK
|
|
274
|
+
_codex_config_requests_fast_service_tier = _lib_pricing._codex_config_requests_fast_service_tier
|
|
271
275
|
_short_model_name = _lib_pricing._short_model_name
|
|
272
276
|
|
|
273
277
|
_lib_display_tz = _load_sibling("_lib_display_tz")
|
|
@@ -1383,7 +1387,7 @@ class _CodexCostStats:
|
|
|
1383
1387
|
samples: list = field(default_factory=list)
|
|
1384
1388
|
|
|
1385
1389
|
|
|
1386
|
-
def _compute_codex_cost_stats(entries):
|
|
1390
|
+
def _compute_codex_cost_stats(entries, speed: str = "standard"):
|
|
1387
1391
|
"""Walk ``entries: Iterable[CodexEntry]`` and compute the totals +
|
|
1388
1392
|
per-entry computed-cost samples that ``_render_codex_cost_report``
|
|
1389
1393
|
consumes (issue #92).
|
|
@@ -1414,6 +1418,7 @@ def _compute_codex_cost_stats(entries):
|
|
|
1414
1418
|
entry.cached_input_tokens,
|
|
1415
1419
|
entry.output_tokens,
|
|
1416
1420
|
entry.reasoning_output_tokens,
|
|
1421
|
+
speed=speed,
|
|
1417
1422
|
)
|
|
1418
1423
|
stats.total_cost += cost
|
|
1419
1424
|
stats.samples.append(_CodexCostSample(
|
|
@@ -1500,6 +1505,7 @@ def _emit_codex_debug_samples_if_set(
|
|
|
1500
1505
|
entries,
|
|
1501
1506
|
*,
|
|
1502
1507
|
command_label: str,
|
|
1508
|
+
speed: str = "standard",
|
|
1503
1509
|
) -> None:
|
|
1504
1510
|
"""Emit the codex --debug report once per process when ``args.debug``
|
|
1505
1511
|
is True (issue #92).
|
|
@@ -1517,7 +1523,7 @@ def _emit_codex_debug_samples_if_set(
|
|
|
1517
1523
|
if not getattr(args, "debug", False):
|
|
1518
1524
|
return
|
|
1519
1525
|
sample_limit = int(getattr(args, "debug_samples", 5))
|
|
1520
|
-
stats = _compute_codex_cost_stats(entries)
|
|
1526
|
+
stats = _compute_codex_cost_stats(entries, speed=speed)
|
|
1521
1527
|
stats.command_label = command_label
|
|
1522
1528
|
for line in _render_codex_cost_report(stats, sample_limit):
|
|
1523
1529
|
eprint(line)
|
|
@@ -4804,6 +4810,32 @@ def cmd_weekly(args: argparse.Namespace) -> int:
|
|
|
4804
4810
|
return 0
|
|
4805
4811
|
|
|
4806
4812
|
|
|
4813
|
+
def _detect_codex_fast_service_tier() -> bool:
|
|
4814
|
+
"""True iff ``~/.codex/config.toml`` requests the fast/priority tier.
|
|
4815
|
+
|
|
4816
|
+
Reads from ``~/.codex`` only (single root; ``$CODEX_HOME`` multi-root is
|
|
4817
|
+
deferred — see #108). Tolerates an absent/unreadable config (→ False →
|
|
4818
|
+
standard tier).
|
|
4819
|
+
"""
|
|
4820
|
+
cfg = pathlib.Path.home() / ".codex" / "config.toml"
|
|
4821
|
+
try:
|
|
4822
|
+
content = cfg.read_text(encoding="utf-8", errors="replace")
|
|
4823
|
+
except OSError:
|
|
4824
|
+
return False
|
|
4825
|
+
return _codex_config_requests_fast_service_tier(content)
|
|
4826
|
+
|
|
4827
|
+
|
|
4828
|
+
def _resolve_codex_speed(requested: str) -> str:
|
|
4829
|
+
"""Resolve a ``--speed`` value to an effective tier.
|
|
4830
|
+
|
|
4831
|
+
``auto`` → ``fast`` iff ``~/.codex/config.toml`` requests it, else
|
|
4832
|
+
``standard``. ``fast``/``standard`` pass through unchanged.
|
|
4833
|
+
"""
|
|
4834
|
+
if requested == "auto":
|
|
4835
|
+
return "fast" if _detect_codex_fast_service_tier() else "standard"
|
|
4836
|
+
return requested
|
|
4837
|
+
|
|
4838
|
+
|
|
4807
4839
|
def cmd_codex_daily(args: argparse.Namespace) -> int:
|
|
4808
4840
|
"""Show Codex usage report grouped by date (display tz, --tz, or --timezone)."""
|
|
4809
4841
|
config = load_config()
|
|
@@ -4823,13 +4855,14 @@ def cmd_codex_daily(args: argparse.Namespace) -> int:
|
|
|
4823
4855
|
range_start, range_end = range
|
|
4824
4856
|
|
|
4825
4857
|
entries = get_codex_entries(range_start, range_end)
|
|
4826
|
-
|
|
4858
|
+
speed = _resolve_codex_speed(args.speed)
|
|
4859
|
+
_emit_codex_debug_samples_if_set(args, entries, command_label="codex-daily", speed=speed)
|
|
4827
4860
|
# Route through ``build_codex_daily_view`` (issue #58). The View
|
|
4828
4861
|
# wraps ``_aggregate_codex_daily`` without changing it — preserves
|
|
4829
4862
|
# LiteLLM token semantics, intentional dedup vs upstream, and
|
|
4830
4863
|
# ``CODEX_LEGACY_FALLBACK_MODEL`` warning end-to-end.
|
|
4831
4864
|
view = build_codex_daily_view(
|
|
4832
|
-
entries, now_utc=_command_as_of(), tz_name=tz_name,
|
|
4865
|
+
entries, now_utc=_command_as_of(), tz_name=tz_name, speed=speed,
|
|
4833
4866
|
)
|
|
4834
4867
|
days = list(view.rows) # asc — matches aggregator default
|
|
4835
4868
|
if args.order == "desc":
|
|
@@ -4883,10 +4916,11 @@ def cmd_codex_monthly(args: argparse.Namespace) -> int:
|
|
|
4883
4916
|
range_start, range_end = range
|
|
4884
4917
|
|
|
4885
4918
|
entries = get_codex_entries(range_start, range_end)
|
|
4886
|
-
|
|
4919
|
+
speed = _resolve_codex_speed(args.speed)
|
|
4920
|
+
_emit_codex_debug_samples_if_set(args, entries, command_label="codex-monthly", speed=speed)
|
|
4887
4921
|
# Route through ``build_codex_monthly_view`` (issue #58).
|
|
4888
4922
|
view = build_codex_monthly_view(
|
|
4889
|
-
entries, now_utc=_command_as_of(), tz_name=tz_name,
|
|
4923
|
+
entries, now_utc=_command_as_of(), tz_name=tz_name, speed=speed,
|
|
4890
4924
|
)
|
|
4891
4925
|
months = list(view.rows)
|
|
4892
4926
|
if args.order == "desc":
|
|
@@ -4943,11 +4977,12 @@ def cmd_codex_weekly(args: argparse.Namespace) -> int:
|
|
|
4943
4977
|
week_start_idx = WEEKDAY_MAP[week_start_name]
|
|
4944
4978
|
|
|
4945
4979
|
entries = get_codex_entries(range_start, range_end)
|
|
4946
|
-
|
|
4980
|
+
speed = _resolve_codex_speed(args.speed)
|
|
4981
|
+
_emit_codex_debug_samples_if_set(args, entries, command_label="codex-weekly", speed=speed)
|
|
4947
4982
|
# Route through ``build_codex_weekly_view`` (issue #58).
|
|
4948
4983
|
view = build_codex_weekly_view(
|
|
4949
4984
|
entries, now_utc=now_utc, tz_name=tz_name,
|
|
4950
|
-
week_start_idx=week_start_idx,
|
|
4985
|
+
week_start_idx=week_start_idx, speed=speed,
|
|
4951
4986
|
)
|
|
4952
4987
|
weeks = list(view.rows)
|
|
4953
4988
|
if args.order == "desc":
|
|
@@ -5004,12 +5039,13 @@ def cmd_codex_session(args: argparse.Namespace) -> int:
|
|
|
5004
5039
|
range_start, range_end = range
|
|
5005
5040
|
|
|
5006
5041
|
entries = get_codex_entries(range_start, range_end)
|
|
5007
|
-
|
|
5042
|
+
speed = _resolve_codex_speed(args.speed)
|
|
5043
|
+
_emit_codex_debug_samples_if_set(args, entries, command_label="codex-session", speed=speed)
|
|
5008
5044
|
# Route through ``build_codex_session_view`` (issue #58). View rows
|
|
5009
5045
|
# come descending by last_activity (aggregator default + upstream
|
|
5010
5046
|
# parity); --order asc reverses.
|
|
5011
5047
|
view = build_codex_session_view(
|
|
5012
|
-
entries, now_utc=_command_as_of(), tz_name=tz_name,
|
|
5048
|
+
entries, now_utc=_command_as_of(), tz_name=tz_name, speed=speed,
|
|
5013
5049
|
)
|
|
5014
5050
|
sessions = list(view.rows)
|
|
5015
5051
|
if args.order == "asc":
|
|
@@ -9959,6 +9995,12 @@ def _add_codex_shared_args(parser: argparse.ArgumentParser) -> None:
|
|
|
9959
9995
|
"-O", "--offline", action=argparse.BooleanOptionalAction, default=False,
|
|
9960
9996
|
help="Accepted for drop-in compat with ccusage-codex; we are always offline.",
|
|
9961
9997
|
)
|
|
9998
|
+
parser.add_argument(
|
|
9999
|
+
"--speed", choices=("auto", "standard", "fast"), default="auto",
|
|
10000
|
+
help="Codex pricing tier. auto (default) reads service_tier from "
|
|
10001
|
+
"~/.codex/config.toml (fast|priority -> fast pricing); fast "
|
|
10002
|
+
"forces the fast-tier multiplier; standard forces base pricing.",
|
|
10003
|
+
)
|
|
9962
10004
|
parser.add_argument(
|
|
9963
10005
|
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
9964
10006
|
help="Display timezone: local, utc, or IANA name. Overrides "
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.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": {
|