cctally 1.65.0 → 1.66.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/bin/_cctally_alerts.py +1 -1
  3. package/bin/_cctally_cache.py +203 -22
  4. package/bin/_cctally_cache_report.py +7 -5
  5. package/bin/_cctally_core.py +49 -14
  6. package/bin/_cctally_dashboard.py +481 -4891
  7. package/bin/_cctally_dashboard_cache_report.py +714 -0
  8. package/bin/_cctally_dashboard_conversation.py +771 -0
  9. package/bin/_cctally_dashboard_envelope.py +1520 -0
  10. package/bin/_cctally_dashboard_share.py +2012 -0
  11. package/bin/_cctally_db.py +127 -5
  12. package/bin/_cctally_doctor.py +104 -3
  13. package/bin/_cctally_five_hour.py +2 -1
  14. package/bin/_cctally_forecast.py +78 -135
  15. package/bin/_cctally_parser.py +919 -784
  16. package/bin/_cctally_percent_breakdown.py +1 -1
  17. package/bin/_cctally_pricing_check.py +39 -0
  18. package/bin/_cctally_project.py +8 -5
  19. package/bin/_cctally_record.py +279 -274
  20. package/bin/_cctally_reporting.py +1 -1
  21. package/bin/_cctally_sync_week.py +1 -1
  22. package/bin/_cctally_telemetry.py +3 -5
  23. package/bin/_cctally_tui.py +42 -18
  24. package/bin/_lib_alert_dispatch.py +1 -1
  25. package/bin/_lib_blocks.py +6 -1
  26. package/bin/_lib_credit.py +133 -0
  27. package/bin/_lib_doctor.py +150 -1
  28. package/bin/_lib_five_hour.py +34 -0
  29. package/bin/_lib_forecast.py +144 -0
  30. package/bin/_lib_json_envelope.py +38 -0
  31. package/bin/_lib_jsonl.py +115 -73
  32. package/bin/_lib_log.py +96 -0
  33. package/bin/_lib_pricing.py +47 -1
  34. package/bin/_lib_pricing_check.py +25 -3
  35. package/bin/_lib_record.py +179 -0
  36. package/bin/_lib_render.py +18 -10
  37. package/bin/_lib_snapshot_cache.py +61 -0
  38. package/bin/cctally +51 -7
  39. package/bin/cctally-dashboard +2 -2
  40. package/bin/cctally-statusline +1 -0
  41. package/bin/cctally-tui +2 -2
  42. package/package.json +10 -1
@@ -156,7 +156,7 @@ def cmd_percent_breakdown(args: argparse.Namespace) -> int:
156
156
  }
157
157
 
158
158
  if args.json:
159
- print(json.dumps(output, indent=2))
159
+ print(json.dumps(c.stamp_schema_version(output), indent=2))
160
160
  return 0
161
161
 
162
162
  # Prefer the reset-adjusted ISO timestamps in the terminal header
@@ -283,6 +283,14 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
283
283
 
284
284
  drift = {"value_drift": [], "missing_from_us": [], "ahead_of_litellm": []}
285
285
  existence = {"status": "skipped", "unpriced_vendor_models": []}
286
+ # staleSuppressions is network-derived (needs the LiteLLM scoped table), so
287
+ # under --offline it is SKIPPED, not degraded — matching the offline branch,
288
+ # which leaves degraded_components empty (#279 S7 W7). expiredSuppressions is
289
+ # date-derived and runs OFFLINE too, off the _command_as_of() clock seam.
290
+ stale_suppressions: list = []
291
+ expired_suppressions = _lib_pricing_check.expired_allowlist_entries(
292
+ c.PRICING_DRIFT_ALLOWLIST, now_utc,
293
+ )
286
294
 
287
295
  if not args.offline:
288
296
  litellm, ok = _fetch_litellm_prices()
@@ -297,6 +305,10 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
297
305
  "missing_from_us": list(res.missing_from_us),
298
306
  "ahead_of_litellm": list(res.ahead_of_litellm),
299
307
  }
308
+ stale_suppressions = _lib_pricing_check.stale_allowlist_entries(
309
+ c.PRICING_DRIFT_ALLOWLIST, c.CLAUDE_MODEL_PRICING,
310
+ c.CODEX_MODEL_PRICING, scoped,
311
+ )
300
312
  else:
301
313
  status = "degraded"
302
314
  degraded.append("litellm")
@@ -307,11 +319,15 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
307
319
 
308
320
  # Actionable = any finding on a leg that ran. `ahead_of_litellm` is
309
321
  # NEVER actionable (invariant #2). A degraded leg contributes no finding.
322
+ # A stale OR expired suppression is actionable (remove the allowlist entry /
323
+ # re-sync the snapshot) — both wired in explicitly (#279 S7 W7).
310
324
  actionable = (
311
325
  bool(coverage)
312
326
  or bool(drift["value_drift"])
313
327
  or bool(drift["missing_from_us"])
314
328
  or bool(existence["unpriced_vendor_models"])
329
+ or bool(stale_suppressions)
330
+ or bool(expired_suppressions)
315
331
  )
316
332
 
317
333
  payload = {
@@ -322,6 +338,12 @@ def cmd_pricing_check(args: argparse.Namespace) -> int:
322
338
  "coverage": [dataclasses.asdict(g) for g in coverage],
323
339
  "drift": drift,
324
340
  "existence": existence,
341
+ # Additive keys on the existing schemaVersion 1 envelope (no bump — CLI
342
+ # contract additive rule). staleSuppressions: allowlist entries whose
343
+ # divergence no longer exists (network). expiredSuppressions: allowlist
344
+ # entries past their `expires` cutover (date).
345
+ "staleSuppressions": list(stale_suppressions),
346
+ "expiredSuppressions": list(expired_suppressions),
325
347
  "litellmSource": c.LITELLM_PRICES_URL,
326
348
  }
327
349
 
@@ -385,6 +407,23 @@ def _render_pricing_check_text(payload: dict, *, offline: bool, actionable: bool
385
407
  out("\n Existence: all vendor models priced.\n")
386
408
  elif ex["status"] == "degraded":
387
409
  out("\n Existence: /v1/models unavailable (skipped).\n")
410
+ stale = payload.get("staleSuppressions") or []
411
+ if stale:
412
+ out(f"\n Stale suppressions ({len(stale)}) — the divergence no "
413
+ f"longer exists, remove the allowlist entry:\n")
414
+ for e in stale:
415
+ fld = f".{e['field']}" if e.get("field") else ""
416
+ out(f" • {e['model']}{fld}\n")
417
+
418
+ # Expired suppressions are date-derived, so they render in BOTH offline and
419
+ # online modes.
420
+ expired = payload.get("expiredSuppressions") or []
421
+ if expired:
422
+ out(f"\n Expired suppressions ({len(expired)}) — past their `expires` "
423
+ f"cutover, remove the allowlist entry / re-sync the snapshot:\n")
424
+ for e in expired:
425
+ fld = f".{e['field']}" if e.get("field") else ""
426
+ out(f" • {e['model']}{fld} (expired {e.get('expires')})\n")
388
427
 
389
428
  # Single-sourced from cmd_pricing_check's exit-code predicate (don't
390
429
  # recompute the four-clause boolean here — it would drift).
@@ -352,7 +352,7 @@ def _project_json_output(
352
352
  "projects": projects_json,
353
353
  "warnings": warnings,
354
354
  }
355
- return json.dumps(payload, indent=2)
355
+ return json.dumps(_cctally().stamp_schema_version(payload), indent=2)
356
356
 
357
357
 
358
358
  def _project_sort_key(row: dict, sort_by: str, order: str):
@@ -390,10 +390,10 @@ def cmd_project(args: argparse.Namespace) -> int:
390
390
  # Flag-combination validation (must run before any expensive work).
391
391
  if args.weeks is not None and args.weeks < 1:
392
392
  eprint("Error: --weeks must be >= 1")
393
- return 1
393
+ return 2
394
394
  if args.weeks is not None and (args.since or args.until):
395
395
  eprint("Error: --weeks cannot be combined with --since/--until")
396
- return 1
396
+ return 2
397
397
  if args.since and args.until:
398
398
  # Parse both as dates using the same multi-format helper shape used
399
399
  # elsewhere in the codebase so YYYY-MM-DD and YYYYMMDD both compare
@@ -415,7 +415,7 @@ def cmd_project(args: argparse.Namespace) -> int:
415
415
  # complaint triggered by garbage input).
416
416
  if since_parsed is not None and until_parsed is not None and since_parsed > until_parsed:
417
417
  eprint("Error: --since must be <= --until")
418
- return 1
418
+ return 2
419
419
 
420
420
  now = _command_as_of()
421
421
  conn = open_db()
@@ -424,7 +424,10 @@ def cmd_project(args: argparse.Namespace) -> int:
424
424
  if args.since or args.until:
425
425
  parsed = c._parse_cli_date_range(args, now_utc=now)
426
426
  if isinstance(parsed, int):
427
- return parsed
427
+ # Translate the ccusage-parity helper's exit 1 into project's own
428
+ # native usage code 2 — project is cctally-native, not a ccusage
429
+ # drop-in (docs/cli-contract.md; #279 S6 W2).
430
+ return 2
428
431
  since_dt, until_dt = parsed
429
432
  since_dt = since_dt.astimezone(dt.timezone.utc)
430
433
  until_dt = until_dt.astimezone(dt.timezone.utc)