cctally 1.13.0 → 1.15.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 +19 -0
- package/README.md +2 -2
- package/bin/_cctally_core.py +50 -2
- package/bin/_cctally_db.py +91 -0
- package/bin/_cctally_record.py +7 -1
- package/bin/_cctally_refresh.py +12 -2
- package/bin/_cctally_setup.py +80 -0
- package/bin/_lib_aggregators.py +18 -5
- package/bin/_lib_doctor.py +39 -0
- package/bin/_lib_render.py +43 -19
- package/bin/cctally +580 -393
- package/package.json +1 -1
package/bin/cctally
CHANGED
|
@@ -909,6 +909,17 @@ def _migrate_legacy_data_dir() -> None:
|
|
|
909
909
|
Removable in a future major version once early users have been on
|
|
910
910
|
cctally long enough that the legacy dir is gone everywhere.
|
|
911
911
|
"""
|
|
912
|
+
# Dev-instance isolation (F2): the legacy ccusage-subscription rename is a
|
|
913
|
+
# PROD-only concern. Skip it whenever the data dir was relocated away from
|
|
914
|
+
# the canonical prod path — i.e. dev-checkout auto-detect (DEV_MODE) or an
|
|
915
|
+
# explicit CCTALLY_DATA_DIR override — so a dev run (APP_DIR = cctally-dev)
|
|
916
|
+
# or a per-branch override never hijacks the one-shot move into the wrong
|
|
917
|
+
# dir. "not DEV_MODE and not CCTALLY_DATA_DIR" is exactly the prod-default
|
|
918
|
+
# resolution branch, i.e. APP_DIR == ~/.local/share/cctally. Under the test
|
|
919
|
+
# suppressor DEV_MODE is False and the existing migration tests (which pin
|
|
920
|
+
# APP_DIR directly, no override) still exercise the move.
|
|
921
|
+
if _cctally_core.DEV_MODE or os.environ.get("CCTALLY_DATA_DIR", "").strip():
|
|
922
|
+
return
|
|
912
923
|
if _cctally_core.APP_DIR.exists():
|
|
913
924
|
return # already migrated, or fresh install at the new path
|
|
914
925
|
if not _cctally_core.LEGACY_APP_DIR.exists():
|
|
@@ -3369,7 +3380,16 @@ def _backfill_five_hour_blocks(conn: sqlite3.Connection) -> int:
|
|
|
3369
3380
|
now_iso = now_utc_iso()
|
|
3370
3381
|
now_dt = parse_iso_datetime(now_iso, "now")
|
|
3371
3382
|
|
|
3372
|
-
|
|
3383
|
+
# BEGIN IMMEDIATE (not deferred): this transaction's first DML is a
|
|
3384
|
+
# READ (min_row/max_row below), so a plain deferred BEGIN takes a read
|
|
3385
|
+
# snapshot and only tries to upgrade to the write lock at the first
|
|
3386
|
+
# INSERT OR IGNORE. Under concurrent first-run openers, a competing
|
|
3387
|
+
# commit landing between that read and the first write makes the upgrade
|
|
3388
|
+
# fail with SQLITE_BUSY_SNAPSHOT *immediately* — busy_timeout cannot
|
|
3389
|
+
# absorb it, and the whole backfill rolls back. Acquiring the write lock
|
|
3390
|
+
# up front serializes the backfill cleanly behind busy_timeout instead.
|
|
3391
|
+
# See cctally-dev#87.
|
|
3392
|
+
conn.execute("BEGIN IMMEDIATE")
|
|
3373
3393
|
try:
|
|
3374
3394
|
for key in keys:
|
|
3375
3395
|
# MIN-captured row defines the immutable block boundary
|
|
@@ -9715,6 +9735,10 @@ def doctor_gather_state(
|
|
|
9715
9735
|
effective_update_reason=effective_update_reason,
|
|
9716
9736
|
now_utc=now_utc,
|
|
9717
9737
|
cctally_version=cctally_version,
|
|
9738
|
+
# Dev-instance isolation (§4): which data dir resolved + how.
|
|
9739
|
+
dev_mode=_cctally_core.DEV_MODE,
|
|
9740
|
+
app_dir=str(_cctally_core.APP_DIR),
|
|
9741
|
+
is_dev_checkout=_cctally_core._is_dev_checkout(),
|
|
9718
9742
|
)
|
|
9719
9743
|
|
|
9720
9744
|
|
|
@@ -9863,6 +9887,70 @@ def _add_ccusage_alias_args(parser, *, ansi_emit: bool) -> None:
|
|
|
9863
9887
|
)
|
|
9864
9888
|
|
|
9865
9889
|
|
|
9890
|
+
def _add_codex_shared_args(parser: argparse.ArgumentParser) -> None:
|
|
9891
|
+
"""Register upstream `ccusage-codex sharedArgs` on a codex subparser.
|
|
9892
|
+
|
|
9893
|
+
Upstream sharedArgs (node_modules/@ccusage/codex/dist/index.js):
|
|
9894
|
+
--timezone/-z, --locale/-l, --compact, --color, --noColor,
|
|
9895
|
+
--offline/--no-offline.
|
|
9896
|
+
|
|
9897
|
+
Honored here: --timezone (dates + aggregation buckets) and
|
|
9898
|
+
--compact (table layout). Accepted-but-no-op (stored on the
|
|
9899
|
+
namespace for drop-in parity with upstream scripts): --locale
|
|
9900
|
+
(we don't locale-format dates), --color / --noColor (we don't
|
|
9901
|
+
emit ANSI codes today). --offline is accepted as a no-op too
|
|
9902
|
+
(we are always offline); it uses BooleanOptionalAction so
|
|
9903
|
+
`--no-offline` also parses cleanly. `-O` is kept as the short
|
|
9904
|
+
form for offline for backward compat with earlier builds.
|
|
9905
|
+
"""
|
|
9906
|
+
parser.add_argument(
|
|
9907
|
+
"-z", "--timezone", default=None, metavar="TZ",
|
|
9908
|
+
help="IANA timezone for date bucketing and Date/Last Activity cells.",
|
|
9909
|
+
)
|
|
9910
|
+
parser.add_argument(
|
|
9911
|
+
"-l", "--locale", default=None, metavar="LOCALE",
|
|
9912
|
+
help="Accepted for drop-in compat; no-op (dates are not locale-formatted).",
|
|
9913
|
+
)
|
|
9914
|
+
parser.add_argument(
|
|
9915
|
+
"--compact", action="store_true",
|
|
9916
|
+
help="Force compact table layout regardless of terminal width.",
|
|
9917
|
+
)
|
|
9918
|
+
parser.add_argument(
|
|
9919
|
+
"--color", action="store_true",
|
|
9920
|
+
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
9921
|
+
)
|
|
9922
|
+
parser.add_argument(
|
|
9923
|
+
"--noColor", action="store_true", dest="no_color",
|
|
9924
|
+
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
9925
|
+
)
|
|
9926
|
+
parser.add_argument(
|
|
9927
|
+
"-O", "--offline", action=argparse.BooleanOptionalAction, default=False,
|
|
9928
|
+
help="Accepted for drop-in compat with ccusage-codex; we are always offline.",
|
|
9929
|
+
)
|
|
9930
|
+
parser.add_argument(
|
|
9931
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
9932
|
+
help="Display timezone: local, utc, or IANA name. Overrides "
|
|
9933
|
+
"config display.tz for this call. Takes precedence over "
|
|
9934
|
+
"upstream's --timezone for drop-in parity.",
|
|
9935
|
+
)
|
|
9936
|
+
# Issue #92: codex parity for the #89 --debug surface. Codex JSONL
|
|
9937
|
+
# has no recorded costUSD to diff against, so the report is the
|
|
9938
|
+
# codex variant ("Codex Pricing Debug Report": totals + top-N
|
|
9939
|
+
# highest computed-cost entries), wired via
|
|
9940
|
+
# _emit_codex_debug_samples_if_set in each cmd_codex_* body.
|
|
9941
|
+
parser.add_argument(
|
|
9942
|
+
"-d", "--debug", action="store_true",
|
|
9943
|
+
help="Emit a stderr 'Codex Pricing Debug Report' (totals + "
|
|
9944
|
+
"the N highest computed-cost sample entries).",
|
|
9945
|
+
)
|
|
9946
|
+
parser.add_argument(
|
|
9947
|
+
"--debug-samples", type=_nonneg_int, default=5, metavar="N",
|
|
9948
|
+
help="Cap on top-entry sample rows in the --debug report "
|
|
9949
|
+
"(default 5; N=0 suppresses the sample block; "
|
|
9950
|
+
"negatives rejected at parse time).",
|
|
9951
|
+
)
|
|
9952
|
+
|
|
9953
|
+
|
|
9866
9954
|
def _add_share_args(parser, *, has_status_line: bool = False) -> None:
|
|
9867
9955
|
"""Attach shareable-reports flags + format/json mutex to a subparser.
|
|
9868
9956
|
|
|
@@ -9991,6 +10079,394 @@ def _share_validate_args(args) -> None:
|
|
|
9991
10079
|
sys.exit(2)
|
|
9992
10080
|
|
|
9993
10081
|
|
|
10082
|
+
def _build_daily_parser(subparsers, name, *, help_text, xref):
|
|
10083
|
+
"""Build the `daily` leaf parser (issue #86 Session B; routes to cmd_daily).
|
|
10084
|
+
|
|
10085
|
+
Build-once, register-twice: this body is the verbatim former inline `daily`
|
|
10086
|
+
construction, parameterized only by `name`, the parent-list `help_text`, and
|
|
10087
|
+
the `xref` appended to `description` (renders on `cctally <name> --help`).
|
|
10088
|
+
"""
|
|
10089
|
+
p = subparsers.add_parser(
|
|
10090
|
+
name,
|
|
10091
|
+
help=help_text,
|
|
10092
|
+
formatter_class=CLIHelpFormatter,
|
|
10093
|
+
description="Show usage grouped by date, matching upstream ccusage daily output."
|
|
10094
|
+
"\n\n" + xref,
|
|
10095
|
+
epilog=textwrap.dedent("""\
|
|
10096
|
+
Examples:
|
|
10097
|
+
cctally daily --since 20260414
|
|
10098
|
+
cctally daily --since 20260410 --until 20260416
|
|
10099
|
+
cctally daily --since 20260414 --breakdown
|
|
10100
|
+
cctally daily --since 20260414 --json
|
|
10101
|
+
cctally daily --order desc
|
|
10102
|
+
"""),
|
|
10103
|
+
)
|
|
10104
|
+
p.add_argument(
|
|
10105
|
+
"-s", "--since",
|
|
10106
|
+
default=None,
|
|
10107
|
+
metavar="YYYYMMDD",
|
|
10108
|
+
help="Filter from date (inclusive).",
|
|
10109
|
+
)
|
|
10110
|
+
p.add_argument(
|
|
10111
|
+
"-u", "--until",
|
|
10112
|
+
default=None,
|
|
10113
|
+
metavar="YYYYMMDD",
|
|
10114
|
+
help="Filter until date (inclusive).",
|
|
10115
|
+
)
|
|
10116
|
+
p.add_argument(
|
|
10117
|
+
"-b", "--breakdown",
|
|
10118
|
+
action="store_true",
|
|
10119
|
+
help="Show per-model cost breakdown sub-rows.",
|
|
10120
|
+
)
|
|
10121
|
+
p.add_argument(
|
|
10122
|
+
"-o", "--order",
|
|
10123
|
+
choices=("asc", "desc"),
|
|
10124
|
+
default="asc",
|
|
10125
|
+
help="Sort direction by date (default: asc).",
|
|
10126
|
+
)
|
|
10127
|
+
p.add_argument(
|
|
10128
|
+
"--reveal-projects",
|
|
10129
|
+
action="store_true",
|
|
10130
|
+
dest="reveal_projects",
|
|
10131
|
+
help="In --format output, show real project basenames instead of "
|
|
10132
|
+
"the default project-1, project-2, ... anonymization.",
|
|
10133
|
+
)
|
|
10134
|
+
p.add_argument(
|
|
10135
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10136
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10137
|
+
"Overrides config display.tz for this call.",
|
|
10138
|
+
)
|
|
10139
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10140
|
+
_add_share_args(p)
|
|
10141
|
+
p.set_defaults(func=cmd_daily)
|
|
10142
|
+
return p
|
|
10143
|
+
|
|
10144
|
+
|
|
10145
|
+
def _build_monthly_parser(subparsers, name, *, help_text, xref):
|
|
10146
|
+
"""Build the `monthly` leaf parser (issue #86 Session B; routes to cmd_monthly)."""
|
|
10147
|
+
p = subparsers.add_parser(
|
|
10148
|
+
name,
|
|
10149
|
+
help=help_text,
|
|
10150
|
+
formatter_class=CLIHelpFormatter,
|
|
10151
|
+
description="Show usage grouped by calendar month, matching upstream ccusage monthly output."
|
|
10152
|
+
"\n\n" + xref,
|
|
10153
|
+
epilog=textwrap.dedent("""\
|
|
10154
|
+
Examples:
|
|
10155
|
+
cctally monthly --since 20260101
|
|
10156
|
+
cctally monthly --since 20260101 --until 20260331
|
|
10157
|
+
cctally monthly --since 20260101 --breakdown
|
|
10158
|
+
cctally monthly --since 20260101 --json
|
|
10159
|
+
cctally monthly --order desc
|
|
10160
|
+
"""),
|
|
10161
|
+
)
|
|
10162
|
+
p.add_argument(
|
|
10163
|
+
"-s", "--since",
|
|
10164
|
+
default=None,
|
|
10165
|
+
metavar="YYYYMMDD",
|
|
10166
|
+
help="Filter from date (inclusive).",
|
|
10167
|
+
)
|
|
10168
|
+
p.add_argument(
|
|
10169
|
+
"-u", "--until",
|
|
10170
|
+
default=None,
|
|
10171
|
+
metavar="YYYYMMDD",
|
|
10172
|
+
help="Filter until date (inclusive).",
|
|
10173
|
+
)
|
|
10174
|
+
p.add_argument(
|
|
10175
|
+
"-b", "--breakdown",
|
|
10176
|
+
action="store_true",
|
|
10177
|
+
help="Show per-model cost breakdown sub-rows.",
|
|
10178
|
+
)
|
|
10179
|
+
p.add_argument(
|
|
10180
|
+
"-o", "--order",
|
|
10181
|
+
choices=("asc", "desc"),
|
|
10182
|
+
default="asc",
|
|
10183
|
+
help="Sort direction by month (default: asc).",
|
|
10184
|
+
)
|
|
10185
|
+
p.add_argument(
|
|
10186
|
+
"--reveal-projects",
|
|
10187
|
+
action="store_true",
|
|
10188
|
+
dest="reveal_projects",
|
|
10189
|
+
help="In --format output, show real project basenames instead of "
|
|
10190
|
+
"the default project-1, project-2, ... anonymization.",
|
|
10191
|
+
)
|
|
10192
|
+
p.add_argument(
|
|
10193
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10194
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10195
|
+
"Overrides config display.tz for this call.",
|
|
10196
|
+
)
|
|
10197
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10198
|
+
_add_share_args(p)
|
|
10199
|
+
p.set_defaults(func=cmd_monthly)
|
|
10200
|
+
return p
|
|
10201
|
+
|
|
10202
|
+
|
|
10203
|
+
def _build_weekly_parser(subparsers, name, *, help_text, xref):
|
|
10204
|
+
"""Build the `weekly` leaf parser (issue #86 Session B; routes to cmd_weekly)."""
|
|
10205
|
+
p = subparsers.add_parser(
|
|
10206
|
+
name,
|
|
10207
|
+
help=help_text,
|
|
10208
|
+
formatter_class=CLIHelpFormatter,
|
|
10209
|
+
description="Show Claude usage grouped by subscription week. Boundaries are anchored "
|
|
10210
|
+
"to weekly_usage_snapshots.week_start_at with 7-day-cadence extrapolation "
|
|
10211
|
+
"for pre-snapshot history. Columns extend daily/monthly's set with Used % "
|
|
10212
|
+
"and $/1%."
|
|
10213
|
+
"\n\n" + xref,
|
|
10214
|
+
epilog=textwrap.dedent("""\
|
|
10215
|
+
Examples:
|
|
10216
|
+
cctally weekly
|
|
10217
|
+
cctally weekly --since 20260101
|
|
10218
|
+
cctally weekly --breakdown
|
|
10219
|
+
cctally weekly --json
|
|
10220
|
+
cctally weekly --order desc
|
|
10221
|
+
"""),
|
|
10222
|
+
)
|
|
10223
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYYMMDD",
|
|
10224
|
+
help="Filter from date (inclusive).")
|
|
10225
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYYMMDD",
|
|
10226
|
+
help="Filter until date (inclusive).")
|
|
10227
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10228
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10229
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10230
|
+
help="Sort direction by week (default: asc).")
|
|
10231
|
+
p.add_argument("--reveal-projects", action="store_true", dest="reveal_projects",
|
|
10232
|
+
help="In --format output, show real project basenames instead of "
|
|
10233
|
+
"the default project-1, project-2, ... anonymization.")
|
|
10234
|
+
p.add_argument("--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10235
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10236
|
+
"Overrides config display.tz for this call.")
|
|
10237
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10238
|
+
_add_share_args(p)
|
|
10239
|
+
p.set_defaults(func=cmd_weekly)
|
|
10240
|
+
return p
|
|
10241
|
+
|
|
10242
|
+
|
|
10243
|
+
def _build_session_parser(subparsers, name, *, help_text, xref):
|
|
10244
|
+
"""Build the `session` leaf parser (issue #86 Session B; routes to cmd_session)."""
|
|
10245
|
+
p = subparsers.add_parser(
|
|
10246
|
+
name,
|
|
10247
|
+
help=help_text,
|
|
10248
|
+
formatter_class=CLIHelpFormatter,
|
|
10249
|
+
description="Show Claude usage grouped by JSONL sessionId. Resumed sessions (same "
|
|
10250
|
+
"sessionId across multiple files) collapse into one row. 11-column "
|
|
10251
|
+
"layout paralleling codex-session."
|
|
10252
|
+
"\n\n" + xref,
|
|
10253
|
+
epilog=textwrap.dedent("""\
|
|
10254
|
+
Examples:
|
|
10255
|
+
cctally session
|
|
10256
|
+
cctally session --since 20260401
|
|
10257
|
+
cctally session --since 20260401 --breakdown
|
|
10258
|
+
cctally session --json
|
|
10259
|
+
cctally session --order desc
|
|
10260
|
+
"""),
|
|
10261
|
+
)
|
|
10262
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYYMMDD",
|
|
10263
|
+
help="Filter from date (inclusive).")
|
|
10264
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYYMMDD",
|
|
10265
|
+
help="Filter until date (inclusive).")
|
|
10266
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10267
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10268
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10269
|
+
help="Sort direction by last activity (default: asc — earliest first).")
|
|
10270
|
+
p.add_argument("--reveal-projects", action="store_true", dest="reveal_projects",
|
|
10271
|
+
help="In --format output, show real project basenames instead of "
|
|
10272
|
+
"the default project-1, project-2, ... anonymization.")
|
|
10273
|
+
p.add_argument("--top-n", type=int, default=15, dest="top_n",
|
|
10274
|
+
metavar="N",
|
|
10275
|
+
help="In --format output, cap rows to top N by cost (default: 15). "
|
|
10276
|
+
"Must be >= 1; values above 50 emit a readability warning. "
|
|
10277
|
+
"Has no effect on terminal/JSON output.")
|
|
10278
|
+
p.add_argument("--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10279
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10280
|
+
"Overrides config display.tz for this call.")
|
|
10281
|
+
p.add_argument(
|
|
10282
|
+
"-i", "--id", default=None, metavar="SESSION_ID", dest="id",
|
|
10283
|
+
help="Filter to a single session by exact-string sessionId. "
|
|
10284
|
+
"Match is against the post-resume-merge id (sessions "
|
|
10285
|
+
"resumed across multiple JSONL files collapse to one id). "
|
|
10286
|
+
"Unknown id → exit 0 with the empty-render branch.",
|
|
10287
|
+
)
|
|
10288
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10289
|
+
_add_share_args(p)
|
|
10290
|
+
p.set_defaults(func=cmd_session)
|
|
10291
|
+
return p
|
|
10292
|
+
|
|
10293
|
+
|
|
10294
|
+
def _build_blocks_parser(subparsers, name, *, help_text, xref):
|
|
10295
|
+
"""Build the `blocks` leaf parser (issue #86 Session B; routes to cmd_blocks).
|
|
10296
|
+
|
|
10297
|
+
Note: `blocks` intentionally has NO `_add_share_args` (matches the former
|
|
10298
|
+
inline block — it is not part of the shareable-output flag surface).
|
|
10299
|
+
"""
|
|
10300
|
+
p = subparsers.add_parser(
|
|
10301
|
+
name,
|
|
10302
|
+
help=help_text,
|
|
10303
|
+
formatter_class=CLIHelpFormatter,
|
|
10304
|
+
description="Show usage grouped by 5-hour session blocks, matching upstream ccusage blocks output."
|
|
10305
|
+
"\n\n" + xref,
|
|
10306
|
+
epilog=textwrap.dedent("""\
|
|
10307
|
+
Examples:
|
|
10308
|
+
cctally blocks --since 20260414
|
|
10309
|
+
cctally blocks --since 20260410 --until 20260416
|
|
10310
|
+
cctally blocks --since 20260414 --breakdown
|
|
10311
|
+
cctally blocks --since 20260414 --json
|
|
10312
|
+
"""),
|
|
10313
|
+
)
|
|
10314
|
+
p.add_argument(
|
|
10315
|
+
"-s", "--since",
|
|
10316
|
+
default=None,
|
|
10317
|
+
metavar="YYYYMMDD",
|
|
10318
|
+
help="Filter from date (inclusive).",
|
|
10319
|
+
)
|
|
10320
|
+
p.add_argument(
|
|
10321
|
+
"-u", "--until",
|
|
10322
|
+
default=None,
|
|
10323
|
+
metavar="YYYYMMDD",
|
|
10324
|
+
help="Filter until date (inclusive).",
|
|
10325
|
+
)
|
|
10326
|
+
p.add_argument(
|
|
10327
|
+
"-b", "--breakdown",
|
|
10328
|
+
action="store_true",
|
|
10329
|
+
help="Show per-model cost breakdown.",
|
|
10330
|
+
)
|
|
10331
|
+
p.add_argument(
|
|
10332
|
+
"--json",
|
|
10333
|
+
action="store_true",
|
|
10334
|
+
dest="json",
|
|
10335
|
+
help="Output JSON matching upstream ccusage blocks format.",
|
|
10336
|
+
)
|
|
10337
|
+
p.add_argument(
|
|
10338
|
+
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10339
|
+
help="Display timezone: local, utc, or IANA name. "
|
|
10340
|
+
"Overrides config display.tz for this call.",
|
|
10341
|
+
)
|
|
10342
|
+
_add_ccusage_alias_args(p, ansi_emit=False)
|
|
10343
|
+
p.set_defaults(func=cmd_blocks)
|
|
10344
|
+
return p
|
|
10345
|
+
|
|
10346
|
+
|
|
10347
|
+
def _build_codex_daily_parser(subparsers, name, *, help_text, xref):
|
|
10348
|
+
"""Build the `codex-daily` leaf parser (issue #86 Session B; routes to cmd_codex_daily)."""
|
|
10349
|
+
p = subparsers.add_parser(
|
|
10350
|
+
name,
|
|
10351
|
+
help=help_text,
|
|
10352
|
+
formatter_class=CLIHelpFormatter,
|
|
10353
|
+
description="Show Codex usage grouped by date, matching upstream ccusage-codex daily output."
|
|
10354
|
+
"\n\n" + xref,
|
|
10355
|
+
epilog=textwrap.dedent("""\
|
|
10356
|
+
Examples:
|
|
10357
|
+
cctally codex-daily --since 20260401
|
|
10358
|
+
cctally codex-daily --since 20260401 --breakdown
|
|
10359
|
+
cctally codex-daily --since 20260401 --json
|
|
10360
|
+
cctally codex-daily --order desc
|
|
10361
|
+
"""),
|
|
10362
|
+
)
|
|
10363
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10364
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10365
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10366
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10367
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10368
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10369
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10370
|
+
help="Sort direction by date (default: asc).")
|
|
10371
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10372
|
+
help="Output JSON matching upstream ccusage-codex daily format.")
|
|
10373
|
+
_add_codex_shared_args(p)
|
|
10374
|
+
p.set_defaults(func=cmd_codex_daily)
|
|
10375
|
+
return p
|
|
10376
|
+
|
|
10377
|
+
|
|
10378
|
+
def _build_codex_monthly_parser(subparsers, name, *, help_text, xref):
|
|
10379
|
+
"""Build the `codex-monthly` leaf parser (issue #86 Session B; routes to cmd_codex_monthly)."""
|
|
10380
|
+
p = subparsers.add_parser(
|
|
10381
|
+
name,
|
|
10382
|
+
help=help_text,
|
|
10383
|
+
formatter_class=CLIHelpFormatter,
|
|
10384
|
+
description="Show Codex usage grouped by calendar month, matching upstream ccusage-codex monthly output."
|
|
10385
|
+
"\n\n" + xref,
|
|
10386
|
+
epilog=textwrap.dedent("""\
|
|
10387
|
+
Examples:
|
|
10388
|
+
cctally codex-monthly --since 20260101
|
|
10389
|
+
cctally codex-monthly --breakdown
|
|
10390
|
+
cctally codex-monthly --json
|
|
10391
|
+
"""),
|
|
10392
|
+
)
|
|
10393
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10394
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10395
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10396
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10397
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10398
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10399
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10400
|
+
help="Sort direction by month (default: asc).")
|
|
10401
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10402
|
+
help="Output JSON matching upstream ccusage-codex monthly format.")
|
|
10403
|
+
_add_codex_shared_args(p)
|
|
10404
|
+
p.set_defaults(func=cmd_codex_monthly)
|
|
10405
|
+
return p
|
|
10406
|
+
|
|
10407
|
+
|
|
10408
|
+
def _build_codex_weekly_parser(subparsers, name, *, help_text, xref):
|
|
10409
|
+
"""Build the `codex-weekly` leaf parser (issue #86 Session B; routes to cmd_codex_weekly)."""
|
|
10410
|
+
p = subparsers.add_parser(
|
|
10411
|
+
name,
|
|
10412
|
+
help=help_text,
|
|
10413
|
+
formatter_class=CLIHelpFormatter,
|
|
10414
|
+
description="Show Codex usage grouped by week. Week-start day is read from config.json "
|
|
10415
|
+
"(collector.week_start, Monday default). Not a ccusage-codex drop-in — "
|
|
10416
|
+
"upstream has no `codex weekly` command."
|
|
10417
|
+
"\n\n" + xref,
|
|
10418
|
+
epilog=textwrap.dedent("""\
|
|
10419
|
+
Examples:
|
|
10420
|
+
cctally codex-weekly
|
|
10421
|
+
cctally codex-weekly --since 20260301
|
|
10422
|
+
cctally codex-weekly --breakdown
|
|
10423
|
+
cctally codex-weekly --json
|
|
10424
|
+
cctally codex-weekly --order desc
|
|
10425
|
+
"""),
|
|
10426
|
+
)
|
|
10427
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10428
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10429
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10430
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10431
|
+
p.add_argument("-b", "--breakdown", action="store_true",
|
|
10432
|
+
help="Show per-model cost breakdown sub-rows.")
|
|
10433
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10434
|
+
help="Sort direction by week (default: asc).")
|
|
10435
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10436
|
+
help="Output JSON.")
|
|
10437
|
+
_add_codex_shared_args(p)
|
|
10438
|
+
p.set_defaults(func=cmd_codex_weekly)
|
|
10439
|
+
return p
|
|
10440
|
+
|
|
10441
|
+
|
|
10442
|
+
def _build_codex_session_parser(subparsers, name, *, help_text, xref):
|
|
10443
|
+
"""Build the `codex-session` leaf parser (issue #86 Session B; routes to cmd_codex_session)."""
|
|
10444
|
+
p = subparsers.add_parser(
|
|
10445
|
+
name,
|
|
10446
|
+
help=help_text,
|
|
10447
|
+
formatter_class=CLIHelpFormatter,
|
|
10448
|
+
description="Show Codex usage grouped by session, matching upstream ccusage-codex session output."
|
|
10449
|
+
"\n\n" + xref,
|
|
10450
|
+
epilog=textwrap.dedent("""\
|
|
10451
|
+
Examples:
|
|
10452
|
+
cctally codex-session
|
|
10453
|
+
cctally codex-session --since 20260401
|
|
10454
|
+
cctally codex-session --json
|
|
10455
|
+
"""),
|
|
10456
|
+
)
|
|
10457
|
+
p.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
10458
|
+
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10459
|
+
p.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
10460
|
+
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
10461
|
+
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10462
|
+
help="Sort direction by last activity (default: asc — earliest first).")
|
|
10463
|
+
p.add_argument("--json", action="store_true", dest="json",
|
|
10464
|
+
help="Output JSON matching upstream ccusage-codex session format.")
|
|
10465
|
+
_add_codex_shared_args(p)
|
|
10466
|
+
p.set_defaults(func=cmd_codex_session)
|
|
10467
|
+
return p
|
|
10468
|
+
|
|
10469
|
+
|
|
9994
10470
|
def build_parser() -> argparse.ArgumentParser:
|
|
9995
10471
|
p = argparse.ArgumentParser(
|
|
9996
10472
|
prog="cctally",
|
|
@@ -10723,49 +11199,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
10723
11199
|
rc.set_defaults(func=cmd_range_cost)
|
|
10724
11200
|
|
|
10725
11201
|
# -- blocks --
|
|
10726
|
-
|
|
10727
|
-
"blocks",
|
|
10728
|
-
|
|
10729
|
-
|
|
10730
|
-
description="Show usage grouped by 5-hour session blocks, matching upstream ccusage blocks output.",
|
|
10731
|
-
epilog=textwrap.dedent("""\
|
|
10732
|
-
Examples:
|
|
10733
|
-
cctally blocks --since 20260414
|
|
10734
|
-
cctally blocks --since 20260410 --until 20260416
|
|
10735
|
-
cctally blocks --since 20260414 --breakdown
|
|
10736
|
-
cctally blocks --since 20260414 --json
|
|
10737
|
-
"""),
|
|
10738
|
-
)
|
|
10739
|
-
bl.add_argument(
|
|
10740
|
-
"-s", "--since",
|
|
10741
|
-
default=None,
|
|
10742
|
-
metavar="YYYYMMDD",
|
|
10743
|
-
help="Filter from date (inclusive).",
|
|
10744
|
-
)
|
|
10745
|
-
bl.add_argument(
|
|
10746
|
-
"-u", "--until",
|
|
10747
|
-
default=None,
|
|
10748
|
-
metavar="YYYYMMDD",
|
|
10749
|
-
help="Filter until date (inclusive).",
|
|
10750
|
-
)
|
|
10751
|
-
bl.add_argument(
|
|
10752
|
-
"-b", "--breakdown",
|
|
10753
|
-
action="store_true",
|
|
10754
|
-
help="Show per-model cost breakdown.",
|
|
10755
|
-
)
|
|
10756
|
-
bl.add_argument(
|
|
10757
|
-
"--json",
|
|
10758
|
-
action="store_true",
|
|
10759
|
-
dest="json",
|
|
10760
|
-
help="Output JSON matching upstream ccusage blocks format.",
|
|
10761
|
-
)
|
|
10762
|
-
bl.add_argument(
|
|
10763
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10764
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
10765
|
-
"Overrides config display.tz for this call.",
|
|
10766
|
-
)
|
|
10767
|
-
_add_ccusage_alias_args(bl, ansi_emit=False)
|
|
10768
|
-
bl.set_defaults(func=cmd_blocks)
|
|
11202
|
+
_build_blocks_parser(
|
|
11203
|
+
sub, "blocks",
|
|
11204
|
+
help_text="Show usage report grouped by 5-hour session blocks",
|
|
11205
|
+
xref="Alias of `cctally claude blocks` (the canonical form).")
|
|
10769
11206
|
|
|
10770
11207
|
# -- five-hour-blocks --
|
|
10771
11208
|
fhb = sub.add_parser(
|
|
@@ -10851,319 +11288,46 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
10851
11288
|
p_cache_sync.set_defaults(func=cmd_cache_sync)
|
|
10852
11289
|
|
|
10853
11290
|
# -- daily --
|
|
10854
|
-
|
|
10855
|
-
"daily",
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
description="Show usage grouped by date, matching upstream ccusage daily output.",
|
|
10859
|
-
epilog=textwrap.dedent("""\
|
|
10860
|
-
Examples:
|
|
10861
|
-
cctally daily --since 20260414
|
|
10862
|
-
cctally daily --since 20260410 --until 20260416
|
|
10863
|
-
cctally daily --since 20260414 --breakdown
|
|
10864
|
-
cctally daily --since 20260414 --json
|
|
10865
|
-
cctally daily --order desc
|
|
10866
|
-
"""),
|
|
10867
|
-
)
|
|
10868
|
-
dy.add_argument(
|
|
10869
|
-
"-s", "--since",
|
|
10870
|
-
default=None,
|
|
10871
|
-
metavar="YYYYMMDD",
|
|
10872
|
-
help="Filter from date (inclusive).",
|
|
10873
|
-
)
|
|
10874
|
-
dy.add_argument(
|
|
10875
|
-
"-u", "--until",
|
|
10876
|
-
default=None,
|
|
10877
|
-
metavar="YYYYMMDD",
|
|
10878
|
-
help="Filter until date (inclusive).",
|
|
10879
|
-
)
|
|
10880
|
-
dy.add_argument(
|
|
10881
|
-
"-b", "--breakdown",
|
|
10882
|
-
action="store_true",
|
|
10883
|
-
help="Show per-model cost breakdown sub-rows.",
|
|
10884
|
-
)
|
|
10885
|
-
dy.add_argument(
|
|
10886
|
-
"-o", "--order",
|
|
10887
|
-
choices=("asc", "desc"),
|
|
10888
|
-
default="asc",
|
|
10889
|
-
help="Sort direction by date (default: asc).",
|
|
10890
|
-
)
|
|
10891
|
-
dy.add_argument(
|
|
10892
|
-
"--reveal-projects",
|
|
10893
|
-
action="store_true",
|
|
10894
|
-
dest="reveal_projects",
|
|
10895
|
-
help="In --format output, show real project basenames instead of "
|
|
10896
|
-
"the default project-1, project-2, ... anonymization.",
|
|
10897
|
-
)
|
|
10898
|
-
dy.add_argument(
|
|
10899
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10900
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
10901
|
-
"Overrides config display.tz for this call.",
|
|
10902
|
-
)
|
|
10903
|
-
_add_ccusage_alias_args(dy, ansi_emit=False)
|
|
10904
|
-
_add_share_args(dy)
|
|
10905
|
-
dy.set_defaults(func=cmd_daily)
|
|
11291
|
+
_build_daily_parser(
|
|
11292
|
+
sub, "daily",
|
|
11293
|
+
help_text="Show usage report grouped by date",
|
|
11294
|
+
xref="Alias of `cctally claude daily` (the canonical form).")
|
|
10906
11295
|
|
|
10907
11296
|
# -- monthly --
|
|
10908
|
-
|
|
10909
|
-
"monthly",
|
|
10910
|
-
|
|
10911
|
-
|
|
10912
|
-
description="Show usage grouped by calendar month, matching upstream ccusage monthly output.",
|
|
10913
|
-
epilog=textwrap.dedent("""\
|
|
10914
|
-
Examples:
|
|
10915
|
-
cctally monthly --since 20260101
|
|
10916
|
-
cctally monthly --since 20260101 --until 20260331
|
|
10917
|
-
cctally monthly --since 20260101 --breakdown
|
|
10918
|
-
cctally monthly --since 20260101 --json
|
|
10919
|
-
cctally monthly --order desc
|
|
10920
|
-
"""),
|
|
10921
|
-
)
|
|
10922
|
-
mo.add_argument(
|
|
10923
|
-
"-s", "--since",
|
|
10924
|
-
default=None,
|
|
10925
|
-
metavar="YYYYMMDD",
|
|
10926
|
-
help="Filter from date (inclusive).",
|
|
10927
|
-
)
|
|
10928
|
-
mo.add_argument(
|
|
10929
|
-
"-u", "--until",
|
|
10930
|
-
default=None,
|
|
10931
|
-
metavar="YYYYMMDD",
|
|
10932
|
-
help="Filter until date (inclusive).",
|
|
10933
|
-
)
|
|
10934
|
-
mo.add_argument(
|
|
10935
|
-
"-b", "--breakdown",
|
|
10936
|
-
action="store_true",
|
|
10937
|
-
help="Show per-model cost breakdown sub-rows.",
|
|
10938
|
-
)
|
|
10939
|
-
mo.add_argument(
|
|
10940
|
-
"-o", "--order",
|
|
10941
|
-
choices=("asc", "desc"),
|
|
10942
|
-
default="asc",
|
|
10943
|
-
help="Sort direction by month (default: asc).",
|
|
10944
|
-
)
|
|
10945
|
-
mo.add_argument(
|
|
10946
|
-
"--reveal-projects",
|
|
10947
|
-
action="store_true",
|
|
10948
|
-
dest="reveal_projects",
|
|
10949
|
-
help="In --format output, show real project basenames instead of "
|
|
10950
|
-
"the default project-1, project-2, ... anonymization.",
|
|
10951
|
-
)
|
|
10952
|
-
mo.add_argument(
|
|
10953
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10954
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
10955
|
-
"Overrides config display.tz for this call.",
|
|
10956
|
-
)
|
|
10957
|
-
_add_ccusage_alias_args(mo, ansi_emit=False)
|
|
10958
|
-
_add_share_args(mo)
|
|
10959
|
-
mo.set_defaults(func=cmd_monthly)
|
|
11297
|
+
_build_monthly_parser(
|
|
11298
|
+
sub, "monthly",
|
|
11299
|
+
help_text="Show usage report grouped by month",
|
|
11300
|
+
xref="Alias of `cctally claude monthly` (the canonical form).")
|
|
10960
11301
|
|
|
10961
11302
|
# -- weekly --
|
|
10962
|
-
|
|
10963
|
-
"weekly",
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
description="Show Claude usage grouped by subscription week. Boundaries are anchored "
|
|
10967
|
-
"to weekly_usage_snapshots.week_start_at with 7-day-cadence extrapolation "
|
|
10968
|
-
"for pre-snapshot history. Columns extend daily/monthly's set with Used % "
|
|
10969
|
-
"and $/1%.",
|
|
10970
|
-
epilog=textwrap.dedent("""\
|
|
10971
|
-
Examples:
|
|
10972
|
-
cctally weekly
|
|
10973
|
-
cctally weekly --since 20260101
|
|
10974
|
-
cctally weekly --breakdown
|
|
10975
|
-
cctally weekly --json
|
|
10976
|
-
cctally weekly --order desc
|
|
10977
|
-
"""),
|
|
10978
|
-
)
|
|
10979
|
-
we.add_argument("-s", "--since", default=None, metavar="YYYYMMDD",
|
|
10980
|
-
help="Filter from date (inclusive).")
|
|
10981
|
-
we.add_argument("-u", "--until", default=None, metavar="YYYYMMDD",
|
|
10982
|
-
help="Filter until date (inclusive).")
|
|
10983
|
-
we.add_argument("-b", "--breakdown", action="store_true",
|
|
10984
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
10985
|
-
we.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
10986
|
-
help="Sort direction by week (default: asc).")
|
|
10987
|
-
we.add_argument("--reveal-projects", action="store_true", dest="reveal_projects",
|
|
10988
|
-
help="In --format output, show real project basenames instead of "
|
|
10989
|
-
"the default project-1, project-2, ... anonymization.")
|
|
10990
|
-
we.add_argument("--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
10991
|
-
help="Display timezone: local, utc, or IANA name. "
|
|
10992
|
-
"Overrides config display.tz for this call.")
|
|
10993
|
-
_add_ccusage_alias_args(we, ansi_emit=False)
|
|
10994
|
-
_add_share_args(we)
|
|
10995
|
-
we.set_defaults(func=cmd_weekly)
|
|
10996
|
-
|
|
10997
|
-
# -- codex shared args helper --
|
|
10998
|
-
def _add_codex_shared_args(parser: argparse.ArgumentParser) -> None:
|
|
10999
|
-
"""Register upstream `ccusage-codex sharedArgs` on a codex subparser.
|
|
11000
|
-
|
|
11001
|
-
Upstream sharedArgs (node_modules/@ccusage/codex/dist/index.js):
|
|
11002
|
-
--timezone/-z, --locale/-l, --compact, --color, --noColor,
|
|
11003
|
-
--offline/--no-offline.
|
|
11004
|
-
|
|
11005
|
-
Honored here: --timezone (dates + aggregation buckets) and
|
|
11006
|
-
--compact (table layout). Accepted-but-no-op (stored on the
|
|
11007
|
-
namespace for drop-in parity with upstream scripts): --locale
|
|
11008
|
-
(we don't locale-format dates), --color / --noColor (we don't
|
|
11009
|
-
emit ANSI codes today). --offline is accepted as a no-op too
|
|
11010
|
-
(we are always offline); it uses BooleanOptionalAction so
|
|
11011
|
-
`--no-offline` also parses cleanly. `-O` is kept as the short
|
|
11012
|
-
form for offline for backward compat with earlier builds.
|
|
11013
|
-
"""
|
|
11014
|
-
parser.add_argument(
|
|
11015
|
-
"-z", "--timezone", default=None, metavar="TZ",
|
|
11016
|
-
help="IANA timezone for date bucketing and Date/Last Activity cells.",
|
|
11017
|
-
)
|
|
11018
|
-
parser.add_argument(
|
|
11019
|
-
"-l", "--locale", default=None, metavar="LOCALE",
|
|
11020
|
-
help="Accepted for drop-in compat; no-op (dates are not locale-formatted).",
|
|
11021
|
-
)
|
|
11022
|
-
parser.add_argument(
|
|
11023
|
-
"--compact", action="store_true",
|
|
11024
|
-
help="Force compact table layout regardless of terminal width.",
|
|
11025
|
-
)
|
|
11026
|
-
parser.add_argument(
|
|
11027
|
-
"--color", action="store_true",
|
|
11028
|
-
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
11029
|
-
)
|
|
11030
|
-
parser.add_argument(
|
|
11031
|
-
"--noColor", action="store_true", dest="no_color",
|
|
11032
|
-
help="Accepted for drop-in compat; no-op today (no ANSI escapes are emitted).",
|
|
11033
|
-
)
|
|
11034
|
-
parser.add_argument(
|
|
11035
|
-
"-O", "--offline", action=argparse.BooleanOptionalAction, default=False,
|
|
11036
|
-
help="Accepted for drop-in compat with ccusage-codex; we are always offline.",
|
|
11037
|
-
)
|
|
11038
|
-
parser.add_argument(
|
|
11039
|
-
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
11040
|
-
help="Display timezone: local, utc, or IANA name. Overrides "
|
|
11041
|
-
"config display.tz for this call. Takes precedence over "
|
|
11042
|
-
"upstream's --timezone for drop-in parity.",
|
|
11043
|
-
)
|
|
11044
|
-
# Issue #92: codex parity for the #89 --debug surface. Codex JSONL
|
|
11045
|
-
# has no recorded costUSD to diff against, so the report is the
|
|
11046
|
-
# codex variant ("Codex Pricing Debug Report": totals + top-N
|
|
11047
|
-
# highest computed-cost entries), wired via
|
|
11048
|
-
# _emit_codex_debug_samples_if_set in each cmd_codex_* body.
|
|
11049
|
-
parser.add_argument(
|
|
11050
|
-
"-d", "--debug", action="store_true",
|
|
11051
|
-
help="Emit a stderr 'Codex Pricing Debug Report' (totals + "
|
|
11052
|
-
"the N highest computed-cost sample entries).",
|
|
11053
|
-
)
|
|
11054
|
-
parser.add_argument(
|
|
11055
|
-
"--debug-samples", type=_nonneg_int, default=5, metavar="N",
|
|
11056
|
-
help="Cap on top-entry sample rows in the --debug report "
|
|
11057
|
-
"(default 5; N=0 suppresses the sample block; "
|
|
11058
|
-
"negatives rejected at parse time).",
|
|
11059
|
-
)
|
|
11303
|
+
_build_weekly_parser(
|
|
11304
|
+
sub, "weekly",
|
|
11305
|
+
help_text="Show usage grouped by subscription week (with Used %% and $/1%%)",
|
|
11306
|
+
xref="Alias of `cctally claude weekly` (the canonical form).")
|
|
11060
11307
|
|
|
11061
11308
|
# -- codex-daily --
|
|
11062
|
-
|
|
11063
|
-
"codex-daily",
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
description="Show Codex usage grouped by date, matching upstream ccusage-codex daily output.",
|
|
11067
|
-
epilog=textwrap.dedent("""\
|
|
11068
|
-
Examples:
|
|
11069
|
-
cctally codex-daily --since 20260401
|
|
11070
|
-
cctally codex-daily --since 20260401 --breakdown
|
|
11071
|
-
cctally codex-daily --since 20260401 --json
|
|
11072
|
-
cctally codex-daily --order desc
|
|
11073
|
-
"""),
|
|
11074
|
-
)
|
|
11075
|
-
cd.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11076
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11077
|
-
cd.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11078
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11079
|
-
cd.add_argument("-b", "--breakdown", action="store_true",
|
|
11080
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
11081
|
-
cd.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11082
|
-
help="Sort direction by date (default: asc).")
|
|
11083
|
-
cd.add_argument("--json", action="store_true", dest="json",
|
|
11084
|
-
help="Output JSON matching upstream ccusage-codex daily format.")
|
|
11085
|
-
_add_codex_shared_args(cd)
|
|
11086
|
-
cd.set_defaults(func=cmd_codex_daily)
|
|
11309
|
+
_build_codex_daily_parser(
|
|
11310
|
+
sub, "codex-daily",
|
|
11311
|
+
help_text="Show Codex usage report grouped by date (drop-in for `ccusage-codex daily`)",
|
|
11312
|
+
xref="Alias of `cctally codex daily` (the canonical form).")
|
|
11087
11313
|
|
|
11088
11314
|
# -- codex-monthly --
|
|
11089
|
-
|
|
11090
|
-
"codex-monthly",
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
description="Show Codex usage grouped by calendar month, matching upstream ccusage-codex monthly output.",
|
|
11094
|
-
epilog=textwrap.dedent("""\
|
|
11095
|
-
Examples:
|
|
11096
|
-
cctally codex-monthly --since 20260101
|
|
11097
|
-
cctally codex-monthly --breakdown
|
|
11098
|
-
cctally codex-monthly --json
|
|
11099
|
-
"""),
|
|
11100
|
-
)
|
|
11101
|
-
cmn.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11102
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11103
|
-
cmn.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11104
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11105
|
-
cmn.add_argument("-b", "--breakdown", action="store_true",
|
|
11106
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
11107
|
-
cmn.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11108
|
-
help="Sort direction by month (default: asc).")
|
|
11109
|
-
cmn.add_argument("--json", action="store_true", dest="json",
|
|
11110
|
-
help="Output JSON matching upstream ccusage-codex monthly format.")
|
|
11111
|
-
_add_codex_shared_args(cmn)
|
|
11112
|
-
cmn.set_defaults(func=cmd_codex_monthly)
|
|
11315
|
+
_build_codex_monthly_parser(
|
|
11316
|
+
sub, "codex-monthly",
|
|
11317
|
+
help_text="Show Codex usage grouped by month (drop-in for `ccusage-codex monthly`)",
|
|
11318
|
+
xref="Alias of `cctally codex monthly` (the canonical form).")
|
|
11113
11319
|
|
|
11114
11320
|
# -- codex-weekly --
|
|
11115
|
-
|
|
11116
|
-
"codex-weekly",
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
description="Show Codex usage grouped by week. Week-start day is read from config.json "
|
|
11120
|
-
"(collector.week_start, Monday default). Not a ccusage-codex drop-in — "
|
|
11121
|
-
"upstream has no `codex weekly` command.",
|
|
11122
|
-
epilog=textwrap.dedent("""\
|
|
11123
|
-
Examples:
|
|
11124
|
-
cctally codex-weekly
|
|
11125
|
-
cctally codex-weekly --since 20260301
|
|
11126
|
-
cctally codex-weekly --breakdown
|
|
11127
|
-
cctally codex-weekly --json
|
|
11128
|
-
cctally codex-weekly --order desc
|
|
11129
|
-
"""),
|
|
11130
|
-
)
|
|
11131
|
-
cw.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11132
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11133
|
-
cw.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11134
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11135
|
-
cw.add_argument("-b", "--breakdown", action="store_true",
|
|
11136
|
-
help="Show per-model cost breakdown sub-rows.")
|
|
11137
|
-
cw.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11138
|
-
help="Sort direction by week (default: asc).")
|
|
11139
|
-
cw.add_argument("--json", action="store_true", dest="json",
|
|
11140
|
-
help="Output JSON.")
|
|
11141
|
-
_add_codex_shared_args(cw)
|
|
11142
|
-
cw.set_defaults(func=cmd_codex_weekly)
|
|
11321
|
+
_build_codex_weekly_parser(
|
|
11322
|
+
sub, "codex-weekly",
|
|
11323
|
+
help_text="Show Codex usage grouped by week (week-start from config.json)",
|
|
11324
|
+
xref="Alias of `cctally codex weekly` (the canonical form).")
|
|
11143
11325
|
|
|
11144
11326
|
# -- codex-session --
|
|
11145
|
-
|
|
11146
|
-
"codex-session",
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
description="Show Codex usage grouped by session, matching upstream ccusage-codex session output.",
|
|
11150
|
-
epilog=textwrap.dedent("""\
|
|
11151
|
-
Examples:
|
|
11152
|
-
cctally codex-session
|
|
11153
|
-
cctally codex-session --since 20260401
|
|
11154
|
-
cctally codex-session --json
|
|
11155
|
-
"""),
|
|
11156
|
-
)
|
|
11157
|
-
cs.add_argument("-s", "--since", default=None, metavar="YYYY-MM-DD",
|
|
11158
|
-
help="Filter from date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11159
|
-
cs.add_argument("-u", "--until", default=None, metavar="YYYY-MM-DD",
|
|
11160
|
-
help="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
11161
|
-
cs.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
11162
|
-
help="Sort direction by last activity (default: asc — earliest first).")
|
|
11163
|
-
cs.add_argument("--json", action="store_true", dest="json",
|
|
11164
|
-
help="Output JSON matching upstream ccusage-codex session format.")
|
|
11165
|
-
_add_codex_shared_args(cs)
|
|
11166
|
-
cs.set_defaults(func=cmd_codex_session)
|
|
11327
|
+
_build_codex_session_parser(
|
|
11328
|
+
sub, "codex-session",
|
|
11329
|
+
help_text="Show Codex usage grouped by session (drop-in for `ccusage-codex session`)",
|
|
11330
|
+
xref="Alias of `cctally codex session` (the canonical form).")
|
|
11167
11331
|
|
|
11168
11332
|
# -- project --
|
|
11169
11333
|
p_project = sub.add_parser(
|
|
@@ -11261,51 +11425,62 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
11261
11425
|
diff_p.set_defaults(func=cmd_diff)
|
|
11262
11426
|
|
|
11263
11427
|
# -- session --
|
|
11264
|
-
|
|
11265
|
-
"session",
|
|
11266
|
-
|
|
11428
|
+
_build_session_parser(
|
|
11429
|
+
sub, "session",
|
|
11430
|
+
help_text="Show Claude usage grouped by sessionId (merges resumed-across-files sessions)",
|
|
11431
|
+
xref="Alias of `cctally claude session` (the canonical form).")
|
|
11432
|
+
|
|
11433
|
+
# --- `claude` subgroup (drop-in for `ccusage claude …`); issue #86 Session B ---
|
|
11434
|
+
# Build-once, register-twice: these reuse the same nine builders as the flat
|
|
11435
|
+
# forms above. Nested subparsers reuse dest="command" so args.command resolves
|
|
11436
|
+
# to the leaf name (e.g. "blocks"), keeping banner suppression byte-identical
|
|
11437
|
+
# to the flat form with zero hook-path changes.
|
|
11438
|
+
claude_p = sub.add_parser(
|
|
11439
|
+
"claude",
|
|
11440
|
+
help="Claude-source reports (drop-in for `ccusage claude …`)",
|
|
11267
11441
|
formatter_class=CLIHelpFormatter,
|
|
11268
|
-
description="
|
|
11269
|
-
"
|
|
11270
|
-
"
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11291
|
-
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
|
|
11304
|
-
|
|
11305
|
-
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11442
|
+
description="Claude-source usage reports. Each subcommand is a drop-in for the "
|
|
11443
|
+
"matching `ccusage claude <cmd>` and shares its engine with the "
|
|
11444
|
+
"top-level `cctally <cmd>` alias.")
|
|
11445
|
+
claude_sub = claude_p.add_subparsers(dest="command", required=True, metavar="<command>")
|
|
11446
|
+
_build_daily_parser(claude_sub, "daily",
|
|
11447
|
+
help_text="Show usage grouped by date",
|
|
11448
|
+
xref="Drop-in for `ccusage claude daily`. Same engine as `cctally daily`.")
|
|
11449
|
+
_build_monthly_parser(claude_sub, "monthly",
|
|
11450
|
+
help_text="Show usage grouped by month",
|
|
11451
|
+
xref="Drop-in for `ccusage claude monthly`. Same engine as `cctally monthly`.")
|
|
11452
|
+
_build_weekly_parser(claude_sub, "weekly",
|
|
11453
|
+
help_text="Show usage grouped by subscription week",
|
|
11454
|
+
xref="Drop-in for `ccusage claude weekly`. Same engine as `cctally weekly`.")
|
|
11455
|
+
_build_session_parser(claude_sub, "session",
|
|
11456
|
+
help_text="Show usage grouped by session",
|
|
11457
|
+
xref="Drop-in for `ccusage claude session`. Same engine as `cctally session`.")
|
|
11458
|
+
_build_blocks_parser(claude_sub, "blocks",
|
|
11459
|
+
help_text="Show usage grouped by 5-hour session blocks",
|
|
11460
|
+
xref="Drop-in for `ccusage claude blocks`. Same engine as `cctally blocks`.")
|
|
11461
|
+
|
|
11462
|
+
# --- `codex` subgroup (drop-in for `ccusage codex …`); issue #86 Session B ---
|
|
11463
|
+
codex_p = sub.add_parser(
|
|
11464
|
+
"codex",
|
|
11465
|
+
help="Codex-source reports (drop-in for `ccusage codex …`)",
|
|
11466
|
+
formatter_class=CLIHelpFormatter,
|
|
11467
|
+
description="Codex-source usage reports. daily/monthly/session are drop-ins for "
|
|
11468
|
+
"`ccusage codex <cmd>`; weekly is a cctally extension. Each shares its "
|
|
11469
|
+
"engine with the matching `cctally codex-<cmd>` alias.")
|
|
11470
|
+
codex_sub = codex_p.add_subparsers(dest="command", required=True, metavar="<command>")
|
|
11471
|
+
_build_codex_daily_parser(codex_sub, "daily",
|
|
11472
|
+
help_text="Show Codex usage grouped by date",
|
|
11473
|
+
xref="Drop-in for `ccusage codex daily`. Same engine as `cctally codex-daily`.")
|
|
11474
|
+
_build_codex_monthly_parser(codex_sub, "monthly",
|
|
11475
|
+
help_text="Show Codex usage grouped by month",
|
|
11476
|
+
xref="Drop-in for `ccusage codex monthly`. Same engine as `cctally codex-monthly`.")
|
|
11477
|
+
_build_codex_session_parser(codex_sub, "session",
|
|
11478
|
+
help_text="Show Codex usage grouped by session",
|
|
11479
|
+
xref="Drop-in for `ccusage codex session`. Same engine as `cctally codex-session`.")
|
|
11480
|
+
_build_codex_weekly_parser(codex_sub, "weekly",
|
|
11481
|
+
help_text="Show Codex usage grouped by week",
|
|
11482
|
+
xref="cctally extension (no upstream `ccusage codex weekly`). Same engine as "
|
|
11483
|
+
"`cctally codex-weekly`.")
|
|
11309
11484
|
|
|
11310
11485
|
# ---- config (persisted user preferences) ----
|
|
11311
11486
|
cfg_p = sub.add_parser(
|
|
@@ -11448,6 +11623,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
11448
11623
|
help="Skip confirmations")
|
|
11449
11624
|
sp.add_argument("--json", action="store_true",
|
|
11450
11625
|
help="Emit machine-readable output")
|
|
11626
|
+
sp.add_argument("--force-dev", action="store_true", dest="force_dev",
|
|
11627
|
+
help="Allow setup to run from a dev checkout (writes "
|
|
11628
|
+
"dev-pointing hooks into ~/.claude/settings.json)")
|
|
11451
11629
|
# Legacy bespoke-hook migration flags (install-mode only — see cmd_setup
|
|
11452
11630
|
# post-parse validation). Spec Section 2 mode×flag matrix.
|
|
11453
11631
|
mig_group = sp.add_mutually_exclusive_group()
|
|
@@ -13495,10 +13673,19 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
13495
13673
|
# works without a subcommand (`cctally --version`).
|
|
13496
13674
|
if getattr(args, "version", False):
|
|
13497
13675
|
v = _lib_changelog._read_latest_changelog_version()
|
|
13498
|
-
if v is None
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13676
|
+
base = "cctally unknown" if v is None else f"cctally {v[0]}"
|
|
13677
|
+
# Dev-instance isolation (§4, P3): append the dev marker + resolved
|
|
13678
|
+
# data dir whenever running from a checkout — keyed on
|
|
13679
|
+
# _is_dev_checkout(), NOT DEV_MODE, so the CCTALLY_DATA_DIR
|
|
13680
|
+
# override-on-checkout case (DEV_MODE False) still shows the marker
|
|
13681
|
+
# instead of masquerading as the installed binary. Prod (no .git)
|
|
13682
|
+
# output is unchanged. The override case is labelled distinctly so
|
|
13683
|
+
# the user can tell auto-detect (cctally-dev) from an explicit dir.
|
|
13684
|
+
if _cctally_core.DEV_MODE:
|
|
13685
|
+
base += f" (dev — {_cctally_core.APP_DIR})"
|
|
13686
|
+
elif _cctally_core._is_dev_checkout():
|
|
13687
|
+
base += f" (dev checkout, custom data dir — {_cctally_core.APP_DIR})"
|
|
13688
|
+
print(base)
|
|
13502
13689
|
return 0
|
|
13503
13690
|
if not getattr(args, "func", None):
|
|
13504
13691
|
parser.error("a subcommand is required")
|