cctally 1.14.0 → 1.16.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 +13 -0
- package/README.md +2 -2
- package/bin/_cctally_db.py +12 -0
- package/bin/_lib_aggregators.py +2 -1
- package/bin/_lib_view_models.py +8 -8
- package/bin/cctally +587 -398
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.16.0] - 2026-05-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`-m/--mode {auto,calculate,display}` cost-source selector on `cctally daily`, `monthly`, `weekly`, `session`, and `blocks`** — a drop-in for `ccusage <cmd> --mode`. `auto` (the default) uses the recorded `costUSD` from JSONL when present and otherwise computes from embedded pricing; `calculate` always computes from pricing, ignoring any recorded `costUSD`; `display` shows the recorded `costUSD` only and renders `$0.00` when an entry has none (ccusage-faithful — and because most modern Claude Code JSONL omits `costUSD`, `display` reports `$0` for nearly everything). On `blocks` the mode is honored on both the main grouping and the active canonical-swapped block. `cctally five-hour-blocks` also accepts `--mode` but as a documented no-op, since its cost is the authoritative per-block value materialized at record-time. The default-`auto` output is byte-identical to before for `daily`, `monthly`, `weekly`, and `blocks`, and no `mode` key is added to any JSON shape (see the Changed note below for the one `session` exception). (#86)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- **`cctally session` (and the TUI session views) now prefer a session's recorded `costUSD` over a recomputed cost by default, for the historical sessions whose JSONL still carries it.** `session` was the one report that always recomputed cost from embedded pricing and ignored any recorded `costUSD`, even though `daily`, `monthly`, `weekly`, and the dashboard already preferred the recorded value — so on `costUSD`-bearing windows `cctally session` totals silently disagreed with `cctally daily`. Wiring the new `--mode auto` default through the session aggregator closes that gap: default totals now match the other reports (and the shipped reconcile invariant `Σ session.totalCost == Σ daily.totalCost`). Only the ~3.9% of historical files that still carry `costUSD` are affected (modern Claude Code omits it); everything else is unchanged. The TUI session panel/detail follow the same default and have no `--mode` flag; pass `cctally session --mode calculate` on the CLI to force the previous always-recompute behavior. (#86)
|
|
15
|
+
|
|
16
|
+
## [1.15.0] - 2026-05-26
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **`cctally claude <cmd>` and `cctally codex <cmd>` subgroup commands** let you paste ccusage's hierarchical syntax verbatim — `cctally claude {daily,monthly,weekly,session,blocks}` is a drop-in for `ccusage claude <cmd>`, and `cctally codex {daily,monthly,session,weekly}` for `ccusage codex <cmd>` (`codex weekly` is a cctally extension; upstream has none). Each subgroup leaf routes to the exact same engine as its flat form, so the table / `--json` / exit code are byte-identical — only `--help` adds a one-line alias/canonical cross-reference. The flat forms (`cctally daily`, `cctally codex-daily`, …) remain fully supported as back-compat aliases, with no deprecation warning. (#86)
|
|
20
|
+
|
|
8
21
|
## [1.14.0] - 2026-05-26
|
|
9
22
|
|
|
10
23
|
### Added
|
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ For status-line integration, alerts, and configuration, see [docs/installation.m
|
|
|
113
113
|
|
|
114
114
|
## What cctally adds
|
|
115
115
|
|
|
116
|
-
`cctally` started as a local-first replacement for [`ccusage`](https://github.com/ryoppippi/ccusage), and it stays compatible at the level of common CLI flows (`daily`, `monthly`, `weekly`, `session`, `blocks`). Beyond that, it adds:
|
|
116
|
+
`cctally` started as a local-first replacement for [`ccusage`](https://github.com/ryoppippi/ccusage), and it stays compatible at the level of common CLI flows (`daily`, `monthly`, `weekly`, `session`, `blocks`). Paste from ccusage verbatim: `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. Beyond that, it adds:
|
|
117
117
|
|
|
118
118
|
- **Live web dashboard.** Nine-panel SSE-driven view at `localhost:8789` (current week, forecast, trend, sessions, weekly, monthly, blocks, daily, recent alerts), with per-panel modals, a mobile layout, threshold alerts, and a settings drawer.
|
|
119
119
|
- **TUI live mode.** The same data inside your terminal (`cctally tui`; requires the optional `rich` package).
|
|
@@ -123,7 +123,7 @@ For status-line integration, alerts, and configuration, see [docs/installation.m
|
|
|
123
123
|
- **5-hour block analytics.** Per-block usage with model and project breakdowns (`cctally five-hour-blocks --breakdown=model`).
|
|
124
124
|
- **Time-window diff.** Compare two windows with model and project decomposition (`cctally diff`).
|
|
125
125
|
- **Project rollup.** Usage by Git project (`cctally project`).
|
|
126
|
-
- **Codex parity.**
|
|
126
|
+
- **Codex parity.** `cctally codex daily / monthly / session` are drop-ins for `ccusage codex daily / monthly / session`; the flat `codex-*` forms (drop-ins for the standalone `ccusage-codex` binary) remain as aliases, plus an added `cctally codex weekly` / `cctally codex-weekly` rollup (upstream has no `codex weekly`).
|
|
127
127
|
- **Persistent SQLite.** Week-over-week comparisons survive across runs.
|
|
128
128
|
|
|
129
129
|
**On speed.** Pricing is embedded and computed at query time from a delta-tail SQLite cache (`~/.local/share/cctally/cache.db`), with no shell-outs. First-table latency 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]
|
package/bin/_cctally_db.py
CHANGED
|
@@ -2371,6 +2371,10 @@ def _recompute_banner_should_emit(
|
|
|
2371
2371
|
confuses scripted pipelines. Banner still lands on the
|
|
2372
2372
|
next interactive non-report command (``report``,
|
|
2373
2373
|
``weekly``, ``percent-breakdown``, etc.) once on upgrade.
|
|
2374
|
+
Subgroup forms (``cctally claude/codex <cmd>``, issue #86
|
|
2375
|
+
Session B) carry the source group in ``argv[1]`` and the
|
|
2376
|
+
leaf in ``argv[2]``; we resolve the leaf so suppression is
|
|
2377
|
+
byte-identical to the flat alias.
|
|
2374
2378
|
|
|
2375
2379
|
Returns True iff the banner should be printed. Defensive: any
|
|
2376
2380
|
error reading ``sys.argv`` falls back to "don't print" — silence
|
|
@@ -2388,6 +2392,14 @@ def _recompute_banner_should_emit(
|
|
|
2388
2392
|
return False
|
|
2389
2393
|
try:
|
|
2390
2394
|
argv1 = sys.argv[1] if len(sys.argv) > 1 else None
|
|
2395
|
+
# Subgroup forms (`cctally claude <cmd>` / `cctally codex <cmd>`) carry
|
|
2396
|
+
# the source group in argv[1]; the suppression key is the leaf command
|
|
2397
|
+
# in argv[2]. Resolve it so the recompute banner suppresses identically
|
|
2398
|
+
# to the flat alias (issue #86 Session B; matches the args.command leaf
|
|
2399
|
+
# resolution used by the error-sentinel banner). Purely additive — flat
|
|
2400
|
+
# invocations (argv1 not in {claude,codex}) are byte-identical to before.
|
|
2401
|
+
if argv1 in ("claude", "codex") and len(sys.argv) > 2:
|
|
2402
|
+
argv1 = sys.argv[2]
|
|
2391
2403
|
except Exception:
|
|
2392
2404
|
argv1 = None
|
|
2393
2405
|
if argv1 in _BANNER_SUPPRESSED_COMMANDS:
|
package/bin/_lib_aggregators.py
CHANGED
|
@@ -608,6 +608,7 @@ def _aggregate_codex_sessions(entries: list[CodexEntry]) -> list[CodexSessionUsa
|
|
|
608
608
|
|
|
609
609
|
def _aggregate_claude_sessions(
|
|
610
610
|
entries: list["_JoinedClaudeEntry"],
|
|
611
|
+
mode: str = "auto",
|
|
611
612
|
) -> list[ClaudeSessionUsage]:
|
|
612
613
|
"""Group entries by session_id, collapsing resumed-across-files sessions.
|
|
613
614
|
|
|
@@ -666,7 +667,7 @@ def _aggregate_claude_sessions(
|
|
|
666
667
|
"cache_creation_input_tokens": entry.cache_creation_tokens,
|
|
667
668
|
"cache_read_input_tokens": entry.cache_read_tokens,
|
|
668
669
|
}
|
|
669
|
-
cost = _calculate_entry_cost(entry.model, usage)
|
|
670
|
+
cost = _calculate_entry_cost(entry.model, usage, mode=mode, cost_usd=entry.cost_usd)
|
|
670
671
|
|
|
671
672
|
sess["input"] += entry.input_tokens
|
|
672
673
|
sess["cache_create"] += entry.cache_creation_tokens
|
package/bin/_lib_view_models.py
CHANGED
|
@@ -318,7 +318,7 @@ class DailyView:
|
|
|
318
318
|
display_tz_label: str = ""
|
|
319
319
|
|
|
320
320
|
|
|
321
|
-
def build_daily_view(entries, *, now_utc, display_tz=None):
|
|
321
|
+
def build_daily_view(entries, *, now_utc, display_tz=None, mode="auto"):
|
|
322
322
|
"""Build a ``DailyView`` from raw ``UsageEntry`` list (spec §5.1).
|
|
323
323
|
|
|
324
324
|
Gap-free: only days with entries appear in ``view.rows`` /
|
|
@@ -336,7 +336,7 @@ def build_daily_view(entries, *, now_utc, display_tz=None):
|
|
|
336
336
|
share consumers ignore them and read ``view.aggregated`` instead.
|
|
337
337
|
"""
|
|
338
338
|
_agg = _load_lib("_lib_aggregators")
|
|
339
|
-
buckets = _agg._aggregate_daily(entries, mode=
|
|
339
|
+
buckets = _agg._aggregate_daily(entries, mode=mode, tz=display_tz)
|
|
340
340
|
if not buckets:
|
|
341
341
|
return DailyView(
|
|
342
342
|
rows=(),
|
|
@@ -427,7 +427,7 @@ class MonthlyView:
|
|
|
427
427
|
display_tz_label: str = ""
|
|
428
428
|
|
|
429
429
|
|
|
430
|
-
def build_monthly_view(entries, *, now_utc, n=12, display_tz=None):
|
|
430
|
+
def build_monthly_view(entries, *, now_utc, n=12, display_tz=None, mode="auto"):
|
|
431
431
|
"""Build a ``MonthlyView`` for the trailing ``n`` calendar months
|
|
432
432
|
(spec §5.2).
|
|
433
433
|
|
|
@@ -440,7 +440,7 @@ def build_monthly_view(entries, *, now_utc, n=12, display_tz=None):
|
|
|
440
440
|
CLI table footer would.
|
|
441
441
|
"""
|
|
442
442
|
_agg = _load_lib("_lib_aggregators")
|
|
443
|
-
buckets = _agg._aggregate_monthly(entries, mode=
|
|
443
|
+
buckets = _agg._aggregate_monthly(entries, mode=mode, tz=display_tz)
|
|
444
444
|
if not buckets:
|
|
445
445
|
return MonthlyView(
|
|
446
446
|
rows=(), aggregated=(),
|
|
@@ -528,7 +528,7 @@ class WeeklyView:
|
|
|
528
528
|
|
|
529
529
|
|
|
530
530
|
def build_weekly_view(conn, entries, *, weeks, now_utc, display_tz=None,
|
|
531
|
-
as_of_utc=None):
|
|
531
|
+
as_of_utc=None, mode="auto"):
|
|
532
532
|
"""Build a ``WeeklyView`` from subscription-week boundaries
|
|
533
533
|
(spec §5.3).
|
|
534
534
|
|
|
@@ -548,7 +548,7 @@ def build_weekly_view(conn, entries, *, weeks, now_utc, display_tz=None,
|
|
|
548
548
|
"""
|
|
549
549
|
_agg = _load_lib("_lib_aggregators")
|
|
550
550
|
_cct_core = _load_lib("_cctally_core")
|
|
551
|
-
buckets_asc = _agg._aggregate_weekly(entries, weeks)
|
|
551
|
+
buckets_asc = _agg._aggregate_weekly(entries, weeks, mode=mode)
|
|
552
552
|
if not buckets_asc:
|
|
553
553
|
return WeeklyView(
|
|
554
554
|
rows=(), aggregated=(), overlay=(),
|
|
@@ -1022,7 +1022,7 @@ class SessionsView:
|
|
|
1022
1022
|
display_tz_label: str = ""
|
|
1023
1023
|
|
|
1024
1024
|
|
|
1025
|
-
def build_sessions_view(entries, *, now_utc, limit=None, display_tz=None):
|
|
1025
|
+
def build_sessions_view(entries, *, now_utc, limit=None, display_tz=None, mode="auto"):
|
|
1026
1026
|
"""Build a ``SessionsView`` from joined Claude session entries
|
|
1027
1027
|
(spec §5.5).
|
|
1028
1028
|
|
|
@@ -1053,7 +1053,7 @@ def build_sessions_view(entries, *, now_utc, limit=None, display_tz=None):
|
|
|
1053
1053
|
"""
|
|
1054
1054
|
import os as _os # late: keep top-level imports lean.
|
|
1055
1055
|
_agg = _load_lib("_lib_aggregators")
|
|
1056
|
-
aggregated = _agg._aggregate_claude_sessions(entries)
|
|
1056
|
+
aggregated = _agg._aggregate_claude_sessions(entries, mode=mode)
|
|
1057
1057
|
# Apply limit truncation up front so `rows` and `aggregated` stay
|
|
1058
1058
|
# in lockstep (spec §4.3 invariant: `total_sessions == len(rows)
|
|
1059
1059
|
# == len(aggregated)`). limit=None → keep everything.
|
package/bin/cctally
CHANGED
|
@@ -4238,7 +4238,7 @@ def cmd_blocks(args: argparse.Namespace) -> int:
|
|
|
4238
4238
|
range_start=range_start,
|
|
4239
4239
|
range_end=range_end,
|
|
4240
4240
|
display_tz=tz,
|
|
4241
|
-
mode=
|
|
4241
|
+
mode=args.mode,
|
|
4242
4242
|
skip_rows=True,
|
|
4243
4243
|
)
|
|
4244
4244
|
blocks = list(view.aggregated)
|
|
@@ -4263,7 +4263,7 @@ def cmd_blocks(args: argparse.Namespace) -> int:
|
|
|
4263
4263
|
# additional entries. Without re-aggregation the displayed window
|
|
4264
4264
|
# said one thing and the cost said another (live data: window
|
|
4265
4265
|
# 20:50→01:50 with $45 cost vs the real $128).
|
|
4266
|
-
_maybe_swap_active_block_to_canonical(blocks, all_entries, now=now_utc)
|
|
4266
|
+
_maybe_swap_active_block_to_canonical(blocks, all_entries, now=now_utc, mode=args.mode)
|
|
4267
4267
|
|
|
4268
4268
|
if args.json:
|
|
4269
4269
|
print(_blocks_to_json(blocks))
|
|
@@ -4284,6 +4284,7 @@ def _maybe_swap_active_block_to_canonical(
|
|
|
4284
4284
|
all_entries: list[Any],
|
|
4285
4285
|
*,
|
|
4286
4286
|
now: dt.datetime,
|
|
4287
|
+
mode: str = "auto",
|
|
4287
4288
|
) -> None:
|
|
4288
4289
|
"""In-place swap of an ACTIVE heuristic block to its API-anchored
|
|
4289
4290
|
canonical window — timestamps AND token/cost totals.
|
|
@@ -4359,10 +4360,10 @@ def _maybe_swap_active_block_to_canonical(
|
|
|
4359
4360
|
if block_end_utc <= now.astimezone(dt.timezone.utc):
|
|
4360
4361
|
return
|
|
4361
4362
|
# Re-aggregate entries over the canonical interval. Build a fresh
|
|
4362
|
-
# Block via ``_build_activity_block``
|
|
4363
|
-
#
|
|
4364
|
-
#
|
|
4365
|
-
# the
|
|
4363
|
+
# Block via ``_build_activity_block`` so every total stays in one code
|
|
4364
|
+
# path — no field-by-field assignment that could drift if the dataclass
|
|
4365
|
+
# grows new fields. Thread the caller's ``mode`` so the active block's
|
|
4366
|
+
# cost honors --mode like the main grouping (Session C / Codex F1).
|
|
4366
4367
|
canonical_entries = [
|
|
4367
4368
|
e for e in all_entries
|
|
4368
4369
|
if block_start_utc <= e.timestamp < block_end_utc
|
|
@@ -4372,7 +4373,7 @@ def _maybe_swap_active_block_to_canonical(
|
|
|
4372
4373
|
block_start_utc,
|
|
4373
4374
|
block_end_utc,
|
|
4374
4375
|
now.astimezone(dt.timezone.utc),
|
|
4375
|
-
|
|
4376
|
+
mode,
|
|
4376
4377
|
anchor="recorded",
|
|
4377
4378
|
)
|
|
4378
4379
|
blocks[active_idx] = rebuilt
|
|
@@ -4541,7 +4542,7 @@ def cmd_daily(args: argparse.Namespace) -> int:
|
|
|
4541
4542
|
# fields live on BucketUsage, not on DailyPanelRow. The builder's
|
|
4542
4543
|
# `_aggregate_daily` call is the same one we used inline.
|
|
4543
4544
|
view = build_daily_view(all_entries, now_utc=_command_as_of(),
|
|
4544
|
-
display_tz=tz)
|
|
4545
|
+
display_tz=tz, mode=args.mode)
|
|
4545
4546
|
# `_aggregate_daily` returned ascending order; build_daily_view stores
|
|
4546
4547
|
# `aggregated` newest-first. CLI's default order is ascending, so
|
|
4547
4548
|
# re-reverse to match the prior on-the-wire shape.
|
|
@@ -4628,7 +4629,7 @@ def cmd_monthly(args: argparse.Namespace) -> int:
|
|
|
4628
4629
|
# Pass a large `n` so the CLI's `--since`/`--until` window controls
|
|
4629
4630
|
# how many months render (the dashboard caps at n=12; CLI doesn't).
|
|
4630
4631
|
view = build_monthly_view(all_entries, now_utc=_command_as_of(),
|
|
4631
|
-
n=10**6, display_tz=tz)
|
|
4632
|
+
n=10**6, display_tz=tz, mode=args.mode)
|
|
4632
4633
|
# The view stores `aggregated` newest-first; CLI default is asc.
|
|
4633
4634
|
months = list(reversed(view.aggregated))
|
|
4634
4635
|
|
|
@@ -4745,7 +4746,7 @@ def cmd_weekly(args: argparse.Namespace) -> int:
|
|
|
4745
4746
|
# assertions stay aligned.
|
|
4746
4747
|
view = build_weekly_view(
|
|
4747
4748
|
conn, all_entries, weeks=weeks, now_utc=now_utc,
|
|
4748
|
-
display_tz=args._resolved_tz, as_of_utc=as_of_utc,
|
|
4749
|
+
display_tz=args._resolved_tz, as_of_utc=as_of_utc, mode=args.mode,
|
|
4749
4750
|
)
|
|
4750
4751
|
buckets = list(reversed(view.aggregated))
|
|
4751
4752
|
overlay = list(reversed(view.overlay))
|
|
@@ -5973,6 +5974,7 @@ def cmd_session(args: argparse.Namespace) -> int:
|
|
|
5973
5974
|
# files collapses to ONE entry in BOTH tuples).
|
|
5974
5975
|
view = build_sessions_view(
|
|
5975
5976
|
entries, now_utc=_command_as_of(), limit=None, display_tz=tz,
|
|
5977
|
+
mode=args.mode,
|
|
5976
5978
|
)
|
|
5977
5979
|
sessions = list(view.aggregated)
|
|
5978
5980
|
|
|
@@ -9783,6 +9785,36 @@ def _argparse_has_arg(parser, option_string: str) -> bool:
|
|
|
9783
9785
|
return False
|
|
9784
9786
|
|
|
9785
9787
|
|
|
9788
|
+
def _add_mode_arg(parser, *, noop: bool = False) -> None:
|
|
9789
|
+
"""Add ccusage's -m/--mode {auto,calculate,display} cost-source flag.
|
|
9790
|
+
|
|
9791
|
+
Standalone (not folded into _add_ccusage_alias_args) so it lands only
|
|
9792
|
+
on the six Session-C reporting commands and never collides with
|
|
9793
|
+
range-cost, which defines its own -m/--mode.
|
|
9794
|
+
|
|
9795
|
+
noop=True (five-hour-blocks only): the flag is accepted for surface
|
|
9796
|
+
parity with `blocks` but does not alter numbers — that command's cost
|
|
9797
|
+
is the authoritative materialized five_hour_blocks.total_cost_usd
|
|
9798
|
+
computed at record-time (always auto semantics).
|
|
9799
|
+
"""
|
|
9800
|
+
help_real = (
|
|
9801
|
+
"Cost source: auto (recorded costUSD when present, else computed), "
|
|
9802
|
+
"calculate (always compute from embedded pricing), display "
|
|
9803
|
+
"(recorded costUSD only; $0 when absent). Default: auto."
|
|
9804
|
+
)
|
|
9805
|
+
help_noop = (
|
|
9806
|
+
"Accepted for ccusage drop-in compat; no-op here — five-hour-blocks "
|
|
9807
|
+
"cost is the authoritative materialized per-block value computed at "
|
|
9808
|
+
"record-time. Default: auto."
|
|
9809
|
+
)
|
|
9810
|
+
parser.add_argument(
|
|
9811
|
+
"-m", "--mode",
|
|
9812
|
+
default="auto",
|
|
9813
|
+
choices=["auto", "calculate", "display"],
|
|
9814
|
+
help=help_noop if noop else help_real,
|
|
9815
|
+
)
|
|
9816
|
+
|
|
9817
|
+
|
|
9786
9818
|
def _add_ccusage_alias_args(parser, *, ansi_emit: bool) -> None:
|
|
9787
9819
|
"""Attach the Session A ccusage alias surface to a Claude-cmd subparser.
|
|
9788
9820
|
|
|
@@ -9887,6 +9919,70 @@ def _add_ccusage_alias_args(parser, *, ansi_emit: bool) -> None:
|
|
|
9887
9919
|
)
|
|
9888
9920
|
|
|
9889
9921
|
|
|
9922
|
+
def _add_codex_shared_args(parser: argparse.ArgumentParser) -> None:
|
|
9923
|
+
"""Register upstream `ccusage-codex sharedArgs` on a codex subparser.
|
|
9924
|
+
|
|
9925
|
+
Upstream sharedArgs (node_modules/@ccusage/codex/dist/index.js):
|
|
9926
|
+
--timezone/-z, --locale/-l, --compact, --color, --noColor,
|
|
9927
|
+
--offline/--no-offline.
|
|
9928
|
+
|
|
9929
|
+
Honored here: --timezone (dates + aggregation buckets) and
|
|
9930
|
+
--compact (table layout). Accepted-but-no-op (stored on the
|
|
9931
|
+
namespace for drop-in parity with upstream scripts): --locale
|
|
9932
|
+
(we don't locale-format dates), --color / --noColor (we don't
|
|
9933
|
+
emit ANSI codes today). --offline is accepted as a no-op too
|
|
9934
|
+
(we are always offline); it uses BooleanOptionalAction so
|
|
9935
|
+
`--no-offline` also parses cleanly. `-O` is kept as the short
|
|
9936
|
+
form for offline for backward compat with earlier builds.
|
|
9937
|
+
"""
|
|
9938
|
+
parser.add_argument(
|
|
9939
|
+
"-z", "--timezone", default=None, metavar="TZ",
|
|
9940
|
+
help="IANA timezone for date bucketing and Date/Last Activity cells.",
|
|
9941
|
+
)
|
|
9942
|
+
parser.add_argument(
|
|
9943
|
+
"-l", "--locale", default=None, metavar="LOCALE",
|
|
9944
|
+
help="Accepted for drop-in compat; no-op (dates are not locale-formatted).",
|
|
9945
|
+
)
|
|
9946
|
+
parser.add_argument(
|
|
9947
|
+
"--compact", action="store_true",
|
|
9948
|
+
help="Force compact table layout regardless of terminal width.",
|
|
9949
|
+
)
|
|
9950
|
+
parser.add_argument(
|
|
9951
|
+
"--color", action="store_true",
|
|
9952
|
+
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
9953
|
+
)
|
|
9954
|
+
parser.add_argument(
|
|
9955
|
+
"--noColor", action="store_true", dest="no_color",
|
|
9956
|
+
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
9957
|
+
)
|
|
9958
|
+
parser.add_argument(
|
|
9959
|
+
"-O", "--offline", action=argparse.BooleanOptionalAction, default=False,
|
|
9960
|
+
help="Accepted for drop-in compat with ccusage-codex; we are always offline.",
|
|
9961
|
+
)
|
|
9962
|
+
parser.add_argument(
|
|
9963
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
9964
|
+
help="Display timezone: local, utc, or IANA name. Overrides "
|
|
9965
|
+
"config display.tz for this call. Takes precedence over "
|
|
9966
|
+
"upstream's --timezone for drop-in parity.",
|
|
9967
|
+
)
|
|
9968
|
+
# Issue #92: codex parity for the #89 --debug surface. Codex JSONL
|
|
9969
|
+
# has no recorded costUSD to diff against, so the report is the
|
|
9970
|
+
# codex variant ("Codex Pricing Debug Report": totals + top-N
|
|
9971
|
+
# highest computed-cost entries), wired via
|
|
9972
|
+
# _emit_codex_debug_samples_if_set in each cmd_codex_* body.
|
|
9973
|
+
parser.add_argument(
|
|
9974
|
+
"-d", "--debug", action="store_true",
|
|
9975
|
+
help="Emit a stderr 'Codex Pricing Debug Report' (totals + "
|
|
9976
|
+
"the N highest computed-cost sample entries).",
|
|
9977
|
+
)
|
|
9978
|
+
parser.add_argument(
|
|
9979
|
+
"--debug-samples", type=_nonneg_int, default=5, metavar="N",
|
|
9980
|
+
help="Cap on top-entry sample rows in the --debug report "
|
|
9981
|
+
"(default 5; N=0 suppresses the sample block; "
|
|
9982
|
+
"negatives rejected at parse time).",
|
|
9983
|
+
)
|
|
9984
|
+
|
|
9985
|
+
|
|
9890
9986
|
def _add_share_args(parser, *, has_status_line: bool = False) -> None:
|
|
9891
9987
|
"""Attach shareable-reports flags + format/json mutex to a subparser.
|
|
9892
9988
|
|
|
@@ -10015,6 +10111,399 @@ def _share_validate_args(args) -> None:
|
|
|
10015
10111
|
sys.exit(2)
|
|
10016
10112
|
|
|
10017
10113
|
|
|
10114
|
+
def _build_daily_parser(subparsers, name, *, help_text, xref):
|
|
10115
|
+
"""Build the `daily` leaf parser (issue #86 Session B; routes to cmd_daily).
|
|
10116
|
+
|
|
10117
|
+
Build-once, register-twice: this body is the verbatim former inline `daily`
|
|
10118
|
+
construction, parameterized only by `name`, the parent-list `help_text`, and
|
|
10119
|
+
the `xref` appended to `description` (renders on `cctally <name> --help`).
|
|
10120
|
+
"""
|
|
10121
|
+
p = subparsers.add_parser(
|
|
10122
|
+
name,
|
|
10123
|
+
help=help_text,
|
|
10124
|
+
formatter_class=CLIHelpFormatter,
|
|
10125
|
+
description="Show usage grouped by date, matching upstream ccusage daily output."
|
|
10126
|
+
"\n\n" + xref,
|
|
10127
|
+
epilog=textwrap.dedent("""\
|
|
10128
|
+
Examples:
|
|
10129
|
+
cctally daily --since 20260414
|
|
10130
|
+
cctally daily --since 20260410 --until 20260416
|
|
10131
|
+
cctally daily --since 20260414 --breakdown
|
|
10132
|
+
cctally daily --since 20260414 --json
|
|
10133
|
+
cctally daily --order desc
|
|
10134
|
+
"""),
|
|
10135
|
+
)
|
|
10136
|
+
p.add_argument(
|
|
10137
|
+
"-s", "--since",
|
|
10138
|
+
default=None,
|
|
10139
|
+
metavar="YYYYMMDD",
|
|
10140
|
+
help="Filter from date (inclusive).",
|
|
10141
|
+
)
|
|
10142
|
+
p.add_argument(
|
|
10143
|
+
"-u", "--until",
|
|
10144
|
+
default=None,
|
|
10145
|
+
metavar="YYYYMMDD",
|
|
10146
|
+
help="Filter until date (inclusive).",
|
|
10147
|
+
)
|
|
10148
|
+
p.add_argument(
|
|
10149
|
+
"-b", "--breakdown",
|
|
10150
|
+
action="store_true",
|
|
10151
|
+
help="Show per-model cost breakdown sub-rows.",
|
|
10152
|
+
)
|
|
10153
|
+
p.add_argument(
|
|
10154
|
+
"-o", "--order",
|
|
10155
|
+
choices=("asc", "desc"),
|
|
10156
|
+
default="asc",
|
|
10157
|
+
help="Sort direction by date (default: asc).",
|
|
10158
|
+
)
|
|
10159
|
+
p.add_argument(
|
|
10160
|
+
"--reveal-projects",
|
|
10161
|
+
action="store_true",
|
|
10162
|
+
dest="reveal_projects",
|
|
10163
|
+
help="In --format output, show real project basenames instead of "
|
|
10164
|
+
"the default project-1, project-2, ... anonymization.",
|
|
10165
|
+
)
|
|
10166
|
+
p.add_argument(
|
|
10167
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10168
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10169
|
+
"Overrides config display.tz for this call.",
|
|
10170
|
+
)
|
|
10171
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10172
|
+
_add_mode_arg(p)
|
|
10173
|
+
_add_share_args(p)
|
|
10174
|
+
p.set_defaults(func=cmd_daily)
|
|
10175
|
+
return p
|
|
10176
|
+
|
|
10177
|
+
|
|
10178
|
+
def _build_monthly_parser(subparsers, name, *, help_text, xref):
|
|
10179
|
+
"""Build the `monthly` leaf parser (issue #86 Session B; routes to cmd_monthly)."""
|
|
10180
|
+
p = subparsers.add_parser(
|
|
10181
|
+
name,
|
|
10182
|
+
help=help_text,
|
|
10183
|
+
formatter_class=CLIHelpFormatter,
|
|
10184
|
+
description="Show usage grouped by calendar month, matching upstream ccusage monthly output."
|
|
10185
|
+
"\n\n" + xref,
|
|
10186
|
+
epilog=textwrap.dedent("""\
|
|
10187
|
+
Examples:
|
|
10188
|
+
cctally monthly --since 20260101
|
|
10189
|
+
cctally monthly --since 20260101 --until 20260331
|
|
10190
|
+
cctally monthly --since 20260101 --breakdown
|
|
10191
|
+
cctally monthly --since 20260101 --json
|
|
10192
|
+
cctally monthly --order desc
|
|
10193
|
+
"""),
|
|
10194
|
+
)
|
|
10195
|
+
p.add_argument(
|
|
10196
|
+
"-s", "--since",
|
|
10197
|
+
default=None,
|
|
10198
|
+
metavar="YYYYMMDD",
|
|
10199
|
+
help="Filter from date (inclusive).",
|
|
10200
|
+
)
|
|
10201
|
+
p.add_argument(
|
|
10202
|
+
"-u", "--until",
|
|
10203
|
+
default=None,
|
|
10204
|
+
metavar="YYYYMMDD",
|
|
10205
|
+
help="Filter until date (inclusive).",
|
|
10206
|
+
)
|
|
10207
|
+
p.add_argument(
|
|
10208
|
+
"-b", "--breakdown",
|
|
10209
|
+
action="store_true",
|
|
10210
|
+
help="Show per-model cost breakdown sub-rows.",
|
|
10211
|
+
)
|
|
10212
|
+
p.add_argument(
|
|
10213
|
+
"-o", "--order",
|
|
10214
|
+
choices=("asc", "desc"),
|
|
10215
|
+
default="asc",
|
|
10216
|
+
help="Sort direction by month (default: asc).",
|
|
10217
|
+
)
|
|
10218
|
+
p.add_argument(
|
|
10219
|
+
"--reveal-projects",
|
|
10220
|
+
action="store_true",
|
|
10221
|
+
dest="reveal_projects",
|
|
10222
|
+
help="In --format output, show real project basenames instead of "
|
|
10223
|
+
"the default project-1, project-2, ... anonymization.",
|
|
10224
|
+
)
|
|
10225
|
+
p.add_argument(
|
|
10226
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10227
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10228
|
+
"Overrides config display.tz for this call.",
|
|
10229
|
+
)
|
|
10230
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10231
|
+
_add_mode_arg(p)
|
|
10232
|
+
_add_share_args(p)
|
|
10233
|
+
p.set_defaults(func=cmd_monthly)
|
|
10234
|
+
return p
|
|
10235
|
+
|
|
10236
|
+
|
|
10237
|
+
def _build_weekly_parser(subparsers, name, *, help_text, xref):
|
|
10238
|
+
"""Build the `weekly` leaf parser (issue #86 Session B; routes to cmd_weekly)."""
|
|
10239
|
+
p = subparsers.add_parser(
|
|
10240
|
+
name,
|
|
10241
|
+
help=help_text,
|
|
10242
|
+
formatter_class=CLIHelpFormatter,
|
|
10243
|
+
description="Show Claude usage grouped by subscription week. Boundaries are anchored "
|
|
10244
|
+
"to weekly_usage_snapshots.week_start_at with 7-day-cadence extrapolation "
|
|
10245
|
+
"for pre-snapshot history. Columns extend daily/monthly's set with Used % "
|
|
10246
|
+
"and $/1%."
|
|
10247
|
+
"\n\n" + xref,
|
|
10248
|
+
epilog=textwrap.dedent("""\
|
|
10249
|
+
Examples:
|
|
10250
|
+
cctally weekly
|
|
10251
|
+
cctally weekly --since 20260101
|
|
10252
|
+
cctally weekly --breakdown
|
|
10253
|
+
cctally weekly --json
|
|
10254
|
+
cctally weekly --order desc
|
|
10255
|
+
"""),
|
|
10256
|
+
)
|
|
10257
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYYMMDD",
|
|
10258
|
+
help="Filter from date (inclusive).")
|
|
10259
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYYMMDD",
|
|
10260
|
+
help="Filter until date (inclusive).")
|
|
10261
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10262
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10263
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10264
|
+
help="Sort direction by week (default: asc).")
|
|
10265
|
+
p.add_argument("--reveal-projects", action="store_true", dest="reveal_projects",
|
|
10266
|
+
help="In --format output, show real project basenames instead of "
|
|
10267
|
+
"the default project-1, project-2, ... anonymization.")
|
|
10268
|
+
p.add_argument("--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10269
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10270
|
+
"Overrides config display.tz for this call.")
|
|
10271
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10272
|
+
_add_mode_arg(p)
|
|
10273
|
+
_add_share_args(p)
|
|
10274
|
+
p.set_defaults(func=cmd_weekly)
|
|
10275
|
+
return p
|
|
10276
|
+
|
|
10277
|
+
|
|
10278
|
+
def _build_session_parser(subparsers, name, *, help_text, xref):
|
|
10279
|
+
"""Build the `session` leaf parser (issue #86 Session B; routes to cmd_session)."""
|
|
10280
|
+
p = subparsers.add_parser(
|
|
10281
|
+
name,
|
|
10282
|
+
help=help_text,
|
|
10283
|
+
formatter_class=CLIHelpFormatter,
|
|
10284
|
+
description="Show Claude usage grouped by JSONL sessionId. Resumed sessions (same "
|
|
10285
|
+
"sessionId across multiple files) collapse into one row. 11-column "
|
|
10286
|
+
"layout paralleling codex-session."
|
|
10287
|
+
"\n\n" + xref,
|
|
10288
|
+
epilog=textwrap.dedent("""\
|
|
10289
|
+
Examples:
|
|
10290
|
+
cctally session
|
|
10291
|
+
cctally session --since 20260401
|
|
10292
|
+
cctally session --since 20260401 --breakdown
|
|
10293
|
+
cctally session --json
|
|
10294
|
+
cctally session --order desc
|
|
10295
|
+
"""),
|
|
10296
|
+
)
|
|
10297
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYYMMDD",
|
|
10298
|
+
help="Filter from date (inclusive).")
|
|
10299
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYYMMDD",
|
|
10300
|
+
help="Filter until date (inclusive).")
|
|
10301
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10302
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10303
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10304
|
+
help="Sort direction by last activity (default: asc — earliest first).")
|
|
10305
|
+
p.add_argument("--reveal-projects", action="store_true", dest="reveal_projects",
|
|
10306
|
+
help="In --format output, show real project basenames instead of "
|
|
10307
|
+
"the default project-1, project-2, ... anonymization.")
|
|
10308
|
+
p.add_argument("--top-n", type=int, default=15, dest="top_n",
|
|
10309
|
+
metavar="N",
|
|
10310
|
+
help="In --format output, cap rows to top N by cost (default: 15). "
|
|
10311
|
+
"Must be >= 1; values above 50 emit a readability warning. "
|
|
10312
|
+
"Has no effect on terminal/JSON output.")
|
|
10313
|
+
p.add_argument("--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10314
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10315
|
+
"Overrides config display.tz for this call.")
|
|
10316
|
+
p.add_argument(
|
|
10317
|
+
"-i", "--id", default=None, metavar="SESSION_ID", dest="id",
|
|
10318
|
+
help="Filter to a single session by exact-string sessionId. "
|
|
10319
|
+
"Match is against the post-resume-merge id (sessions "
|
|
10320
|
+
"resumed across multiple JSONL files collapse to one id). "
|
|
10321
|
+
"Unknown id → exit 0 with the empty-render branch.",
|
|
10322
|
+
)
|
|
10323
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10324
|
+
_add_mode_arg(p)
|
|
10325
|
+
_add_share_args(p)
|
|
10326
|
+
p.set_defaults(func=cmd_session)
|
|
10327
|
+
return p
|
|
10328
|
+
|
|
10329
|
+
|
|
10330
|
+
def _build_blocks_parser(subparsers, name, *, help_text, xref):
|
|
10331
|
+
"""Build the `blocks` leaf parser (issue #86 Session B; routes to cmd_blocks).
|
|
10332
|
+
|
|
10333
|
+
Note: `blocks` intentionally has NO `_add_share_args` (matches the former
|
|
10334
|
+
inline block — it is not part of the shareable-output flag surface).
|
|
10335
|
+
"""
|
|
10336
|
+
p = subparsers.add_parser(
|
|
10337
|
+
name,
|
|
10338
|
+
help=help_text,
|
|
10339
|
+
formatter_class=CLIHelpFormatter,
|
|
10340
|
+
description="Show usage grouped by 5-hour session blocks, matching upstream ccusage blocks output."
|
|
10341
|
+
"\n\n" + xref,
|
|
10342
|
+
epilog=textwrap.dedent("""\
|
|
10343
|
+
Examples:
|
|
10344
|
+
cctally blocks --since 20260414
|
|
10345
|
+
cctally blocks --since 20260410 --until 20260416
|
|
10346
|
+
cctally blocks --since 20260414 --breakdown
|
|
10347
|
+
cctally blocks --since 20260414 --json
|
|
10348
|
+
"""),
|
|
10349
|
+
)
|
|
10350
|
+
p.add_argument(
|
|
10351
|
+
"-s", "--since",
|
|
10352
|
+
default=None,
|
|
10353
|
+
metavar="YYYYMMDD",
|
|
10354
|
+
help="Filter from date (inclusive).",
|
|
10355
|
+
)
|
|
10356
|
+
p.add_argument(
|
|
10357
|
+
"-u", "--until",
|
|
10358
|
+
default=None,
|
|
10359
|
+
metavar="YYYYMMDD",
|
|
10360
|
+
help="Filter until date (inclusive).",
|
|
10361
|
+
)
|
|
10362
|
+
p.add_argument(
|
|
10363
|
+
"-b", "--breakdown",
|
|
10364
|
+
action="store_true",
|
|
10365
|
+
help="Show per-model cost breakdown.",
|
|
10366
|
+
)
|
|
10367
|
+
p.add_argument(
|
|
10368
|
+
"--json",
|
|
10369
|
+
action="store_true",
|
|
10370
|
+
dest="json",
|
|
10371
|
+
help="Output JSON matching upstream ccusage blocks format.",
|
|
10372
|
+
)
|
|
10373
|
+
p.add_argument(
|
|
10374
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10375
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10376
|
+
"Overrides config display.tz for this call.",
|
|
10377
|
+
)
|
|
10378
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10379
|
+
_add_mode_arg(p)
|
|
10380
|
+
p.set_defaults(func=cmd_blocks)
|
|
10381
|
+
return p
|
|
10382
|
+
|
|
10383
|
+
|
|
10384
|
+
def _build_codex_daily_parser(subparsers, name, *, help_text, xref):
|
|
10385
|
+
"""Build the `codex-daily` leaf parser (issue #86 Session B; routes to cmd_codex_daily)."""
|
|
10386
|
+
p = subparsers.add_parser(
|
|
10387
|
+
name,
|
|
10388
|
+
help=help_text,
|
|
10389
|
+
formatter_class=CLIHelpFormatter,
|
|
10390
|
+
description="Show Codex usage grouped by date, matching upstream ccusage-codex daily output."
|
|
10391
|
+
"\n\n" + xref,
|
|
10392
|
+
epilog=textwrap.dedent("""\
|
|
10393
|
+
Examples:
|
|
10394
|
+
cctally codex-daily --since 20260401
|
|
10395
|
+
cctally codex-daily --since 20260401 --breakdown
|
|
10396
|
+
cctally codex-daily --since 20260401 --json
|
|
10397
|
+
cctally codex-daily --order desc
|
|
10398
|
+
"""),
|
|
10399
|
+
)
|
|
10400
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10401
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10402
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10403
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10404
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10405
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10406
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10407
|
+
help="Sort direction by date (default: asc).")
|
|
10408
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10409
|
+
help="Output JSON matching upstream ccusage-codex daily format.")
|
|
10410
|
+
_add_codex_shared_args(p)
|
|
10411
|
+
p.set_defaults(func=cmd_codex_daily)
|
|
10412
|
+
return p
|
|
10413
|
+
|
|
10414
|
+
|
|
10415
|
+
def _build_codex_monthly_parser(subparsers, name, *, help_text, xref):
|
|
10416
|
+
"""Build the `codex-monthly` leaf parser (issue #86 Session B; routes to cmd_codex_monthly)."""
|
|
10417
|
+
p = subparsers.add_parser(
|
|
10418
|
+
name,
|
|
10419
|
+
help=help_text,
|
|
10420
|
+
formatter_class=CLIHelpFormatter,
|
|
10421
|
+
description="Show Codex usage grouped by calendar month, matching upstream ccusage-codex monthly output."
|
|
10422
|
+
"\n\n" + xref,
|
|
10423
|
+
epilog=textwrap.dedent("""\
|
|
10424
|
+
Examples:
|
|
10425
|
+
cctally codex-monthly --since 20260101
|
|
10426
|
+
cctally codex-monthly --breakdown
|
|
10427
|
+
cctally codex-monthly --json
|
|
10428
|
+
"""),
|
|
10429
|
+
)
|
|
10430
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10431
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10432
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10433
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10434
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10435
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10436
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10437
|
+
help="Sort direction by month (default: asc).")
|
|
10438
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10439
|
+
help="Output JSON matching upstream ccusage-codex monthly format.")
|
|
10440
|
+
_add_codex_shared_args(p)
|
|
10441
|
+
p.set_defaults(func=cmd_codex_monthly)
|
|
10442
|
+
return p
|
|
10443
|
+
|
|
10444
|
+
|
|
10445
|
+
def _build_codex_weekly_parser(subparsers, name, *, help_text, xref):
|
|
10446
|
+
"""Build the `codex-weekly` leaf parser (issue #86 Session B; routes to cmd_codex_weekly)."""
|
|
10447
|
+
p = subparsers.add_parser(
|
|
10448
|
+
name,
|
|
10449
|
+
help=help_text,
|
|
10450
|
+
formatter_class=CLIHelpFormatter,
|
|
10451
|
+
description="Show Codex usage grouped by week. Week-start day is read from config.json "
|
|
10452
|
+
"(collector.week_start, Monday default). Not a ccusage-codex drop-in — "
|
|
10453
|
+
"upstream has no `codex weekly` command."
|
|
10454
|
+
"\n\n" + xref,
|
|
10455
|
+
epilog=textwrap.dedent("""\
|
|
10456
|
+
Examples:
|
|
10457
|
+
cctally codex-weekly
|
|
10458
|
+
cctally codex-weekly --since 20260301
|
|
10459
|
+
cctally codex-weekly --breakdown
|
|
10460
|
+
cctally codex-weekly --json
|
|
10461
|
+
cctally codex-weekly --order desc
|
|
10462
|
+
"""),
|
|
10463
|
+
)
|
|
10464
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10465
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10466
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10467
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10468
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10469
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10470
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10471
|
+
help="Sort direction by week (default: asc).")
|
|
10472
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10473
|
+
help="Output JSON.")
|
|
10474
|
+
_add_codex_shared_args(p)
|
|
10475
|
+
p.set_defaults(func=cmd_codex_weekly)
|
|
10476
|
+
return p
|
|
10477
|
+
|
|
10478
|
+
|
|
10479
|
+
def _build_codex_session_parser(subparsers, name, *, help_text, xref):
|
|
10480
|
+
"""Build the `codex-session` leaf parser (issue #86 Session B; routes to cmd_codex_session)."""
|
|
10481
|
+
p = subparsers.add_parser(
|
|
10482
|
+
name,
|
|
10483
|
+
help=help_text,
|
|
10484
|
+
formatter_class=CLIHelpFormatter,
|
|
10485
|
+
description="Show Codex usage grouped by session, matching upstream ccusage-codex session output."
|
|
10486
|
+
"\n\n" + xref,
|
|
10487
|
+
epilog=textwrap.dedent("""\
|
|
10488
|
+
Examples:
|
|
10489
|
+
cctally codex-session
|
|
10490
|
+
cctally codex-session --since 20260401
|
|
10491
|
+
cctally codex-session --json
|
|
10492
|
+
"""),
|
|
10493
|
+
)
|
|
10494
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10495
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10496
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10497
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10498
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10499
|
+
help="Sort direction by last activity (default: asc — earliest first).")
|
|
10500
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10501
|
+
help="Output JSON matching upstream ccusage-codex session format.")
|
|
10502
|
+
_add_codex_shared_args(p)
|
|
10503
|
+
p.set_defaults(func=cmd_codex_session)
|
|
10504
|
+
return p
|
|
10505
|
+
|
|
10506
|
+
|
|
10018
10507
|
def build_parser() -> argparse.ArgumentParser:
|
|
10019
10508
|
p = argparse.ArgumentParser(
|
|
10020
10509
|
prog="cctally",
|
|
@@ -10747,49 +11236,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
10747
11236
|
rc.set_defaults(func=cmd_range_cost)
|
|
10748
11237
|
|
|
10749
11238
|
# -- blocks --
|
|
10750
|
-
|
|
10751
|
-
"blocks",
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
description="Show usage grouped by 5-hour session blocks, matching upstream ccusage blocks output.",
|
|
10755
|
-
epilog=textwrap.dedent("""\
|
|
10756
|
-
Examples:
|
|
10757
|
-
cctally blocks --since 20260414
|
|
10758
|
-
cctally blocks --since 20260410 --until 20260416
|
|
10759
|
-
cctally blocks --since 20260414 --breakdown
|
|
10760
|
-
cctally blocks --since 20260414 --json
|
|
10761
|
-
"""),
|
|
10762
|
-
)
|
|
10763
|
-
bl.add_argument(
|
|
10764
|
-
"-s", "--since",
|
|
10765
|
-
default=None,
|
|
10766
|
-
metavar="YYYYMMDD",
|
|
10767
|
-
help="Filter from date (inclusive).",
|
|
10768
|
-
)
|
|
10769
|
-
bl.add_argument(
|
|
10770
|
-
"-u", "--until",
|
|
10771
|
-
default=None,
|
|
10772
|
-
metavar="YYYYMMDD",
|
|
10773
|
-
help="Filter until date (inclusive).",
|
|
10774
|
-
)
|
|
10775
|
-
bl.add_argument(
|
|
10776
|
-
"-b", "--breakdown",
|
|
10777
|
-
action="store_true",
|
|
10778
|
-
help="Show per-model cost breakdown.",
|
|
10779
|
-
)
|
|
10780
|
-
bl.add_argument(
|
|
10781
|
-
"--json",
|
|
10782
|
-
action="store_true",
|
|
10783
|
-
dest="json",
|
|
10784
|
-
help="Output JSON matching upstream ccusage blocks format.",
|
|
10785
|
-
)
|
|
10786
|
-
bl.add_argument(
|
|
10787
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10788
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
10789
|
-
"Overrides config display.tz for this call.",
|
|
10790
|
-
)
|
|
10791
|
-
_add_ccusage_alias_args(bl, ansi_emit=False)
|
|
10792
|
-
bl.set_defaults(func=cmd_blocks)
|
|
11239
|
+
_build_blocks_parser(
|
|
11240
|
+
sub, "blocks",
|
|
11241
|
+
help_text="Show usage report grouped by 5-hour session blocks",
|
|
11242
|
+
xref="Alias of `cctally claude blocks` (the canonical form).")
|
|
10793
11243
|
|
|
10794
11244
|
# -- five-hour-blocks --
|
|
10795
11245
|
fhb = sub.add_parser(
|
|
@@ -10853,6 +11303,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
10853
11303
|
# guard. The helper's --color add lands as a parsed-and-ignored
|
|
10854
11304
|
# no-op (the renderer emits plain text).
|
|
10855
11305
|
_add_ccusage_alias_args(fhb, ansi_emit=False)
|
|
11306
|
+
_add_mode_arg(fhb, noop=True)
|
|
10856
11307
|
_add_share_args(fhb)
|
|
10857
11308
|
fhb.set_defaults(func=cmd_five_hour_blocks)
|
|
10858
11309
|
|
|
@@ -10875,319 +11326,46 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
10875
11326
|
p_cache_sync.set_defaults(func=cmd_cache_sync)
|
|
10876
11327
|
|
|
10877
11328
|
# -- daily --
|
|
10878
|
-
|
|
10879
|
-
"daily",
|
|
10880
|
-
|
|
10881
|
-
|
|
10882
|
-
description="Show usage grouped by date, matching upstream ccusage daily output.",
|
|
10883
|
-
epilog=textwrap.dedent("""\
|
|
10884
|
-
Examples:
|
|
10885
|
-
cctally daily --since 20260414
|
|
10886
|
-
cctally daily --since 20260410 --until 20260416
|
|
10887
|
-
cctally daily --since 20260414 --breakdown
|
|
10888
|
-
cctally daily --since 20260414 --json
|
|
10889
|
-
cctally daily --order desc
|
|
10890
|
-
"""),
|
|
10891
|
-
)
|
|
10892
|
-
dy.add_argument(
|
|
10893
|
-
"-s", "--since",
|
|
10894
|
-
default=None,
|
|
10895
|
-
metavar="YYYYMMDD",
|
|
10896
|
-
help="Filter from date (inclusive).",
|
|
10897
|
-
)
|
|
10898
|
-
dy.add_argument(
|
|
10899
|
-
"-u", "--until",
|
|
10900
|
-
default=None,
|
|
10901
|
-
metavar="YYYYMMDD",
|
|
10902
|
-
help="Filter until date (inclusive).",
|
|
10903
|
-
)
|
|
10904
|
-
dy.add_argument(
|
|
10905
|
-
"-b", "--breakdown",
|
|
10906
|
-
action="store_true",
|
|
10907
|
-
help="Show per-model cost breakdown sub-rows.",
|
|
10908
|
-
)
|
|
10909
|
-
dy.add_argument(
|
|
10910
|
-
"-o", "--order",
|
|
10911
|
-
choices=("asc", "desc"),
|
|
10912
|
-
default="asc",
|
|
10913
|
-
help="Sort direction by date (default: asc).",
|
|
10914
|
-
)
|
|
10915
|
-
dy.add_argument(
|
|
10916
|
-
"--reveal-projects",
|
|
10917
|
-
action="store_true",
|
|
10918
|
-
dest="reveal_projects",
|
|
10919
|
-
help="In --format output, show real project basenames instead of "
|
|
10920
|
-
"the default project-1, project-2, ... anonymization.",
|
|
10921
|
-
)
|
|
10922
|
-
dy.add_argument(
|
|
10923
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10924
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
10925
|
-
"Overrides config display.tz for this call.",
|
|
10926
|
-
)
|
|
10927
|
-
_add_ccusage_alias_args(dy, ansi_emit=False)
|
|
10928
|
-
_add_share_args(dy)
|
|
10929
|
-
dy.set_defaults(func=cmd_daily)
|
|
11329
|
+
_build_daily_parser(
|
|
11330
|
+
sub, "daily",
|
|
11331
|
+
help_text="Show usage report grouped by date",
|
|
11332
|
+
xref="Alias of `cctally claude daily` (the canonical form).")
|
|
10930
11333
|
|
|
10931
11334
|
# -- monthly --
|
|
10932
|
-
|
|
10933
|
-
"monthly",
|
|
10934
|
-
|
|
10935
|
-
|
|
10936
|
-
description="Show usage grouped by calendar month, matching upstream ccusage monthly output.",
|
|
10937
|
-
epilog=textwrap.dedent("""\
|
|
10938
|
-
Examples:
|
|
10939
|
-
cctally monthly --since 20260101
|
|
10940
|
-
cctally monthly --since 20260101 --until 20260331
|
|
10941
|
-
cctally monthly --since 20260101 --breakdown
|
|
10942
|
-
cctally monthly --since 20260101 --json
|
|
10943
|
-
cctally monthly --order desc
|
|
10944
|
-
"""),
|
|
10945
|
-
)
|
|
10946
|
-
mo.add_argument(
|
|
10947
|
-
"-s", "--since",
|
|
10948
|
-
default=None,
|
|
10949
|
-
metavar="YYYYMMDD",
|
|
10950
|
-
help="Filter from date (inclusive).",
|
|
10951
|
-
)
|
|
10952
|
-
mo.add_argument(
|
|
10953
|
-
"-u", "--until",
|
|
10954
|
-
default=None,
|
|
10955
|
-
metavar="YYYYMMDD",
|
|
10956
|
-
help="Filter until date (inclusive).",
|
|
10957
|
-
)
|
|
10958
|
-
mo.add_argument(
|
|
10959
|
-
"-b", "--breakdown",
|
|
10960
|
-
action="store_true",
|
|
10961
|
-
help="Show per-model cost breakdown sub-rows.",
|
|
10962
|
-
)
|
|
10963
|
-
mo.add_argument(
|
|
10964
|
-
"-o", "--order",
|
|
10965
|
-
choices=("asc", "desc"),
|
|
10966
|
-
default="asc",
|
|
10967
|
-
help="Sort direction by month (default: asc).",
|
|
10968
|
-
)
|
|
10969
|
-
mo.add_argument(
|
|
10970
|
-
"--reveal-projects",
|
|
10971
|
-
action="store_true",
|
|
10972
|
-
dest="reveal_projects",
|
|
10973
|
-
help="In --format output, show real project basenames instead of "
|
|
10974
|
-
"the default project-1, project-2, ... anonymization.",
|
|
10975
|
-
)
|
|
10976
|
-
mo.add_argument(
|
|
10977
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10978
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
10979
|
-
"Overrides config display.tz for this call.",
|
|
10980
|
-
)
|
|
10981
|
-
_add_ccusage_alias_args(mo, ansi_emit=False)
|
|
10982
|
-
_add_share_args(mo)
|
|
10983
|
-
mo.set_defaults(func=cmd_monthly)
|
|
11335
|
+
_build_monthly_parser(
|
|
11336
|
+
sub, "monthly",
|
|
11337
|
+
help_text="Show usage report grouped by month",
|
|
11338
|
+
xref="Alias of `cctally claude monthly` (the canonical form).")
|
|
10984
11339
|
|
|
10985
11340
|
# -- weekly --
|
|
10986
|
-
|
|
10987
|
-
"weekly",
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
description="Show Claude usage grouped by subscription week. Boundaries are anchored "
|
|
10991
|
-
"to weekly_usage_snapshots.week_start_at with 7-day-cadence extrapolation "
|
|
10992
|
-
"for pre-snapshot history. Columns extend daily/monthly's set with Used % "
|
|
10993
|
-
"and $/1%.",
|
|
10994
|
-
epilog=textwrap.dedent("""\
|
|
10995
|
-
Examples:
|
|
10996
|
-
cctally weekly
|
|
10997
|
-
cctally weekly --since 20260101
|
|
10998
|
-
cctally weekly --breakdown
|
|
10999
|
-
cctally weekly --json
|
|
11000
|
-
cctally weekly --order desc
|
|
11001
|
-
"""),
|
|
11002
|
-
)
|
|
11003
|
-
we.add_argument("-s", "--since", default=None, metavar="YYYYMMDD",
|
|
11004
|
-
help="Filter from date (inclusive).")
|
|
11005
|
-
we.add_argument("-u", "--until", default=None, metavar="YYYYMMDD",
|
|
11006
|
-
help="Filter until date (inclusive).")
|
|
11007
|
-
we.add_argument("-b", "--breakdown", action="store_true",
|
|
11008
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
11009
|
-
we.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11010
|
-
help="Sort direction by week (default: asc).")
|
|
11011
|
-
we.add_argument("--reveal-projects", action="store_true", dest="reveal_projects",
|
|
11012
|
-
help="In --format output, show real project basenames instead of "
|
|
11013
|
-
"the default project-1, project-2, ... anonymization.")
|
|
11014
|
-
we.add_argument("--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
11015
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
11016
|
-
"Overrides config display.tz for this call.")
|
|
11017
|
-
_add_ccusage_alias_args(we, ansi_emit=False)
|
|
11018
|
-
_add_share_args(we)
|
|
11019
|
-
we.set_defaults(func=cmd_weekly)
|
|
11020
|
-
|
|
11021
|
-
# -- codex shared args helper --
|
|
11022
|
-
def _add_codex_shared_args(parser: argparse.ArgumentParser) -> None:
|
|
11023
|
-
"""Register upstream `ccusage-codex sharedArgs` on a codex subparser.
|
|
11024
|
-
|
|
11025
|
-
Upstream sharedArgs (node_modules/@ccusage/codex/dist/index.js):
|
|
11026
|
-
--timezone/-z, --locale/-l, --compact, --color, --noColor,
|
|
11027
|
-
--offline/--no-offline.
|
|
11028
|
-
|
|
11029
|
-
Honored here: --timezone (dates + aggregation buckets) and
|
|
11030
|
-
--compact (table layout). Accepted-but-no-op (stored on the
|
|
11031
|
-
namespace for drop-in parity with upstream scripts): --locale
|
|
11032
|
-
(we don't locale-format dates), --color / --noColor (we don't
|
|
11033
|
-
emit ANSI codes today). --offline is accepted as a no-op too
|
|
11034
|
-
(we are always offline); it uses BooleanOptionalAction so
|
|
11035
|
-
`--no-offline` also parses cleanly. `-O` is kept as the short
|
|
11036
|
-
form for offline for backward compat with earlier builds.
|
|
11037
|
-
"""
|
|
11038
|
-
parser.add_argument(
|
|
11039
|
-
"-z", "--timezone", default=None, metavar="TZ",
|
|
11040
|
-
help="IANA timezone for date bucketing and Date/Last Activity cells.",
|
|
11041
|
-
)
|
|
11042
|
-
parser.add_argument(
|
|
11043
|
-
"-l", "--locale", default=None, metavar="LOCALE",
|
|
11044
|
-
help="Accepted for drop-in compat; no-op (dates are not locale-formatted).",
|
|
11045
|
-
)
|
|
11046
|
-
parser.add_argument(
|
|
11047
|
-
"--compact", action="store_true",
|
|
11048
|
-
help="Force compact table layout regardless of terminal width.",
|
|
11049
|
-
)
|
|
11050
|
-
parser.add_argument(
|
|
11051
|
-
"--color", action="store_true",
|
|
11052
|
-
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
11053
|
-
)
|
|
11054
|
-
parser.add_argument(
|
|
11055
|
-
"--noColor", action="store_true", dest="no_color",
|
|
11056
|
-
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
11057
|
-
)
|
|
11058
|
-
parser.add_argument(
|
|
11059
|
-
"-O", "--offline", action=argparse.BooleanOptionalAction, default=False,
|
|
11060
|
-
help="Accepted for drop-in compat with ccusage-codex; we are always offline.",
|
|
11061
|
-
)
|
|
11062
|
-
parser.add_argument(
|
|
11063
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
11064
|
-
help="Display timezone: local, utc, or IANA name. Overrides "
|
|
11065
|
-
"config display.tz for this call. Takes precedence over "
|
|
11066
|
-
"upstream's --timezone for drop-in parity.",
|
|
11067
|
-
)
|
|
11068
|
-
# Issue #92: codex parity for the #89 --debug surface. Codex JSONL
|
|
11069
|
-
# has no recorded costUSD to diff against, so the report is the
|
|
11070
|
-
# codex variant ("Codex Pricing Debug Report": totals + top-N
|
|
11071
|
-
# highest computed-cost entries), wired via
|
|
11072
|
-
# _emit_codex_debug_samples_if_set in each cmd_codex_* body.
|
|
11073
|
-
parser.add_argument(
|
|
11074
|
-
"-d", "--debug", action="store_true",
|
|
11075
|
-
help="Emit a stderr 'Codex Pricing Debug Report' (totals + "
|
|
11076
|
-
"the N highest computed-cost sample entries).",
|
|
11077
|
-
)
|
|
11078
|
-
parser.add_argument(
|
|
11079
|
-
"--debug-samples", type=_nonneg_int, default=5, metavar="N",
|
|
11080
|
-
help="Cap on top-entry sample rows in the --debug report "
|
|
11081
|
-
"(default 5; N=0 suppresses the sample block; "
|
|
11082
|
-
"negatives rejected at parse time).",
|
|
11083
|
-
)
|
|
11341
|
+
_build_weekly_parser(
|
|
11342
|
+
sub, "weekly",
|
|
11343
|
+
help_text="Show usage grouped by subscription week (with Used %% and $/1%%)",
|
|
11344
|
+
xref="Alias of `cctally claude weekly` (the canonical form).")
|
|
11084
11345
|
|
|
11085
11346
|
# -- codex-daily --
|
|
11086
|
-
|
|
11087
|
-
"codex-daily",
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
description="Show Codex usage grouped by date, matching upstream ccusage-codex daily output.",
|
|
11091
|
-
epilog=textwrap.dedent("""\
|
|
11092
|
-
Examples:
|
|
11093
|
-
cctally codex-daily --since 20260401
|
|
11094
|
-
cctally codex-daily --since 20260401 --breakdown
|
|
11095
|
-
cctally codex-daily --since 20260401 --json
|
|
11096
|
-
cctally codex-daily --order desc
|
|
11097
|
-
"""),
|
|
11098
|
-
)
|
|
11099
|
-
cd.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11100
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11101
|
-
cd.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11102
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11103
|
-
cd.add_argument("-b", "--breakdown", action="store_true",
|
|
11104
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
11105
|
-
cd.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11106
|
-
help="Sort direction by date (default: asc).")
|
|
11107
|
-
cd.add_argument("--json", action="store_true", dest="json",
|
|
11108
|
-
help="Output JSON matching upstream ccusage-codex daily format.")
|
|
11109
|
-
_add_codex_shared_args(cd)
|
|
11110
|
-
cd.set_defaults(func=cmd_codex_daily)
|
|
11347
|
+
_build_codex_daily_parser(
|
|
11348
|
+
sub, "codex-daily",
|
|
11349
|
+
help_text="Show Codex usage report grouped by date (drop-in for `ccusage-codex daily`)",
|
|
11350
|
+
xref="Alias of `cctally codex daily` (the canonical form).")
|
|
11111
11351
|
|
|
11112
11352
|
# -- codex-monthly --
|
|
11113
|
-
|
|
11114
|
-
"codex-monthly",
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
description="Show Codex usage grouped by calendar month, matching upstream ccusage-codex monthly output.",
|
|
11118
|
-
epilog=textwrap.dedent("""\
|
|
11119
|
-
Examples:
|
|
11120
|
-
cctally codex-monthly --since 20260101
|
|
11121
|
-
cctally codex-monthly --breakdown
|
|
11122
|
-
cctally codex-monthly --json
|
|
11123
|
-
"""),
|
|
11124
|
-
)
|
|
11125
|
-
cmn.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11126
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11127
|
-
cmn.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11128
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11129
|
-
cmn.add_argument("-b", "--breakdown", action="store_true",
|
|
11130
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
11131
|
-
cmn.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11132
|
-
help="Sort direction by month (default: asc).")
|
|
11133
|
-
cmn.add_argument("--json", action="store_true", dest="json",
|
|
11134
|
-
help="Output JSON matching upstream ccusage-codex monthly format.")
|
|
11135
|
-
_add_codex_shared_args(cmn)
|
|
11136
|
-
cmn.set_defaults(func=cmd_codex_monthly)
|
|
11353
|
+
_build_codex_monthly_parser(
|
|
11354
|
+
sub, "codex-monthly",
|
|
11355
|
+
help_text="Show Codex usage grouped by month (drop-in for `ccusage-codex monthly`)",
|
|
11356
|
+
xref="Alias of `cctally codex monthly` (the canonical form).")
|
|
11137
11357
|
|
|
11138
11358
|
# -- codex-weekly --
|
|
11139
|
-
|
|
11140
|
-
"codex-weekly",
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
description="Show Codex usage grouped by week. Week-start day is read from config.json "
|
|
11144
|
-
"(collector.week_start, Monday default). Not a ccusage-codex drop-in — "
|
|
11145
|
-
"upstream has no `codex weekly` command.",
|
|
11146
|
-
epilog=textwrap.dedent("""\
|
|
11147
|
-
Examples:
|
|
11148
|
-
cctally codex-weekly
|
|
11149
|
-
cctally codex-weekly --since 20260301
|
|
11150
|
-
cctally codex-weekly --breakdown
|
|
11151
|
-
cctally codex-weekly --json
|
|
11152
|
-
cctally codex-weekly --order desc
|
|
11153
|
-
"""),
|
|
11154
|
-
)
|
|
11155
|
-
cw.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11156
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11157
|
-
cw.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11158
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11159
|
-
cw.add_argument("-b", "--breakdown", action="store_true",
|
|
11160
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
11161
|
-
cw.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11162
|
-
help="Sort direction by week (default: asc).")
|
|
11163
|
-
cw.add_argument("--json", action="store_true", dest="json",
|
|
11164
|
-
help="Output JSON.")
|
|
11165
|
-
_add_codex_shared_args(cw)
|
|
11166
|
-
cw.set_defaults(func=cmd_codex_weekly)
|
|
11359
|
+
_build_codex_weekly_parser(
|
|
11360
|
+
sub, "codex-weekly",
|
|
11361
|
+
help_text="Show Codex usage grouped by week (week-start from config.json)",
|
|
11362
|
+
xref="Alias of `cctally codex weekly` (the canonical form).")
|
|
11167
11363
|
|
|
11168
11364
|
# -- codex-session --
|
|
11169
|
-
|
|
11170
|
-
"codex-session",
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
description="Show Codex usage grouped by session, matching upstream ccusage-codex session output.",
|
|
11174
|
-
epilog=textwrap.dedent("""\
|
|
11175
|
-
Examples:
|
|
11176
|
-
cctally codex-session
|
|
11177
|
-
cctally codex-session --since 20260401
|
|
11178
|
-
cctally codex-session --json
|
|
11179
|
-
"""),
|
|
11180
|
-
)
|
|
11181
|
-
cs.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11182
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11183
|
-
cs.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11184
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11185
|
-
cs.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11186
|
-
help="Sort direction by last activity (default: asc — earliest first).")
|
|
11187
|
-
cs.add_argument("--json", action="store_true", dest="json",
|
|
11188
|
-
help="Output JSON matching upstream ccusage-codex session format.")
|
|
11189
|
-
_add_codex_shared_args(cs)
|
|
11190
|
-
cs.set_defaults(func=cmd_codex_session)
|
|
11365
|
+
_build_codex_session_parser(
|
|
11366
|
+
sub, "codex-session",
|
|
11367
|
+
help_text="Show Codex usage grouped by session (drop-in for `ccusage-codex session`)",
|
|
11368
|
+
xref="Alias of `cctally codex session` (the canonical form).")
|
|
11191
11369
|
|
|
11192
11370
|
# -- project --
|
|
11193
11371
|
p_project = sub.add_parser(
|
|
@@ -11285,51 +11463,62 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
11285
11463
|
diff_p.set_defaults(func=cmd_diff)
|
|
11286
11464
|
|
|
11287
11465
|
# -- session --
|
|
11288
|
-
|
|
11289
|
-
"session",
|
|
11290
|
-
|
|
11466
|
+
_build_session_parser(
|
|
11467
|
+
sub, "session",
|
|
11468
|
+
help_text="Show Claude usage grouped by sessionId (merges resumed-across-files sessions)",
|
|
11469
|
+
xref="Alias of `cctally claude session` (the canonical form).")
|
|
11470
|
+
|
|
11471
|
+
# --- `claude` subgroup (drop-in for `ccusage claude …`); issue #86 Session B ---
|
|
11472
|
+
# Build-once, register-twice: these reuse the same nine builders as the flat
|
|
11473
|
+
# forms above. Nested subparsers reuse dest="command" so args.command resolves
|
|
11474
|
+
# to the leaf name (e.g. "blocks"), keeping banner suppression byte-identical
|
|
11475
|
+
# to the flat form with zero hook-path changes.
|
|
11476
|
+
claude_p = sub.add_parser(
|
|
11477
|
+
"claude",
|
|
11478
|
+
help="Claude-source reports (drop-in for `ccusage claude …`)",
|
|
11291
11479
|
formatter_class=CLIHelpFormatter,
|
|
11292
|
-
description="
|
|
11293
|
-
"
|
|
11294
|
-
"
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
|
|
11311
|
-
|
|
11312
|
-
|
|
11313
|
-
|
|
11314
|
-
|
|
11315
|
-
|
|
11316
|
-
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11320
|
-
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11325
|
-
|
|
11326
|
-
|
|
11327
|
-
|
|
11328
|
-
|
|
11329
|
-
|
|
11330
|
-
|
|
11331
|
-
|
|
11332
|
-
|
|
11480
|
+
description="Claude-source usage reports. Each subcommand is a drop-in for the "
|
|
11481
|
+
"matching `ccusage claude <cmd>` and shares its engine with the "
|
|
11482
|
+
"top-level `cctally <cmd>` alias.")
|
|
11483
|
+
claude_sub = claude_p.add_subparsers(dest="command", required=True, metavar="<command>")
|
|
11484
|
+
_build_daily_parser(claude_sub, "daily",
|
|
11485
|
+
help_text="Show usage grouped by date",
|
|
11486
|
+
xref="Drop-in for `ccusage claude daily`. Same engine as `cctally daily`.")
|
|
11487
|
+
_build_monthly_parser(claude_sub, "monthly",
|
|
11488
|
+
help_text="Show usage grouped by month",
|
|
11489
|
+
xref="Drop-in for `ccusage claude monthly`. Same engine as `cctally monthly`.")
|
|
11490
|
+
_build_weekly_parser(claude_sub, "weekly",
|
|
11491
|
+
help_text="Show usage grouped by subscription week",
|
|
11492
|
+
xref="Drop-in for `ccusage claude weekly`. Same engine as `cctally weekly`.")
|
|
11493
|
+
_build_session_parser(claude_sub, "session",
|
|
11494
|
+
help_text="Show usage grouped by session",
|
|
11495
|
+
xref="Drop-in for `ccusage claude session`. Same engine as `cctally session`.")
|
|
11496
|
+
_build_blocks_parser(claude_sub, "blocks",
|
|
11497
|
+
help_text="Show usage grouped by 5-hour session blocks",
|
|
11498
|
+
xref="Drop-in for `ccusage claude blocks`. Same engine as `cctally blocks`.")
|
|
11499
|
+
|
|
11500
|
+
# --- `codex` subgroup (drop-in for `ccusage codex …`); issue #86 Session B ---
|
|
11501
|
+
codex_p = sub.add_parser(
|
|
11502
|
+
"codex",
|
|
11503
|
+
help="Codex-source reports (drop-in for `ccusage codex …`)",
|
|
11504
|
+
formatter_class=CLIHelpFormatter,
|
|
11505
|
+
description="Codex-source usage reports. daily/monthly/session are drop-ins for "
|
|
11506
|
+
"`ccusage codex <cmd>`; weekly is a cctally extension. Each shares its "
|
|
11507
|
+
"engine with the matching `cctally codex-<cmd>` alias.")
|
|
11508
|
+
codex_sub = codex_p.add_subparsers(dest="command", required=True, metavar="<command>")
|
|
11509
|
+
_build_codex_daily_parser(codex_sub, "daily",
|
|
11510
|
+
help_text="Show Codex usage grouped by date",
|
|
11511
|
+
xref="Drop-in for `ccusage codex daily`. Same engine as `cctally codex-daily`.")
|
|
11512
|
+
_build_codex_monthly_parser(codex_sub, "monthly",
|
|
11513
|
+
help_text="Show Codex usage grouped by month",
|
|
11514
|
+
xref="Drop-in for `ccusage codex monthly`. Same engine as `cctally codex-monthly`.")
|
|
11515
|
+
_build_codex_session_parser(codex_sub, "session",
|
|
11516
|
+
help_text="Show Codex usage grouped by session",
|
|
11517
|
+
xref="Drop-in for `ccusage codex session`. Same engine as `cctally codex-session`.")
|
|
11518
|
+
_build_codex_weekly_parser(codex_sub, "weekly",
|
|
11519
|
+
help_text="Show Codex usage grouped by week",
|
|
11520
|
+
xref="cctally extension (no upstream `ccusage codex weekly`). Same engine as "
|
|
11521
|
+
"`cctally codex-weekly`.")
|
|
11333
11522
|
|
|
11334
11523
|
# ---- config (persisted user preferences) ----
|
|
11335
11524
|
cfg_p = sub.add_parser(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cctally",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.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": {
|