tokenmaxxing 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/PKG-INFO +1 -1
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/pyproject.toml +1 -1
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/src/tokenmaxxing/__init__.py +1 -1
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/src/tokenmaxxing/app.py +24 -3
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/.gitignore +0 -0
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/LICENSE +0 -0
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/README.md +0 -0
- {tokenmaxxing-0.1.0 → tokenmaxxing-0.1.2}/src/tokenmaxxing/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tokenmaxxing
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Menu bar app showing your live Claude Code session and weekly usage as a colored progress bar.
|
|
5
5
|
Project-URL: Homepage, https://github.com/alvations/tokenmaxxing
|
|
6
6
|
Project-URL: Repository, https://github.com/alvations/tokenmaxxing
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tokenmaxxing"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Menu bar app showing your live Claude Code session and weekly usage as a colored progress bar."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -61,6 +61,8 @@ LOADING_TEXT = "loading…"
|
|
|
61
61
|
TIMER_INTERVAL = 1
|
|
62
62
|
|
|
63
63
|
REFRESH_SECONDS = 300 # 5 min: OAuth usage endpoint has aggressive undocumented rate limits
|
|
64
|
+
STALE_AFTER_SECONDS = 600 # 10 min: only mark cached data "(stale)" past this age — one
|
|
65
|
+
# missed poll cycle (~5 min) shouldn't trigger the warning
|
|
64
66
|
REFRESH_MAX_BACKOFF = 1800 # Max backoff: 30 minutes
|
|
65
67
|
BACKOFF_EXP_CAP = 11 # 2^11 = 2048s, lets REFRESH_MAX_BACKOFF (1800s) become the binding cap
|
|
66
68
|
HTTP_TIMEOUT = 10
|
|
@@ -623,12 +625,20 @@ class ClaudeMonitorApp(rumps.App):
|
|
|
623
625
|
consecutive_failures = 0
|
|
624
626
|
self._backoff_until = 0
|
|
625
627
|
self._record_snapshot(payload)
|
|
628
|
+
sleep_for = REFRESH_SECONDS
|
|
626
629
|
elif is_rate_limited:
|
|
627
630
|
consecutive_failures += 1
|
|
628
631
|
backoff = _calc_backoff(consecutive_failures)
|
|
629
632
|
self._backoff_until = time.time() + backoff
|
|
633
|
+
# Retry as soon as the backoff window allows, instead of waiting
|
|
634
|
+
# the full REFRESH_SECONDS — otherwise the menu keeps showing
|
|
635
|
+
# "[rate limited]" for ~5 min after a single transient 429 even
|
|
636
|
+
# when the API recovered within seconds.
|
|
637
|
+
sleep_for = backoff
|
|
638
|
+
else:
|
|
639
|
+
sleep_for = REFRESH_SECONDS
|
|
630
640
|
|
|
631
|
-
self._wake.wait(timeout=
|
|
641
|
+
self._wake.wait(timeout=sleep_for)
|
|
632
642
|
self._wake.clear()
|
|
633
643
|
|
|
634
644
|
def _apply_pending(self, _sender):
|
|
@@ -799,13 +809,24 @@ class ClaudeMonitorApp(rumps.App):
|
|
|
799
809
|
return EXTRA_USAGE_LABEL + (SEPARATOR.join(parts) or EXTRA_USAGE_ENABLED)
|
|
800
810
|
|
|
801
811
|
def _get_status_suffix(self) -> str:
|
|
802
|
-
"""Return status suffix: ' [rate limited]', ' (stale)', or ''.
|
|
812
|
+
"""Return status suffix: ' [rate limited]', ' (stale)', or ''.
|
|
813
|
+
|
|
814
|
+
'(stale)' only fires when cached data is older than STALE_AFTER_SECONDS,
|
|
815
|
+
not on every transient error — a single failed poll shouldn't make
|
|
816
|
+
~5-min-old data look untrusted.
|
|
817
|
+
"""
|
|
803
818
|
if self._is_rate_limited:
|
|
804
819
|
return STATUS_RATE_LIMITED
|
|
805
|
-
if self._latest_error:
|
|
820
|
+
if self._latest_error and self._is_data_old():
|
|
806
821
|
return STATUS_STALE
|
|
807
822
|
return ""
|
|
808
823
|
|
|
824
|
+
def _is_data_old(self) -> bool:
|
|
825
|
+
if self._latest_at is None:
|
|
826
|
+
return True # never had a successful poll
|
|
827
|
+
age = (_utc_now() - self._latest_at).total_seconds()
|
|
828
|
+
return age > STALE_AFTER_SECONDS
|
|
829
|
+
|
|
809
830
|
def _view_is_available(self, view) -> bool:
|
|
810
831
|
"""Check if a view dict has valid utilization data."""
|
|
811
832
|
return isinstance(view, dict) and view.get("utilization") is not None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|