cctally 1.74.0 → 1.75.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 +19 -0
- package/bin/_cctally_cache.py +9 -0
- package/bin/_cctally_config.py +3 -3
- package/bin/_cctally_core.py +12 -3
- package/bin/_cctally_dashboard.py +33 -15
- package/bin/_cctally_dashboard_conversation.py +2 -2
- package/bin/_cctally_dashboard_sources.py +401 -18
- package/bin/_cctally_db.py +723 -1
- package/bin/_cctally_parser.py +100 -6
- package/bin/_cctally_percent_breakdown.py +59 -34
- package/bin/_cctally_quota.py +311 -45
- package/bin/_cctally_record.py +52 -1
- package/bin/_cctally_refresh.py +26 -0
- package/bin/_cctally_statusline.py +3 -3
- package/bin/_cctally_tui.py +3 -0
- package/bin/_lib_aggregators.py +7 -0
- package/bin/_lib_cache_report.py +25 -9
- package/bin/_lib_conversation_retention.py +42 -2
- package/bin/_lib_doctor.py +4 -4
- package/bin/cctally +27 -1
- package/dashboard/static/assets/index-fZRxsSf5.js +80 -0
- package/dashboard/static/assets/{index-ZiPO8veo.css → index-yftBNnLR.css} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +1 -1
- package/dashboard/static/assets/index-DeQjEMO6.js +0 -80
package/bin/_cctally_parser.py
CHANGED
|
@@ -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
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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:
|