cctally 1.43.1 → 1.43.2

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 CHANGED
@@ -5,6 +5,11 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.43.2] - 2026-06-13
9
+
10
+ ### Added
11
+ - `refresh-usage` now repaints a locally-running dashboard instantly. After a successful force-refresh it sends a best-effort `POST /api/sync?refresh=0` (a new rebuild-only mode that skips the embedded OAuth re-fetch) to `127.0.0.1:8789`, so the dashboard reflects the new 7d/5h value within ~1s instead of waiting up to one `--sync-interval`. Fully best-effort: with no dashboard running, output and exit codes are unchanged (#180).
12
+
8
13
  ## [1.43.1] - 2026-06-13
9
14
 
10
15
  ### Fixed
@@ -5459,8 +5459,12 @@ class DashboardHTTPHandler(BaseHTTPRequestHandler):
5459
5459
  self.send_error(503, "sync in progress")
5460
5460
  return
5461
5461
  try:
5462
+ do_refresh = (
5463
+ urllib.parse.parse_qs(urllib.parse.urlsplit(self.path).query)
5464
+ .get("refresh", ["1"])[0] != "0"
5465
+ )
5462
5466
  warnings: list = []
5463
- if not type(self).no_sync:
5467
+ if do_refresh and not type(self).no_sync:
5464
5468
  result = _refresh_usage_inproc()
5465
5469
  if result.status != "ok":
5466
5470
  warnings.append({"code": result.status})
@@ -649,6 +649,42 @@ def _refresh_usage_inproc(timeout_seconds: float = 5.0) -> _RefreshUsageResult:
649
649
  return _RefreshUsageResult(status="ok", payload=payload, warnings=warnings)
650
650
 
651
651
 
652
+ def _nudge_dashboard_repaint(port: int = 8789, timeout_seconds: float = 3.0) -> None:
653
+ """Best-effort: tell a locally-running dashboard to rebuild+broadcast NOW.
654
+
655
+ refresh-usage already persisted the new usage value; this only shortens
656
+ the dashboard's repaint latency from <=sync-interval to ~instant by
657
+ POSTing the rebuild-only /api/sync?refresh=0 path. Swallows EVERY error
658
+ (no dashboard listening, timeout, HTTP non-2xx) — never raises, never
659
+ prints, never changes cmd_refresh_usage's exit code.
660
+
661
+ Targets loopback 127.0.0.1 (reaches a local dashboard even when it bound
662
+ 0.0.0.0) on the default port 8789. A dashboard on a non-default port (or
663
+ none) simply isn't nudged and self-heals within its --sync-interval.
664
+
665
+ CSRF: the dashboard's _check_origin_csrf requires Origin/Host authority
666
+ parity. urllib auto-sets Host from the URL; we set Origin to the
667
+ byte-identical authority so the POST is accepted (not 403). The timeout
668
+ is 3.0s — comfortably above the server's _DASHBOARD_SYNC_LOCK_TIMEOUT_
669
+ SECONDS (2.0s) bounded lock-wait — so the client stays connected until
670
+ the 204 lands and never leaves a broken-pipe log line in the dashboard's
671
+ terminal. In the common case (lock free) it returns in single-digit ms.
672
+ """
673
+ # Whole body (incl. Request construction) inside the try so the
674
+ # "swallows EVERY error" contract holds structurally, not just because
675
+ # Request() happens to be total for the hardcoded loopback URL.
676
+ try:
677
+ url = f"http://127.0.0.1:{port}/api/sync?refresh=0"
678
+ req = urllib.request.Request(
679
+ url, data=b"", method="POST",
680
+ headers={"Origin": f"http://127.0.0.1:{port}"},
681
+ )
682
+ with urllib.request.urlopen(req, timeout=timeout_seconds) as resp:
683
+ resp.read()
684
+ except Exception:
685
+ pass
686
+
687
+
652
688
  def cmd_refresh_usage(args: argparse.Namespace) -> int:
653
689
  """Force-fetch the OAuth usage API and persist via cmd_record_usage.
654
690
 
@@ -681,6 +717,9 @@ def cmd_refresh_usage(args: argparse.Namespace) -> int:
681
717
  color = c._forecast_color_enabled(color_mode, sys.stdout)
682
718
  now_epoch = int(dt.datetime.now(dt.timezone.utc).timestamp())
683
719
  print(_render_refresh_usage_text(payload, color=color, now_epoch=now_epoch))
720
+ # Best-effort: repaint a locally-running dashboard NOW (the value is
721
+ # already persisted). Fires in normal/--json/--quiet alike; only on ok.
722
+ c._nudge_dashboard_repaint()
684
723
  return 0
685
724
 
686
725
  if result.status == "rate_limited":
package/bin/cctally CHANGED
@@ -718,6 +718,7 @@ _bust_statusline_cache = _cctally_refresh._bust_statusline_cache
718
718
  _freshness_label = _cctally_refresh._freshness_label
719
719
  _cmd_refresh_usage_handle_rate_limit = _cctally_refresh._cmd_refresh_usage_handle_rate_limit
720
720
  _refresh_usage_inproc = _cctally_refresh._refresh_usage_inproc
721
+ _nudge_dashboard_repaint = _cctally_refresh._nudge_dashboard_repaint
721
722
  cmd_refresh_usage = _cctally_refresh.cmd_refresh_usage
722
723
  _hook_tick_oauth_refresh = _cctally_refresh._hook_tick_oauth_refresh
723
724
  _hook_tick_make_mock_refresh = _cctally_refresh._hook_tick_make_mock_refresh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cctally",
3
- "version": "1.43.1",
3
+ "version": "1.43.2",
4
4
  "description": "Claude Code usage tracker and local dashboard for Pro/Max subscription limits - weekly cost-per-percent trend, quota forecasts, threshold alerts. ccusage-compatible.",
5
5
  "homepage": "https://github.com/omrikais/cctally",
6
6
  "repository": {