cctally 1.82.0 → 1.83.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 +70 -0
- package/README.md +52 -74
- package/bin/_cctally_alerts.py +8 -1
- package/bin/_cctally_cache.py +963 -149
- package/bin/_cctally_config.py +43 -4
- package/bin/_cctally_core.py +933 -759
- package/bin/_cctally_dashboard.py +157 -47
- package/bin/_cctally_dashboard_cache_report.py +13 -6
- package/bin/_cctally_dashboard_conversation.py +1 -0
- package/bin/_cctally_dashboard_envelope.py +186 -8
- package/bin/_cctally_dashboard_share.py +60 -20
- package/bin/_cctally_dashboard_sources.py +427 -128
- package/bin/_cctally_db.py +605 -128
- package/bin/_cctally_doctor.py +413 -28
- package/bin/_cctally_five_hour.py +12 -5
- package/bin/_cctally_journal.py +2050 -156
- package/bin/_cctally_journal_repair.py +519 -0
- package/bin/_cctally_milestone_history.py +142 -56
- package/bin/_cctally_milestones.py +179 -111
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_project.py +24 -18
- package/bin/_cctally_quota.py +139 -25
- package/bin/_cctally_record.py +279 -108
- package/bin/_cctally_rederive.py +1052 -0
- package/bin/_cctally_reporting.py +58 -53
- package/bin/_cctally_setup.py +1 -0
- package/bin/_cctally_source_analytics.py +4 -1
- package/bin/_cctally_statusline.py +11 -11
- package/bin/_cctally_store.py +1039 -31
- package/bin/_cctally_sync_week.py +17 -8
- package/bin/_cctally_tui.py +421 -54
- package/bin/_cctally_update.py +133 -8
- package/bin/_cctally_weekrefs.py +14 -0
- package/bin/_lib_aggregators.py +10 -6
- package/bin/_lib_cache_report.py +101 -9
- package/bin/_lib_codex_pools.py +82 -0
- package/bin/_lib_conversation_query.py +126 -33
- package/bin/_lib_dashboard_sources.py +126 -1
- package/bin/_lib_diff_kernel.py +28 -15
- package/bin/_lib_doctor.py +342 -4
- package/bin/_lib_journal.py +924 -2
- package/bin/_lib_jsonl.py +43 -14
- package/bin/_lib_pricing.py +140 -21
- package/bin/_lib_readme_refresh.py +401 -0
- package/bin/_lib_rederive.py +395 -0
- package/bin/_lib_share.py +58 -2
- package/bin/cctally +56 -8
- package/dashboard/static/assets/{index-DJP4gEB7.js → index-3bgCMVHb.js} +52 -52
- package/dashboard/static/assets/index-D27EIHEI.css +1 -0
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +6 -1
- package/dashboard/static/assets/index-Dk1nplOz.css +0 -1
package/bin/_cctally_config.py
CHANGED
|
@@ -434,8 +434,10 @@ def _config_codex_leaf_value(config: dict, key: str) -> object:
|
|
|
434
434
|
return list(value) if isinstance(value, list) else value
|
|
435
435
|
|
|
436
436
|
|
|
437
|
-
def _resolve_one_account_budget_ref(
|
|
438
|
-
|
|
437
|
+
def _resolve_one_account_budget_ref(
|
|
438
|
+
conn, ref: object, provider: str, label: str,
|
|
439
|
+
*, stats_maintenance_unavailable: bool = False,
|
|
440
|
+
) -> str:
|
|
439
441
|
"""Resolve one per-account-budget ref to an immutable account_key at WRITE
|
|
440
442
|
time (#341, spec §6 — a later label rename must never retarget the budget).
|
|
441
443
|
|
|
@@ -456,6 +458,12 @@ def _resolve_one_account_budget_ref(conn, ref: object, provider: str,
|
|
|
456
458
|
if _re.fullmatch(r"[0-9a-f]{32}", ref):
|
|
457
459
|
return ref # already an immutable account_key
|
|
458
460
|
if conn is None:
|
|
461
|
+
if stats_maintenance_unavailable:
|
|
462
|
+
raise _BudgetConfigError(
|
|
463
|
+
f"{label}: cannot resolve account ref {ref!r} while stats.db "
|
|
464
|
+
"maintenance is in progress; retry after it completes or use "
|
|
465
|
+
"the 32-hex account key"
|
|
466
|
+
)
|
|
459
467
|
raise _BudgetConfigError(
|
|
460
468
|
f"{label}: cannot resolve account ref {ref!r} — no accounts observed "
|
|
461
469
|
"yet; use the 32-hex account key"
|
|
@@ -475,13 +483,44 @@ def _normalize_account_budget_refs(raw_obj: dict, provider: str,
|
|
|
475
483
|
(validated numerically by ``_validate_account_budget_map`` under the lock)."""
|
|
476
484
|
import sqlite3
|
|
477
485
|
conn = None
|
|
486
|
+
stats_maintenance_unavailable = False
|
|
478
487
|
try:
|
|
479
488
|
db_path = _cctally_core.DB_PATH
|
|
480
489
|
if db_path.exists():
|
|
481
|
-
|
|
490
|
+
# #386: `mode=ro` is not exempt from the opener protocol — measured,
|
|
491
|
+
# it CREATES `-shm`/`-wal` when they are absent.
|
|
492
|
+
#
|
|
493
|
+
# LOCK ORDER, deliberately noted: this runs under
|
|
494
|
+
# `config_writer_lock`, so it takes `stats.db.maintenance.lock`
|
|
495
|
+
# SHARED while holding it. That edge is safe because it is one-way —
|
|
496
|
+
# no stats maintenance path writes config.json, so no holder of the
|
|
497
|
+
# stats maintenance lock ever waits on `config_writer_lock`. Adding
|
|
498
|
+
# such a writer would create a cycle; don't.
|
|
499
|
+
#
|
|
500
|
+
# A database read failure degrades to `conn = None`, which preserves
|
|
501
|
+
# raw 32-hex account-key writes but cannot resolve label/email/prefix
|
|
502
|
+
# refs. A bounded maintenance-lock timeout is distinguished below
|
|
503
|
+
# so the user is told to retry after maintenance instead of being
|
|
504
|
+
# told that no accounts have been observed.
|
|
505
|
+
import _cctally_store
|
|
506
|
+
import _cctally_db
|
|
507
|
+
try:
|
|
508
|
+
conn = _cctally_store.stats_open_guarded(
|
|
509
|
+
db_path,
|
|
510
|
+
connect=lambda p: sqlite3.connect(
|
|
511
|
+
f"file:{p}?mode=ro", uri=True),
|
|
512
|
+
)
|
|
513
|
+
except _cctally_db.StatsDbMaintenanceError:
|
|
514
|
+
stats_maintenance_unavailable = True
|
|
515
|
+
conn = None
|
|
516
|
+
except sqlite3.DatabaseError:
|
|
517
|
+
conn = None
|
|
482
518
|
out: dict = {}
|
|
483
519
|
for ref, val in raw_obj.items():
|
|
484
|
-
key = _resolve_one_account_budget_ref(
|
|
520
|
+
key = _resolve_one_account_budget_ref(
|
|
521
|
+
conn, ref, provider, label,
|
|
522
|
+
stats_maintenance_unavailable=stats_maintenance_unavailable,
|
|
523
|
+
)
|
|
485
524
|
out[key] = val
|
|
486
525
|
return out
|
|
487
526
|
finally:
|