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 +5 -0
- package/bin/_cctally_dashboard.py +5 -1
- package/bin/_cctally_refresh.py +39 -0
- package/bin/cctally +1 -0
- package/package.json +1 -1
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})
|
package/bin/_cctally_refresh.py
CHANGED
|
@@ -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.
|
|
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": {
|