cctally 1.74.0 → 1.75.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.
@@ -1055,7 +1055,7 @@ def _build_codex_session_parser(subparsers, name, *, help_text, xref):
1055
1055
  return p
1056
1056
 
1057
1057
 
1058
- def _add_codex_quota_common_args(parser) -> None:
1058
+ def _add_codex_quota_common_args(parser, *, sync_by_default: bool = True) -> None:
1059
1059
  """Attach the shared exact-selector/reporting surface for native quotas."""
1060
1060
  parser.add_argument(
1061
1061
  "--root-key", dest="root_key", metavar="FULL_SOURCE_ROOT_KEY",
@@ -1065,10 +1065,16 @@ def _add_codex_quota_common_args(parser) -> None:
1065
1065
  "--limit-key", dest="limit_key", metavar="FULL_LOGICAL_LIMIT_KEY",
1066
1066
  help="Exact case-sensitive logicalLimitKey selector (no prefix matching).",
1067
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
- )
1068
+ if sync_by_default:
1069
+ parser.add_argument(
1070
+ "--no-sync", action="store_true",
1071
+ help="Read retained local-rollout quota evidence without a Codex cache sync.",
1072
+ )
1073
+ else:
1074
+ parser.add_argument(
1075
+ "--sync", action="store_true",
1076
+ help="Refresh retained Codex evidence before reading the materialized projection.",
1077
+ )
1072
1078
  parser.add_argument(
1073
1079
  "--config", default=None, metavar="PATH",
1074
1080
  help="Read display settings from PATH for this invocation only.",
@@ -1145,6 +1151,46 @@ def _build_codex_quota_parser(subparsers, name, *, help_text, xref=None):
1145
1151
  return quota
1146
1152
 
1147
1153
 
1154
+ def _build_codex_percent_breakdown_parser(subparsers, name, *, help_text, xref=None):
1155
+ """Build the current native seven-day Codex milestone view."""
1156
+ c = _cctally()
1157
+ parser = subparsers.add_parser(
1158
+ name,
1159
+ help=help_text,
1160
+ formatter_class=CLIHelpFormatter,
1161
+ description=textwrap.dedent(
1162
+ """\
1163
+ Show cumulative and marginal Codex cost at each integer percent
1164
+ threshold for one native 7-day quota cycle, using the same terminal
1165
+ design as `cctally percent-breakdown`.
1166
+ """
1167
+ ),
1168
+ epilog=textwrap.dedent(
1169
+ """\
1170
+ Examples:
1171
+ cctally codex percent-breakdown
1172
+ cctally codex percent-breakdown --root-key <key> --limit-key <key>
1173
+ cctally codex percent-breakdown --reset-at 2026-07-15T15:00:00Z
1174
+ """
1175
+ ),
1176
+ )
1177
+ parser.add_argument(
1178
+ "--reset-at", default=None, metavar="ISO-8601",
1179
+ help="Exact retained 7-day reset timestamp. Defaults to the active cycle.",
1180
+ )
1181
+ parser.add_argument(
1182
+ "--speed", choices=("auto", "standard", "fast"), default="auto",
1183
+ help="Codex pricing tier for query-time cost correlation (default: auto).",
1184
+ )
1185
+ _add_codex_quota_common_args(parser, sync_by_default=False)
1186
+ parser.add_argument(
1187
+ "--tz", default=None, type=_argparse_tz, metavar="TZ",
1188
+ help="Display timezone: local, utc, or IANA name. Overrides config display.tz.",
1189
+ )
1190
+ parser.set_defaults(func=c.cmd_codex_percent_breakdown)
1191
+ return parser
1192
+
1193
+
1148
1194
  def _build_sync_week_parser(subparsers, name, *, help_text, xref=None):
1149
1195
  """Build the `sync-week` parser (registered via _REGISTRATION; #279 S6 W3).
1150
1196
 
@@ -2138,7 +2184,7 @@ def _build_cache_sync_parser(subparsers, name, *, help_text, xref=None):
2138
2184
  "--prune-conversations",
2139
2185
  action="store_true",
2140
2186
  help="Prune conversation transcripts older than "
2141
- "conversation.retention_days (default 180) now, without a full "
2187
+ "conversation.retention_days (default 90) now, without a full "
2142
2188
  "rebuild. Re-derivable from JSONL; run `cctally db vacuum` to "
2143
2189
  "reclaim the freed disk space.",
2144
2190
  )
@@ -2372,6 +2418,8 @@ def _build_codex_parser(subparsers, name, *, help_text, xref=None):
2372
2418
  help_text="Show Codex token-reuse analytics", fixed_source="codex")
2373
2419
  _build_report_parser(codex_sub, "report",
2374
2420
  help_text="Show Codex quota-window report", fixed_source="codex")
2421
+ _build_codex_percent_breakdown_parser(codex_sub, "percent-breakdown",
2422
+ help_text="Show per-percent cost milestones for one native 7-day cycle")
2375
2423
  _build_codex_quota_parser(codex_sub, "quota",
2376
2424
  help_text="Native root-qualified Codex quota reports")
2377
2425
 
@@ -2800,6 +2848,52 @@ def _build_db_parser(subparsers, name, *, help_text, xref=None):
2800
2848
  help="Required for --db stats (non-re-derivable; may need a re-record)",
2801
2849
  )
2802
2850
  db_recover.set_defaults(func=c.cmd_db_recover)
2851
+ db_repair = db_sub.add_parser(
2852
+ "repair",
2853
+ help="Recover a malformed stats.db through a verified fresh copy",
2854
+ )
2855
+ db_repair.add_argument(
2856
+ "--db",
2857
+ required=True,
2858
+ choices=("stats",),
2859
+ help="Database to repair (stats only; rebuild cache.db instead)",
2860
+ )
2861
+ db_repair.add_argument(
2862
+ "--yes",
2863
+ action="store_true",
2864
+ help="Required: preserve the corrupt original, then replace stats.db",
2865
+ )
2866
+ db_repair.add_argument(
2867
+ "--busy-timeout-ms",
2868
+ dest="busy_timeout_ms",
2869
+ type=int,
2870
+ default=250,
2871
+ help=argparse.SUPPRESS,
2872
+ )
2873
+ db_repair.set_defaults(func=c.cmd_db_repair)
2874
+ db_backup = db_sub.add_parser(
2875
+ "backup",
2876
+ help="Create a consistent SQLite online-backup snapshot",
2877
+ )
2878
+ db_backup.add_argument(
2879
+ "--db",
2880
+ required=True,
2881
+ choices=("cache", "stats"),
2882
+ help="Which DB to back up",
2883
+ )
2884
+ db_backup.add_argument(
2885
+ "--output",
2886
+ dest="backup_output",
2887
+ help="Destination file (default: timestamped sibling; never overwritten)",
2888
+ )
2889
+ db_backup.add_argument(
2890
+ "--busy-timeout-ms",
2891
+ dest="busy_timeout_ms",
2892
+ type=int,
2893
+ default=15000,
2894
+ help=argparse.SUPPRESS,
2895
+ )
2896
+ db_backup.set_defaults(func=c.cmd_db_backup)
2803
2897
  db_checkpoint = db_sub.add_parser(
2804
2898
  "checkpoint",
2805
2899
  help="Drain the WAL (TRUNCATE checkpoint) — fast, non-destructive",
@@ -33,6 +33,51 @@ def _cctally():
33
33
  return sys.modules["cctally"]
34
34
 
35
35
 
36
+ def _render_percent_breakdown_terminal(
37
+ *,
38
+ week_start_date: str,
39
+ week_end_date: str,
40
+ display_start_iso: str | None,
41
+ display_end_iso: str | None,
42
+ milestone_list: list[dict[str, object]],
43
+ tz,
44
+ empty_message: str = "No percent milestones recorded for this week.",
45
+ ) -> str:
46
+ """Render the canonical weekly per-percent terminal design."""
47
+ c = _cctally()
48
+ lines: list[str] = []
49
+ if display_start_iso and display_end_iso:
50
+ lines.append(
51
+ f"Week: {c._format_ts_compact(display_start_iso, tz=tz)} -> "
52
+ f"{c._format_ts_compact(display_end_iso, tz=tz)}"
53
+ )
54
+ else:
55
+ lines.append(f"Week: {week_start_date}..{week_end_date}")
56
+ if not milestone_list:
57
+ lines.append(empty_message)
58
+ return "\n".join(lines)
59
+
60
+ lines.extend(("Percent breakdown:", ""))
61
+ headers = ["#", "Threshold", "Cumulative Cost", "Marginal Cost", "5h at crossing"]
62
+ rows: list[list[str]] = []
63
+ for idx, milestone in enumerate(milestone_list, start=1):
64
+ percent = int(milestone["percentThreshold"])
65
+ cumulative = float(milestone["cumulativeCostUSD"])
66
+ marginal_value = milestone["marginalCostUSD"]
67
+ five_hour_value = milestone["fiveHourPercentAtCrossing"]
68
+ rows.append([
69
+ str(idx),
70
+ f"{percent}%",
71
+ f"${cumulative:.6f}",
72
+ f"${float(marginal_value):.6f}" if marginal_value is not None else "n/a",
73
+ f"{float(five_hour_value):.0f}%" if five_hour_value is not None else "n/a",
74
+ ])
75
+ lines.append(c._boxed_table(
76
+ headers, rows, ["right", "right", "right", "right", "right"],
77
+ ))
78
+ return "\n".join(lines)
79
+
80
+
36
81
  def cmd_percent_breakdown(args: argparse.Namespace) -> int:
37
82
  c = _cctally()
38
83
  config = c.load_config()
@@ -159,40 +204,20 @@ def cmd_percent_breakdown(args: argparse.Namespace) -> int:
159
204
  print(json.dumps(c.stamp_schema_version(output), indent=2))
160
205
  return 0
161
206
 
162
- # Prefer the reset-adjusted ISO timestamps in the terminal header
163
- # when available; fall back to the raw dates for legacy installs.
164
- if display_start_iso and display_end_iso:
165
- print(
166
- f"Week: {c._format_ts_compact(display_start_iso, tz=tz)} -> "
167
- f"{c._format_ts_compact(display_end_iso, tz=tz)}"
168
- )
169
- else:
170
- print(f"Week: {week_start_date}..{week_end_date}")
171
- if not milestone_list:
172
- if active_segment > 0:
173
- # v1.7.2: distinguish post-credit empty (just got
174
- # credited, no crossings yet) from genuinely-empty week.
175
- # The pre-credit ledger still exists in the DB — just
176
- # filtered out of the body — so the user shouldn't see
177
- # "No milestones" and assume the data is gone.
178
- print(
179
- "(post-credit segment, no milestones crossed yet)"
180
- )
181
- else:
182
- print("No percent milestones recorded for this week.")
183
- return 0
184
-
185
- print("Percent breakdown:\n")
186
- headers = ["#", "Threshold", "Cumulative Cost", "Marginal Cost", "5h at crossing"]
187
- rows: list[list[str]] = []
188
- for idx, m in enumerate(milestone_list, start=1):
189
- pct = f"{m['percentThreshold']}%"
190
- cum = f"${m['cumulativeCostUSD']:.6f}"
191
- marg = f"${m['marginalCostUSD']:.6f}" if m["marginalCostUSD"] is not None else "n/a"
192
- fh = f"{m['fiveHourPercentAtCrossing']:.0f}%" if m["fiveHourPercentAtCrossing"] is not None else "n/a"
193
- rows.append([str(idx), pct, cum, marg, fh])
194
-
195
- print(c._boxed_table(headers, rows, ["right", "right", "right", "right", "right"]))
207
+ empty_message = (
208
+ "(post-credit segment, no milestones crossed yet)"
209
+ if active_segment > 0
210
+ else "No percent milestones recorded for this week."
211
+ )
212
+ print(_render_percent_breakdown_terminal(
213
+ week_start_date=week_start_date,
214
+ week_end_date=week_end_date,
215
+ display_start_iso=display_start_iso,
216
+ display_end_iso=display_end_iso,
217
+ milestone_list=milestone_list,
218
+ tz=tz,
219
+ empty_message=empty_message,
220
+ ))
196
221
 
197
222
  return 0
198
223
  finally: