cctally 1.67.1 → 1.69.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 (40) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/bin/_cctally_alerts.py +54 -2
  3. package/bin/_cctally_cache.py +754 -109
  4. package/bin/_cctally_cache_report.py +41 -17
  5. package/bin/_cctally_codex.py +109 -4
  6. package/bin/_cctally_config.py +368 -2
  7. package/bin/_cctally_core.py +187 -15
  8. package/bin/_cctally_dashboard.py +679 -5
  9. package/bin/_cctally_dashboard_conversation.py +9 -4
  10. package/bin/_cctally_dashboard_envelope.py +110 -1
  11. package/bin/_cctally_dashboard_share.py +557 -79
  12. package/bin/_cctally_db.py +366 -17
  13. package/bin/_cctally_diff.py +49 -16
  14. package/bin/_cctally_doctor.py +131 -0
  15. package/bin/_cctally_forecast.py +12 -4
  16. package/bin/_cctally_parser.py +321 -92
  17. package/bin/_cctally_project.py +24 -8
  18. package/bin/_cctally_quota.py +1381 -0
  19. package/bin/_cctally_record.py +319 -14
  20. package/bin/_cctally_refresh.py +105 -3
  21. package/bin/_cctally_reporting.py +7 -1
  22. package/bin/_cctally_setup.py +343 -113
  23. package/bin/_cctally_statusline.py +228 -0
  24. package/bin/_cctally_tui.py +787 -7
  25. package/bin/_lib_alert_axes.py +11 -0
  26. package/bin/_lib_codex_hooks.py +367 -0
  27. package/bin/_lib_conversation_query.py +156 -67
  28. package/bin/_lib_doctor.py +238 -0
  29. package/bin/_lib_jsonl.py +529 -162
  30. package/bin/_lib_quota.py +566 -0
  31. package/bin/_lib_share.py +152 -34
  32. package/bin/_lib_snapshot_cache.py +26 -0
  33. package/bin/_lib_source_identity.py +74 -0
  34. package/bin/cctally +325 -10
  35. package/dashboard/static/assets/index-CBbErI-P.js +80 -0
  36. package/dashboard/static/assets/index-kDDVOLa_.css +1 -0
  37. package/dashboard/static/dashboard.html +2 -2
  38. package/package.json +5 -1
  39. package/dashboard/static/assets/index-BybNp_Di.js +0 -80
  40. package/dashboard/static/assets/index-DgAmLK65.css +0 -1
@@ -239,7 +239,7 @@ def _accumulate_entry_into_bucket(
239
239
  mb["cache_read"] += entry.cache_read_tokens
240
240
 
241
241
 
242
- def _project_json_output(
242
+ def _project_json_payload(
243
243
  *,
244
244
  since: dt.datetime,
245
245
  until: dt.datetime,
@@ -250,8 +250,8 @@ def _project_json_output(
250
250
  warnings: list[str],
251
251
  include_breakdown: bool,
252
252
  week_snapshots: dict[dt.datetime, float],
253
- ) -> str:
254
- """Render the project subcommand's --json payload per spec §4.
253
+ ) -> dict:
254
+ """Build the project subcommand's --json payload per spec §4.
255
255
 
256
256
  Accepts rows already sorted by the caller (so ordering flags apply
257
257
  uniformly to both terminal and JSON modes). Aggregates `totals.costUsd`
@@ -325,7 +325,12 @@ def _project_json_output(
325
325
  "projects": projects_json,
326
326
  "warnings": warnings,
327
327
  }
328
- return json.dumps(_cctally().stamp_schema_version(payload), indent=2)
328
+ return _cctally().stamp_schema_version(payload)
329
+
330
+
331
+ def _project_json_output(**kwargs) -> str:
332
+ """Serialize :func:`_project_json_payload` without changing CLI bytes."""
333
+ return json.dumps(_project_json_payload(**kwargs), indent=2)
329
334
 
330
335
 
331
336
  def _project_sort_key(row: dict, sort_by: str, order: str):
@@ -393,8 +398,14 @@ def cmd_project(args: argparse.Namespace) -> int:
393
398
  now = _command_as_of()
394
399
  conn = open_db()
395
400
 
396
- # Resolve [since_dt, until_dt] in UTC.
397
- if args.since or args.until:
401
+ # Resolve [since_dt, until_dt] in UTC. All-provider project dispatch
402
+ # resolves its calendar range once, then injects it here so Claude and
403
+ # Codex describe precisely the same interval. The normal Claude parser
404
+ # remains unchanged for every user-facing legacy invocation.
405
+ source_range = getattr(args, "_source_analytics_range", None)
406
+ if source_range is not None:
407
+ since_dt, until_dt = source_range
408
+ elif args.since or args.until:
398
409
  parsed = c._parse_cli_date_range(args, now_utc=now)
399
410
  if isinstance(parsed, int):
400
411
  # Translate the ccusage-parity helper's exit 1 into project's own
@@ -766,7 +777,7 @@ def cmd_project(args: argparse.Namespace) -> int:
766
777
  return 0
767
778
 
768
779
  if args.json:
769
- print(_project_json_output(
780
+ payload = _project_json_payload(
770
781
  since=since_dt,
771
782
  until=until_dt,
772
783
  weeks_in_range=len(weeks_in_range),
@@ -776,7 +787,12 @@ def cmd_project(args: argparse.Namespace) -> int:
776
787
  warnings=warnings,
777
788
  include_breakdown=args.breakdown,
778
789
  week_snapshots=week_snapshots,
779
- ))
790
+ )
791
+ sink = getattr(args, "_source_result_sink", None)
792
+ if sink is not None:
793
+ sink(payload)
794
+ else:
795
+ print(json.dumps(payload, indent=2))
780
796
  return 0
781
797
 
782
798
  # Terminal path