cctally 1.68.0 → 1.69.1
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 +61 -0
- package/bin/_cctally_alerts.py +54 -2
- package/bin/_cctally_cache.py +644 -107
- package/bin/_cctally_cache_report.py +41 -17
- package/bin/_cctally_codex.py +109 -4
- package/bin/_cctally_config.py +368 -2
- package/bin/_cctally_core.py +168 -5
- package/bin/_cctally_dashboard.py +679 -5
- package/bin/_cctally_dashboard_conversation.py +9 -4
- package/bin/_cctally_dashboard_envelope.py +110 -1
- package/bin/_cctally_dashboard_share.py +557 -79
- package/bin/_cctally_db.py +303 -17
- package/bin/_cctally_diff.py +49 -16
- package/bin/_cctally_doctor.py +118 -0
- package/bin/_cctally_forecast.py +12 -4
- package/bin/_cctally_parser.py +298 -92
- package/bin/_cctally_project.py +24 -8
- package/bin/_cctally_quota.py +1381 -0
- package/bin/_cctally_record.py +319 -14
- package/bin/_cctally_refresh.py +105 -3
- package/bin/_cctally_reporting.py +7 -1
- package/bin/_cctally_setup.py +343 -113
- package/bin/_cctally_statusline.py +228 -0
- package/bin/_cctally_tui.py +787 -7
- package/bin/_lib_alert_axes.py +11 -0
- package/bin/_lib_codex_hooks.py +367 -0
- package/bin/_lib_conversation_query.py +156 -67
- package/bin/_lib_doctor.py +202 -0
- package/bin/_lib_jsonl.py +529 -162
- package/bin/_lib_quota.py +566 -0
- package/bin/_lib_share.py +152 -34
- package/bin/_lib_snapshot_cache.py +26 -0
- package/bin/_lib_source_identity.py +74 -0
- package/bin/cctally +324 -10
- package/dashboard/static/assets/index-CBbErI-P.js +80 -0
- package/dashboard/static/assets/index-kDDVOLa_.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +5 -1
- package/dashboard/static/assets/index-BybNp_Di.js +0 -80
- package/dashboard/static/assets/index-DgAmLK65.css +0 -1
package/bin/_cctally_parser.py
CHANGED
|
@@ -281,7 +281,30 @@ def _add_codex_shared_args(parser: argparse.ArgumentParser) -> None:
|
|
|
281
281
|
)
|
|
282
282
|
|
|
283
283
|
|
|
284
|
-
def
|
|
284
|
+
def _add_source_args(
|
|
285
|
+
parser: argparse.ArgumentParser, *, fixed_source: str | None = None,
|
|
286
|
+
speed: bool = False,
|
|
287
|
+
) -> None:
|
|
288
|
+
"""Attach the source selector or pin a nested provider alias."""
|
|
289
|
+
if fixed_source is None:
|
|
290
|
+
parser.add_argument(
|
|
291
|
+
"--source", choices=("claude", "codex", "all"), default="claude",
|
|
292
|
+
help="Analytics provider: claude (default), codex, or all.",
|
|
293
|
+
)
|
|
294
|
+
else:
|
|
295
|
+
if fixed_source not in {"claude", "codex"}:
|
|
296
|
+
raise ValueError(f"unsupported fixed source {fixed_source!r}")
|
|
297
|
+
parser.set_defaults(source=fixed_source)
|
|
298
|
+
if speed:
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
"--speed", choices=("auto", "standard", "fast"), default="auto",
|
|
301
|
+
help="Codex pricing tier for Codex and all-source requests.",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _add_share_args(
|
|
306
|
+
parser, *, has_status_line: bool = False, json_dest: str = "json",
|
|
307
|
+
) -> None:
|
|
285
308
|
"""Attach shareable-reports flags + format/json mutex to a subparser.
|
|
286
309
|
|
|
287
310
|
Idempotent — call exactly once per subparser. Caller MUST remove any
|
|
@@ -310,7 +333,7 @@ def _add_share_args(parser, *, has_status_line: bool = False) -> None:
|
|
|
310
333
|
help="Render output as shareable markdown, self-contained HTML, or SVG. "
|
|
311
334
|
"Default destination: md->stdout, html/svg->~/Downloads file.")
|
|
312
335
|
output_group.add_argument(
|
|
313
|
-
"--json", action="store_true",
|
|
336
|
+
"--json", action="store_true", dest=json_dest,
|
|
314
337
|
help="Emit machine-readable JSON; suppresses terminal render.")
|
|
315
338
|
if has_status_line:
|
|
316
339
|
output_group.add_argument(
|
|
@@ -926,9 +949,10 @@ def _build_codex_daily_parser(subparsers, name, *, help_text, xref):
|
|
|
926
949
|
help="Show per-model cost breakdown sub-rows.")
|
|
927
950
|
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
928
951
|
help="Sort direction by date (default: asc).")
|
|
929
|
-
p.add_argument("--json", action="store_true", dest="json",
|
|
930
|
-
help="Output JSON matching upstream ccusage-codex daily format.")
|
|
931
952
|
_add_codex_shared_args(p)
|
|
953
|
+
p.add_argument("--config", default=None, metavar="PATH",
|
|
954
|
+
help="Read configuration from PATH without writing it.")
|
|
955
|
+
_add_share_args(p)
|
|
932
956
|
p.set_defaults(func=c.cmd_codex_daily)
|
|
933
957
|
return p
|
|
934
958
|
|
|
@@ -957,9 +981,10 @@ def _build_codex_monthly_parser(subparsers, name, *, help_text, xref):
|
|
|
957
981
|
help="Show per-model cost breakdown sub-rows.")
|
|
958
982
|
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
959
983
|
help="Sort direction by month (default: asc).")
|
|
960
|
-
p.add_argument("--json", action="store_true", dest="json",
|
|
961
|
-
help="Output JSON matching upstream ccusage-codex monthly format.")
|
|
962
984
|
_add_codex_shared_args(p)
|
|
985
|
+
p.add_argument("--config", default=None, metavar="PATH",
|
|
986
|
+
help="Read configuration from PATH without writing it.")
|
|
987
|
+
_add_share_args(p)
|
|
963
988
|
p.set_defaults(func=c.cmd_codex_monthly)
|
|
964
989
|
return p
|
|
965
990
|
|
|
@@ -992,9 +1017,10 @@ def _build_codex_weekly_parser(subparsers, name, *, help_text, xref):
|
|
|
992
1017
|
help="Show per-model cost breakdown sub-rows.")
|
|
993
1018
|
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
994
1019
|
help="Sort direction by week (default: asc).")
|
|
995
|
-
p.add_argument("--json", action="store_true", dest="json",
|
|
996
|
-
help="Output JSON.")
|
|
997
1020
|
_add_codex_shared_args(p)
|
|
1021
|
+
p.add_argument("--config", default=None, metavar="PATH",
|
|
1022
|
+
help="Read configuration from PATH without writing it.")
|
|
1023
|
+
_add_share_args(p)
|
|
998
1024
|
p.set_defaults(func=c.cmd_codex_weekly)
|
|
999
1025
|
return p
|
|
1000
1026
|
|
|
@@ -1021,18 +1047,109 @@ def _build_codex_session_parser(subparsers, name, *, help_text, xref):
|
|
|
1021
1047
|
help_until="Filter until date (inclusive; accepts YYYY-MM-DD or YYYYMMDD).")
|
|
1022
1048
|
p.add_argument("-o", "--order", choices=("asc", "desc"), default="asc",
|
|
1023
1049
|
help="Sort direction by last activity (default: asc — earliest first).")
|
|
1024
|
-
p.add_argument("--json", action="store_true", dest="json",
|
|
1025
|
-
help="Output JSON matching upstream ccusage-codex session format.")
|
|
1026
1050
|
_add_codex_shared_args(p)
|
|
1051
|
+
p.add_argument("--config", default=None, metavar="PATH",
|
|
1052
|
+
help="Read configuration from PATH without writing it.")
|
|
1053
|
+
_add_share_args(p)
|
|
1027
1054
|
p.set_defaults(func=c.cmd_codex_session)
|
|
1028
1055
|
return p
|
|
1029
1056
|
|
|
1030
1057
|
|
|
1058
|
+
def _add_codex_quota_common_args(parser) -> None:
|
|
1059
|
+
"""Attach the shared exact-selector/reporting surface for native quotas."""
|
|
1060
|
+
parser.add_argument(
|
|
1061
|
+
"--root-key", dest="root_key", metavar="FULL_SOURCE_ROOT_KEY",
|
|
1062
|
+
help="Exact case-sensitive sourceRootKey selector (no prefix matching).",
|
|
1063
|
+
)
|
|
1064
|
+
parser.add_argument(
|
|
1065
|
+
"--limit-key", dest="limit_key", metavar="FULL_LOGICAL_LIMIT_KEY",
|
|
1066
|
+
help="Exact case-sensitive logicalLimitKey selector (no prefix matching).",
|
|
1067
|
+
)
|
|
1068
|
+
parser.add_argument(
|
|
1069
|
+
"--no-sync", action="store_true",
|
|
1070
|
+
help="Read retained local-rollout quota evidence without a Codex cache sync.",
|
|
1071
|
+
)
|
|
1072
|
+
parser.add_argument(
|
|
1073
|
+
"--config", default=None, metavar="PATH",
|
|
1074
|
+
help="Read display settings from PATH for this invocation only.",
|
|
1075
|
+
)
|
|
1076
|
+
parser.add_argument(
|
|
1077
|
+
"--json", action="store_true",
|
|
1078
|
+
help="Emit stamped schemaVersion:1 JSON.",
|
|
1079
|
+
)
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def _build_codex_quota_parser(subparsers, name, *, help_text, xref=None):
|
|
1083
|
+
"""Build canonical `cctally codex quota …` leaves (issue #294 S2)."""
|
|
1084
|
+
c = _cctally()
|
|
1085
|
+
quota = subparsers.add_parser(
|
|
1086
|
+
name, help=help_text, formatter_class=CLIHelpFormatter,
|
|
1087
|
+
description=(
|
|
1088
|
+
"Native Codex quota history, current status, forecast, reset blocks, "
|
|
1089
|
+
"and percent-crossing breakdowns. Each window remains root-qualified; "
|
|
1090
|
+
"independent percentages are never combined."
|
|
1091
|
+
),
|
|
1092
|
+
)
|
|
1093
|
+
leaves = quota.add_subparsers(dest="quota_command", required=True, metavar="<command>")
|
|
1094
|
+
|
|
1095
|
+
history = leaves.add_parser("history", help="Show physical local-rollout quota history",
|
|
1096
|
+
formatter_class=CLIHelpFormatter)
|
|
1097
|
+
_add_since_until_args(
|
|
1098
|
+
history, metavar_since="DATE_OR_ISO", metavar_until="DATE_OR_ISO",
|
|
1099
|
+
help_since="Inclusive start; date-only is interpreted in display.tz.",
|
|
1100
|
+
help_until="Exclusive end; date-only is interpreted in display.tz.",
|
|
1101
|
+
)
|
|
1102
|
+
_add_codex_quota_common_args(history)
|
|
1103
|
+
history.set_defaults(func=c.cmd_codex_quota_history)
|
|
1104
|
+
|
|
1105
|
+
statusline = leaves.add_parser("statusline", help="Show one native status segment per quota identity",
|
|
1106
|
+
formatter_class=CLIHelpFormatter)
|
|
1107
|
+
statusline.add_argument(
|
|
1108
|
+
"--as-of", default=None, metavar="ISO-8601",
|
|
1109
|
+
help="Interpret retained local evidence at this instant (naive means UTC).",
|
|
1110
|
+
)
|
|
1111
|
+
_add_codex_quota_common_args(statusline)
|
|
1112
|
+
statusline.set_defaults(func=c.cmd_codex_quota_statusline)
|
|
1113
|
+
|
|
1114
|
+
forecast = leaves.add_parser("forecast", help="Forecast each native quota reset window",
|
|
1115
|
+
formatter_class=CLIHelpFormatter)
|
|
1116
|
+
forecast.add_argument(
|
|
1117
|
+
"--as-of", default=None, metavar="ISO-8601",
|
|
1118
|
+
help="Forecast at this instant (naive means UTC).",
|
|
1119
|
+
)
|
|
1120
|
+
_add_codex_quota_common_args(forecast)
|
|
1121
|
+
forecast.set_defaults(func=c.cmd_codex_quota_forecast)
|
|
1122
|
+
|
|
1123
|
+
blocks = leaves.add_parser("blocks", help="Show native reset blocks",
|
|
1124
|
+
formatter_class=CLIHelpFormatter)
|
|
1125
|
+
_add_since_until_args(
|
|
1126
|
+
blocks, metavar_since="DATE_OR_ISO", metavar_until="DATE_OR_ISO",
|
|
1127
|
+
help_since="Inclusive start; date-only is interpreted in display.tz.",
|
|
1128
|
+
help_until="Exclusive end; date-only is interpreted in display.tz.",
|
|
1129
|
+
)
|
|
1130
|
+
_add_codex_quota_common_args(blocks)
|
|
1131
|
+
blocks.set_defaults(func=c.cmd_codex_quota_blocks)
|
|
1132
|
+
|
|
1133
|
+
breakdown = leaves.add_parser("breakdown", help="Correlate one native block's percent crossings",
|
|
1134
|
+
formatter_class=CLIHelpFormatter)
|
|
1135
|
+
breakdown.add_argument(
|
|
1136
|
+
"--reset-at", required=True, metavar="ISO-8601",
|
|
1137
|
+
help="Exact block reset timestamp; date-only is rejected and naive means UTC.",
|
|
1138
|
+
)
|
|
1139
|
+
breakdown.add_argument(
|
|
1140
|
+
"--speed", choices=("auto", "standard", "fast"), default="auto",
|
|
1141
|
+
help="Codex pricing tier for query-time cost correlation (default: auto).",
|
|
1142
|
+
)
|
|
1143
|
+
_add_codex_quota_common_args(breakdown)
|
|
1144
|
+
breakdown.set_defaults(func=c.cmd_codex_quota_breakdown)
|
|
1145
|
+
return quota
|
|
1146
|
+
|
|
1147
|
+
|
|
1031
1148
|
def _build_sync_week_parser(subparsers, name, *, help_text, xref=None):
|
|
1032
1149
|
"""Build the `sync-week` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
1033
1150
|
|
|
1034
|
-
Move-only extraction of the former inline build_parser() block
|
|
1035
|
-
call-time `c = _cctally()` binding
|
|
1151
|
+
Move-only extraction of the former inline build_parser() block with
|
|
1152
|
+
call-time `c = _cctally()` binding.
|
|
1036
1153
|
"""
|
|
1037
1154
|
c = _cctally()
|
|
1038
1155
|
py = subparsers.add_parser(
|
|
@@ -1104,11 +1221,11 @@ def _build_sync_week_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1104
1221
|
)
|
|
1105
1222
|
py.set_defaults(func=c.cmd_sync_week)
|
|
1106
1223
|
|
|
1107
|
-
def _build_report_parser(subparsers, name, *, help_text, xref=None):
|
|
1224
|
+
def _build_report_parser(subparsers, name, *, help_text, xref=None, fixed_source=None):
|
|
1108
1225
|
"""Build the `report` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
1109
1226
|
|
|
1110
|
-
Move-only extraction of the former inline build_parser() block
|
|
1111
|
-
call-time `c = _cctally()` binding
|
|
1227
|
+
Move-only extraction of the former inline build_parser() block with
|
|
1228
|
+
call-time `c = _cctally()` binding.
|
|
1112
1229
|
"""
|
|
1113
1230
|
c = _cctally()
|
|
1114
1231
|
pr = subparsers.add_parser(
|
|
@@ -1116,6 +1233,14 @@ def _build_report_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1116
1233
|
help=help_text,
|
|
1117
1234
|
formatter_class=CLIHelpFormatter,
|
|
1118
1235
|
description=textwrap.dedent(
|
|
1236
|
+
(
|
|
1237
|
+
"""\
|
|
1238
|
+
Report Codex quota-window dollars per percent.
|
|
1239
|
+
|
|
1240
|
+
Each native quota window retains its own reset identity; the
|
|
1241
|
+
report never invents Claude subscription weeks.
|
|
1242
|
+
"""
|
|
1243
|
+
if fixed_source == "codex" else
|
|
1119
1244
|
"""\
|
|
1120
1245
|
Report current and historical dollars per 1% weekly usage.
|
|
1121
1246
|
|
|
@@ -1124,49 +1249,64 @@ def _build_report_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1124
1249
|
- latest cost snapshot (USD)
|
|
1125
1250
|
then computes USD / percent.
|
|
1126
1251
|
"""
|
|
1127
|
-
|
|
1252
|
+
)
|
|
1253
|
+
),
|
|
1128
1254
|
epilog=textwrap.dedent(
|
|
1255
|
+
(
|
|
1256
|
+
"""\
|
|
1257
|
+
Examples:
|
|
1258
|
+
cctally codex report
|
|
1259
|
+
cctally codex report --sync-current
|
|
1260
|
+
cctally codex report --weeks 12 --json
|
|
1261
|
+
"""
|
|
1262
|
+
if fixed_source == "codex" else
|
|
1129
1263
|
"""\
|
|
1130
1264
|
Examples:
|
|
1131
1265
|
cctally report
|
|
1132
1266
|
cctally report --sync-current
|
|
1133
1267
|
cctally report --weeks 12 --json
|
|
1134
1268
|
"""
|
|
1135
|
-
|
|
1269
|
+
)
|
|
1270
|
+
),
|
|
1136
1271
|
)
|
|
1137
1272
|
pr.add_argument(
|
|
1138
1273
|
"--weeks",
|
|
1139
1274
|
type=int,
|
|
1140
1275
|
default=8,
|
|
1141
|
-
help="How many recent
|
|
1276
|
+
help=("How many recent native Codex quota windows to include in the trend."
|
|
1277
|
+
if fixed_source == "codex" else
|
|
1278
|
+
"How many recent week windows to include in the trend."),
|
|
1142
1279
|
)
|
|
1143
1280
|
pr.add_argument(
|
|
1144
1281
|
"--sync-current",
|
|
1145
1282
|
action="store_true",
|
|
1146
|
-
help="
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1283
|
+
help=("Sync Codex accounting and reconcile native quota state first, then "
|
|
1284
|
+
"generate the report." if fixed_source == "codex" else
|
|
1285
|
+
"Run sync-week first, then generate the report."),
|
|
1286
|
+
)
|
|
1287
|
+
if fixed_source != "codex":
|
|
1288
|
+
pr.add_argument(
|
|
1289
|
+
"--week-start-name",
|
|
1290
|
+
default=None,
|
|
1291
|
+
choices=list(WEEKDAY_MAP.keys()),
|
|
1292
|
+
help="Week-start day used if report falls back to date-only week logic.",
|
|
1293
|
+
)
|
|
1294
|
+
pr.add_argument(
|
|
1295
|
+
"--mode",
|
|
1296
|
+
default="auto",
|
|
1297
|
+
choices=["auto", "calculate", "display"],
|
|
1298
|
+
help="Mode passed to sync-week when --sync-current is used.",
|
|
1299
|
+
)
|
|
1300
|
+
pr.add_argument(
|
|
1301
|
+
"--offline",
|
|
1302
|
+
action="store_true",
|
|
1303
|
+
help="Pass --offline to sync-week when --sync-current is used.",
|
|
1304
|
+
)
|
|
1305
|
+
pr.add_argument(
|
|
1306
|
+
"--project",
|
|
1307
|
+
default=None,
|
|
1308
|
+
help="Project filter passed to sync-week when --sync-current is used.",
|
|
1309
|
+
)
|
|
1170
1310
|
pr.add_argument(
|
|
1171
1311
|
"--reveal-projects",
|
|
1172
1312
|
action="store_true",
|
|
@@ -1177,21 +1317,24 @@ def _build_report_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1177
1317
|
pr.add_argument(
|
|
1178
1318
|
"--detail",
|
|
1179
1319
|
action="store_true",
|
|
1180
|
-
help="Include
|
|
1320
|
+
help=("Include native quota-window attribution detail."
|
|
1321
|
+
if fixed_source == "codex" else
|
|
1322
|
+
"Include per-percent cost milestones for the current week."),
|
|
1181
1323
|
)
|
|
1182
1324
|
pr.add_argument(
|
|
1183
1325
|
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
1184
1326
|
help="Display timezone: local, utc, or IANA name. "
|
|
1185
1327
|
"Overrides config display.tz for this call.",
|
|
1186
1328
|
)
|
|
1329
|
+
_add_source_args(pr, fixed_source=fixed_source, speed=True)
|
|
1187
1330
|
_add_share_args(pr)
|
|
1188
1331
|
pr.set_defaults(func=c.cmd_report)
|
|
1189
1332
|
|
|
1190
1333
|
def _build_forecast_parser(subparsers, name, *, help_text, xref=None):
|
|
1191
1334
|
"""Build the `forecast` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
1192
1335
|
|
|
1193
|
-
Move-only extraction of the former inline build_parser() block
|
|
1194
|
-
call-time `c = _cctally()` binding
|
|
1336
|
+
Move-only extraction of the former inline build_parser() block with
|
|
1337
|
+
call-time `c = _cctally()` binding.
|
|
1195
1338
|
"""
|
|
1196
1339
|
c = _cctally()
|
|
1197
1340
|
fc = subparsers.add_parser(
|
|
@@ -1727,26 +1870,23 @@ def _build_refresh_usage_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1727
1870
|
help="HTTP timeout in seconds (default: 5.0).")
|
|
1728
1871
|
rfu.set_defaults(func=c.cmd_refresh_usage)
|
|
1729
1872
|
|
|
1730
|
-
def _build_cache_report_parser(subparsers, name, *, help_text, xref=None):
|
|
1873
|
+
def _build_cache_report_parser(subparsers, name, *, help_text, xref=None, fixed_source=None):
|
|
1731
1874
|
"""Build the `cache-report` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
1732
1875
|
|
|
1733
1876
|
Move-only extraction of the former inline build_parser() block;
|
|
1734
1877
|
call-time `c = _cctally()` binding, --help bytes unchanged.
|
|
1735
1878
|
"""
|
|
1736
1879
|
c = _cctally()
|
|
1880
|
+
cache_description = {
|
|
1881
|
+
None: "Report Claude cache diagnostics or Codex cached-input/token reuse.",
|
|
1882
|
+
"claude": "Report Claude cache diagnostics.",
|
|
1883
|
+
"codex": "Report Codex cached-input/token reuse.",
|
|
1884
|
+
}[fixed_source]
|
|
1737
1885
|
pc = subparsers.add_parser(
|
|
1738
1886
|
name,
|
|
1739
1887
|
help=help_text,
|
|
1740
1888
|
formatter_class=CLIHelpFormatter,
|
|
1741
|
-
description=
|
|
1742
|
-
"""\
|
|
1743
|
-
Query ccusage for daily token breakdown and display cache hit
|
|
1744
|
-
percentages per model. Useful for spotting caching regressions
|
|
1745
|
-
after Claude Code updates.
|
|
1746
|
-
|
|
1747
|
-
Cache hit % = cacheReadTokens / (input + cacheCreate + cacheRead)
|
|
1748
|
-
"""
|
|
1749
|
-
),
|
|
1889
|
+
description=cache_description,
|
|
1750
1890
|
epilog=textwrap.dedent(
|
|
1751
1891
|
"""\
|
|
1752
1892
|
Examples:
|
|
@@ -1780,8 +1920,8 @@ def _build_cache_report_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1780
1920
|
"--by-session",
|
|
1781
1921
|
action="store_true",
|
|
1782
1922
|
dest="by_session",
|
|
1783
|
-
help="Group by
|
|
1784
|
-
"Adds
|
|
1923
|
+
help="Group by source-native session identity instead of by date. "
|
|
1924
|
+
"Adds identity, Last Activity, and Project columns.",
|
|
1785
1925
|
)
|
|
1786
1926
|
pc.add_argument(
|
|
1787
1927
|
"-O", "--offline",
|
|
@@ -1798,18 +1938,13 @@ def _build_cache_report_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1798
1938
|
default=None,
|
|
1799
1939
|
help="Filter to a specific project.",
|
|
1800
1940
|
)
|
|
1801
|
-
pc.add_argument(
|
|
1802
|
-
"--json",
|
|
1803
|
-
action="store_true",
|
|
1804
|
-
help="Emit machine-readable JSON output.",
|
|
1805
|
-
)
|
|
1806
1941
|
pc.add_argument(
|
|
1807
1942
|
"--anomaly-threshold-pp",
|
|
1808
1943
|
type=int,
|
|
1809
1944
|
default=15,
|
|
1810
1945
|
dest="anomaly_threshold_pp",
|
|
1811
|
-
help="
|
|
1812
|
-
"
|
|
1946
|
+
help="Claude cache %% drop threshold (percentage points) vs. a trailing "
|
|
1947
|
+
"median. Default: 15.",
|
|
1813
1948
|
)
|
|
1814
1949
|
pc.add_argument(
|
|
1815
1950
|
"--anomaly-window-days",
|
|
@@ -1823,36 +1958,47 @@ def _build_cache_report_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1823
1958
|
"--no-anomaly",
|
|
1824
1959
|
action="store_true",
|
|
1825
1960
|
dest="no_anomaly",
|
|
1826
|
-
help="Disable
|
|
1961
|
+
help="Disable Claude cache anomaly triggers.",
|
|
1827
1962
|
)
|
|
1828
1963
|
pc.add_argument(
|
|
1829
1964
|
"--sort",
|
|
1830
|
-
choices=["date", "net", "cache", "recent", "cost", "anomaly"],
|
|
1965
|
+
choices=["date", "net", "cache", "recent", "cost", "anomaly", "reuse"],
|
|
1831
1966
|
default=None,
|
|
1832
1967
|
dest="sort",
|
|
1833
|
-
help="Override sort order
|
|
1834
|
-
"--by-session mode.",
|
|
1968
|
+
help="Override sort order; valid values depend on the selected source.",
|
|
1835
1969
|
)
|
|
1836
1970
|
pc.add_argument(
|
|
1837
1971
|
"--tz", default=None, type=_argparse_tz, metavar="TZ",
|
|
1838
1972
|
help="Display timezone: local, utc, or IANA name. "
|
|
1839
1973
|
"Overrides config display.tz for this call.",
|
|
1840
1974
|
)
|
|
1975
|
+
pc.add_argument(
|
|
1976
|
+
"--reveal-projects", action="store_true", dest="reveal_projects",
|
|
1977
|
+
help="In --format output, show real project basenames instead of "
|
|
1978
|
+
"the default project-1, project-2, ... anonymization.",
|
|
1979
|
+
)
|
|
1841
1980
|
_add_ccusage_alias_args(pc, ansi_emit=False)
|
|
1981
|
+
_add_source_args(pc, fixed_source=fixed_source, speed=True)
|
|
1982
|
+
_add_share_args(pc)
|
|
1842
1983
|
pc.set_defaults(func=c.cmd_cache_report)
|
|
1843
1984
|
|
|
1844
|
-
def _build_range_cost_parser(subparsers, name, *, help_text, xref=None):
|
|
1985
|
+
def _build_range_cost_parser(subparsers, name, *, help_text, xref=None, fixed_source=None):
|
|
1845
1986
|
"""Build the `range-cost` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
1846
1987
|
|
|
1847
1988
|
Move-only extraction of the former inline build_parser() block;
|
|
1848
1989
|
call-time `c = _cctally()` binding, --help bytes unchanged.
|
|
1849
1990
|
"""
|
|
1850
1991
|
c = _cctally()
|
|
1992
|
+
range_description = {
|
|
1993
|
+
None: "Compute USD cost for an absolute time range from Claude, Codex, or both providers.",
|
|
1994
|
+
"claude": "Compute USD cost for a Claude absolute time range.",
|
|
1995
|
+
"codex": "Compute USD cost for a Codex absolute time range.",
|
|
1996
|
+
}[fixed_source]
|
|
1851
1997
|
rc = subparsers.add_parser(
|
|
1852
1998
|
name,
|
|
1853
1999
|
help=help_text,
|
|
1854
2000
|
formatter_class=CLIHelpFormatter,
|
|
1855
|
-
description=
|
|
2001
|
+
description=range_description,
|
|
1856
2002
|
epilog=textwrap.dedent("""\
|
|
1857
2003
|
Examples:
|
|
1858
2004
|
cctally range-cost -s "2026-04-10T10:00:00+03:00"
|
|
@@ -1887,19 +2033,20 @@ def _build_range_cost_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1887
2033
|
action="store_true",
|
|
1888
2034
|
help="Show per-model usage and cost breakdown.",
|
|
1889
2035
|
)
|
|
1890
|
-
rc.add_argument(
|
|
1891
|
-
"--json",
|
|
1892
|
-
action="store_true",
|
|
1893
|
-
dest="json",
|
|
1894
|
-
help="Output JSON.",
|
|
1895
|
-
)
|
|
1896
2036
|
rc.add_argument(
|
|
1897
2037
|
"--total-only",
|
|
1898
2038
|
action="store_true",
|
|
1899
2039
|
dest="total_only",
|
|
1900
2040
|
help="Print numeric USD total only.",
|
|
1901
2041
|
)
|
|
2042
|
+
rc.add_argument(
|
|
2043
|
+
"--reveal-projects", action="store_true", dest="reveal_projects",
|
|
2044
|
+
help="In --format output, show real project basenames instead of "
|
|
2045
|
+
"the default project-1, project-2, ... anonymization.",
|
|
2046
|
+
)
|
|
1902
2047
|
_add_ccusage_alias_args(rc, ansi_emit=False)
|
|
2048
|
+
_add_source_args(rc, fixed_source=fixed_source, speed=True)
|
|
2049
|
+
_add_share_args(rc)
|
|
1903
2050
|
rc.set_defaults(func=c.cmd_range_cost)
|
|
1904
2051
|
|
|
1905
2052
|
def _build_five_hour_blocks_parser(subparsers, name, *, help_text, xref=None):
|
|
@@ -1989,20 +2136,34 @@ def _build_cache_sync_parser(subparsers, name, *, help_text, xref=None):
|
|
|
1989
2136
|
)
|
|
1990
2137
|
p_cache_sync.set_defaults(func=c.cmd_cache_sync)
|
|
1991
2138
|
|
|
1992
|
-
def _build_project_parser(subparsers, name, *, help_text, xref=None):
|
|
2139
|
+
def _build_project_parser(subparsers, name, *, help_text, xref=None, fixed_source=None):
|
|
1993
2140
|
"""Build the `project` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
1994
2141
|
|
|
1995
2142
|
Move-only extraction of the former inline build_parser() block;
|
|
1996
2143
|
call-time `c = _cctally()` binding, --help bytes unchanged.
|
|
1997
2144
|
"""
|
|
1998
2145
|
c = _cctally()
|
|
2146
|
+
project_description = {
|
|
2147
|
+
None: (
|
|
2148
|
+
"Aggregate project usage. Claude uses subscription weeks; Codex uses "
|
|
2149
|
+
"calendar weeks; --source all uses an absolute calendar range."
|
|
2150
|
+
),
|
|
2151
|
+
"claude": "Aggregate Claude project usage for subscription weeks.",
|
|
2152
|
+
"codex": "Aggregate Codex project usage for calendar weeks.",
|
|
2153
|
+
}[fixed_source]
|
|
1999
2154
|
p_project = subparsers.add_parser(
|
|
2000
2155
|
name,
|
|
2001
2156
|
help=help_text,
|
|
2002
2157
|
formatter_class=CLIHelpFormatter,
|
|
2003
|
-
description=
|
|
2004
|
-
|
|
2005
|
-
|
|
2158
|
+
description=project_description,
|
|
2159
|
+
epilog=textwrap.dedent(("""\
|
|
2160
|
+
Examples:
|
|
2161
|
+
cctally codex project
|
|
2162
|
+
cctally codex project --weeks 4
|
|
2163
|
+
cctally codex project --project project:0123456789abcdef01234567
|
|
2164
|
+
cctally codex project --breakdown --sort cost --order desc
|
|
2165
|
+
cctally codex project --group full-path --json
|
|
2166
|
+
""" if fixed_source == "codex" else """\
|
|
2006
2167
|
Examples:
|
|
2007
2168
|
cctally project
|
|
2008
2169
|
cctally project --weeks 4
|
|
@@ -2010,25 +2171,34 @@ def _build_project_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2010
2171
|
cctally project --project ccusage --model sonnet
|
|
2011
2172
|
cctally project --breakdown --sort used --order desc
|
|
2012
2173
|
cctally project --group full-path --json
|
|
2013
|
-
"""),
|
|
2174
|
+
""")),
|
|
2014
2175
|
)
|
|
2015
2176
|
_add_since_until_args(
|
|
2016
2177
|
p_project, metavar_since="YYYYMMDD", metavar_until="YYYYMMDD",
|
|
2017
2178
|
help_since="Inclusive start date (YYYY-MM-DD or YYYYMMDD).",
|
|
2018
2179
|
help_until="Inclusive end date (YYYY-MM-DD or YYYYMMDD).")
|
|
2019
2180
|
p_project.add_argument("--weeks", type=int, default=None,
|
|
2020
|
-
help="Last N
|
|
2181
|
+
help=("Last N Codex calendar weeks ending now."
|
|
2182
|
+
if fixed_source == "codex" else
|
|
2183
|
+
"Last N weeks ending now: Claude subscription, Codex calendar; "
|
|
2184
|
+
"all uses an absolute calendar range."))
|
|
2021
2185
|
p_project.add_argument("--project", action="append", default=[], metavar="PATTERN",
|
|
2022
|
-
help="
|
|
2186
|
+
help=("Filter by an exact opaque project key or collision-safe display "
|
|
2187
|
+
"label (repeatable, OR)." if fixed_source == "codex" else
|
|
2188
|
+
"Substring filter on project display key (repeatable, OR)."))
|
|
2023
2189
|
p_project.add_argument("--model", action="append", default=[], metavar="PATTERN",
|
|
2024
2190
|
help="Substring filter on model name (repeatable, OR).")
|
|
2025
2191
|
p_project.add_argument("-b", "--breakdown", action="store_true",
|
|
2026
2192
|
help="Add per-model child rows under each project.")
|
|
2027
2193
|
p_project.add_argument("-o", "--order", choices=("asc", "desc"), default="desc",
|
|
2028
2194
|
help="Sort direction (default: desc).")
|
|
2029
|
-
p_project.add_argument("--sort", choices=("cost", "
|
|
2195
|
+
p_project.add_argument("--sort", choices=(("cost", "name", "last-seen")
|
|
2196
|
+
if fixed_source == "codex"
|
|
2197
|
+
else ("cost", "used", "name", "last-seen")),
|
|
2030
2198
|
default="cost",
|
|
2031
|
-
help="Sort key (default: cost
|
|
2199
|
+
help=("Sort key (default: cost; Claude-only used-percent ordering is "
|
|
2200
|
+
"unavailable)." if fixed_source == "codex" else
|
|
2201
|
+
"Sort key (default: cost)."))
|
|
2032
2202
|
p_project.add_argument("--group", choices=("git-root", "full-path"), default="git-root",
|
|
2033
2203
|
help="Bucket by resolved git-root (default) or raw project_path.")
|
|
2034
2204
|
p_project.add_argument("--reveal-projects", action="store_true", dest="reveal_projects",
|
|
@@ -2040,10 +2210,11 @@ def _build_project_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2040
2210
|
help="Display timezone: local, utc, or IANA name. "
|
|
2041
2211
|
"Overrides config display.tz for this call.")
|
|
2042
2212
|
_add_ccusage_alias_args(p_project, ansi_emit=True)
|
|
2213
|
+
_add_source_args(p_project, fixed_source=fixed_source, speed=True)
|
|
2043
2214
|
_add_share_args(p_project)
|
|
2044
2215
|
p_project.set_defaults(func=c.cmd_project)
|
|
2045
2216
|
|
|
2046
|
-
def _build_diff_parser(subparsers, name, *, help_text, xref=None):
|
|
2217
|
+
def _build_diff_parser(subparsers, name, *, help_text, xref=None, fixed_source=None):
|
|
2047
2218
|
"""Build the `diff` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
2048
2219
|
|
|
2049
2220
|
Move-only extraction of the former inline build_parser() block;
|
|
@@ -2059,7 +2230,11 @@ def _build_diff_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2059
2230
|
diff_p.add_argument("--b", required=True, help="Window B token (same grammar as --a)")
|
|
2060
2231
|
diff_p.add_argument("--allow-mismatch", action="store_true",
|
|
2061
2232
|
help="Permit mismatched window lengths (deltas normalized per-day)")
|
|
2062
|
-
diff_p.add_argument(
|
|
2233
|
+
diff_p.add_argument(
|
|
2234
|
+
"--only",
|
|
2235
|
+
help=("Comma-separated section list: overall,models,projects; "
|
|
2236
|
+
"cache (Claude only); token-reuse (Codex only)"),
|
|
2237
|
+
)
|
|
2063
2238
|
diff_p.add_argument("--with", dest="with_extra",
|
|
2064
2239
|
help="Comma-separated opt-in sections (trend,time)")
|
|
2065
2240
|
diff_p.add_argument("--all", dest="show_all", action="store_true",
|
|
@@ -2077,10 +2252,16 @@ def _build_diff_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2077
2252
|
help="Display timezone: local, utc, or IANA name. "
|
|
2078
2253
|
"Overrides config display.tz for this call.")
|
|
2079
2254
|
diff_p.add_argument("--no-color", action="store_true")
|
|
2080
|
-
diff_p.add_argument("--json", dest="emit_json", action="store_true")
|
|
2081
2255
|
diff_p.add_argument("--width", type=int, help=argparse.SUPPRESS)
|
|
2082
2256
|
diff_p.add_argument("--debug-now", action="store_true", help=argparse.SUPPRESS)
|
|
2257
|
+
diff_p.add_argument(
|
|
2258
|
+
"--reveal-projects", action="store_true", dest="reveal_projects",
|
|
2259
|
+
help="In --format output, show real project basenames instead of "
|
|
2260
|
+
"the default project-1, project-2, ... anonymization.",
|
|
2261
|
+
)
|
|
2083
2262
|
_add_ccusage_alias_args(diff_p, ansi_emit=True)
|
|
2263
|
+
_add_source_args(diff_p, fixed_source=fixed_source, speed=True)
|
|
2264
|
+
_add_share_args(diff_p, json_dest="emit_json")
|
|
2084
2265
|
diff_p.set_defaults(func=c.cmd_diff)
|
|
2085
2266
|
|
|
2086
2267
|
def _build_claude_parser(subparsers, name, *, help_text, xref=None):
|
|
@@ -2128,6 +2309,16 @@ def _build_claude_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2128
2309
|
help_text="Compact one-line status for Claude Code hooks",
|
|
2129
2310
|
xref="Canonical `cctally claude statusline` (flat alias: `cctally statusline`). "
|
|
2130
2311
|
"Drop-in for `ccusage statusline` plus cctally extension segments.")
|
|
2312
|
+
_build_project_parser(claude_sub, "project",
|
|
2313
|
+
help_text="Roll usage up by project", fixed_source="claude")
|
|
2314
|
+
_build_diff_parser(claude_sub, "diff",
|
|
2315
|
+
help_text="Compare usage between two windows", fixed_source="claude")
|
|
2316
|
+
_build_range_cost_parser(claude_sub, "range-cost",
|
|
2317
|
+
help_text="Compute USD cost for a time range", fixed_source="claude")
|
|
2318
|
+
_build_cache_report_parser(claude_sub, "cache-report",
|
|
2319
|
+
help_text="Show cache analytics", fixed_source="claude")
|
|
2320
|
+
_build_report_parser(claude_sub, "report",
|
|
2321
|
+
help_text="Show dollars-per-percent report", fixed_source="claude")
|
|
2131
2322
|
|
|
2132
2323
|
def _build_codex_parser(subparsers, name, *, help_text, xref=None):
|
|
2133
2324
|
"""Build the `codex` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
@@ -2163,6 +2354,18 @@ def _build_codex_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2163
2354
|
help_text="Show Codex usage grouped by week",
|
|
2164
2355
|
xref="cctally extension (no upstream `ccusage codex weekly`). Same engine as "
|
|
2165
2356
|
"`cctally codex-weekly`.")
|
|
2357
|
+
_build_project_parser(codex_sub, "project",
|
|
2358
|
+
help_text="Roll Codex usage up by qualified project", fixed_source="codex")
|
|
2359
|
+
_build_diff_parser(codex_sub, "diff",
|
|
2360
|
+
help_text="Compare Codex usage between two windows", fixed_source="codex")
|
|
2361
|
+
_build_range_cost_parser(codex_sub, "range-cost",
|
|
2362
|
+
help_text="Compute Codex USD cost for a time range", fixed_source="codex")
|
|
2363
|
+
_build_cache_report_parser(codex_sub, "cache-report",
|
|
2364
|
+
help_text="Show Codex token-reuse analytics", fixed_source="codex")
|
|
2365
|
+
_build_report_parser(codex_sub, "report",
|
|
2366
|
+
help_text="Show Codex quota-window report", fixed_source="codex")
|
|
2367
|
+
_build_codex_quota_parser(codex_sub, "quota",
|
|
2368
|
+
help_text="Native root-qualified Codex quota reports")
|
|
2166
2369
|
|
|
2167
2370
|
def _build_config_parser(subparsers, name, *, help_text, xref=None):
|
|
2168
2371
|
"""Build the `config` parser (registered via _REGISTRATION; #279 S6 W3).
|
|
@@ -2726,6 +2929,10 @@ def _build_hook_tick_parser(subparsers, name, *, help_text, xref=None):
|
|
|
2726
2929
|
"(used by --explain and tests)")
|
|
2727
2930
|
ht.add_argument("--mock-oauth-response", type=str, default=None,
|
|
2728
2931
|
help=argparse.SUPPRESS) # JSON string fed to mock fetch (tests only)
|
|
2932
|
+
ht.add_argument("--foreground", action="store_true",
|
|
2933
|
+
help=argparse.SUPPRESS) # Codex native hook wrapper
|
|
2934
|
+
ht.add_argument("--source", choices=("claude", "codex"), default="claude",
|
|
2935
|
+
help=argparse.SUPPRESS) # setup-managed native Codex hook
|
|
2729
2936
|
ht.set_defaults(func=c.cmd_hook_tick)
|
|
2730
2937
|
|
|
2731
2938
|
def _build_preview_parser(subparsers, name, *, help_text, xref=None):
|
|
@@ -2905,8 +3112,8 @@ _REGISTRATION = (
|
|
|
2905
3112
|
_Reg('record-usage', _build_record_usage_parser, "Record usage data from Claude Code status line", None, None),
|
|
2906
3113
|
_Reg('record-credit', _build_record_credit_parser, "Record an in-place weekly credit the auto-detector misses", None, None),
|
|
2907
3114
|
_Reg('refresh-usage', _build_refresh_usage_parser, "Force-fetch 7d/5h percent from OAuth API and record it", None, None),
|
|
2908
|
-
_Reg('cache-report', _build_cache_report_parser, "Show
|
|
2909
|
-
_Reg('range-cost', _build_range_cost_parser, "Compute USD cost for a time range
|
|
3115
|
+
_Reg('cache-report', _build_cache_report_parser, "Show Claude cache diagnostics or Codex token reuse", None, None),
|
|
3116
|
+
_Reg('range-cost', _build_range_cost_parser, "Compute USD cost for a provider-aware time range", None, None),
|
|
2910
3117
|
_Reg('blocks', _build_blocks_parser, "Show usage report grouped by 5-hour session blocks", "Alias of `cctally claude blocks` (the canonical form).", None),
|
|
2911
3118
|
_Reg('statusline', _build_statusline_parser, "Compact one-line status for Claude Code hooks", "Alias of `cctally claude statusline` (the canonical form).", None),
|
|
2912
3119
|
_Reg('five-hour-blocks', _build_five_hour_blocks_parser, "List API-anchored 5h blocks with rollup totals + 7d-drift columns", None, None),
|
|
@@ -2918,7 +3125,7 @@ _REGISTRATION = (
|
|
|
2918
3125
|
_Reg('codex-monthly', _build_codex_monthly_parser, "Show Codex usage grouped by month (drop-in for `ccusage-codex monthly`)", "Alias of `cctally codex monthly` (the canonical form).", None),
|
|
2919
3126
|
_Reg('codex-weekly', _build_codex_weekly_parser, "Show Codex usage grouped by week (week-start from config.json)", "Alias of `cctally codex weekly` (the canonical form).", None),
|
|
2920
3127
|
_Reg('codex-session', _build_codex_session_parser, "Show Codex usage grouped by session (drop-in for `ccusage-codex session`)", "Alias of `cctally codex session` (the canonical form).", None),
|
|
2921
|
-
_Reg('project', _build_project_parser, "Roll usage up by project
|
|
3128
|
+
_Reg('project', _build_project_parser, "Roll Claude/Codex usage up by project", None, None),
|
|
2922
3129
|
_Reg('diff', _build_diff_parser, "Compare Claude usage between two windows.", None, None),
|
|
2923
3130
|
_Reg('session', _build_session_parser, "Show Claude usage grouped by sessionId (merges resumed-across-files sessions)", "Alias of `cctally claude session` (the canonical form).", None),
|
|
2924
3131
|
_Reg('transcript', _build_transcript_parser, "Export or search conversation transcripts (anonymized export by default)", None, None),
|
|
@@ -2989,4 +3196,3 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2989
3196
|
if getattr(a, "help", None) is not argparse.SUPPRESS
|
|
2990
3197
|
]
|
|
2991
3198
|
return p
|
|
2992
|
-
|