ccusage 0.3.0__tar.gz → 0.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ccusage
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Claude Code usage monitor — fetches rate limits from Anthropic's API
5
5
  Project-URL: Homepage, https://github.com/wakamex/ccusage
6
6
  Project-URL: Source, https://github.com/wakamex/ccusage
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ccusage"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Claude Code usage monitor — fetches rate limits from Anthropic's API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -0,0 +1,9 @@
1
+ ccusage can renew an expired Claude login by itself again. Since Anthropic stopped accepting its refresh request, a config directory that Claude Code does not keep fresh, such as one used only by a second daemon, logged `Token refresh failed (403)` on every poll once its access token expired, about 8 hours after login. Directories that Claude Code uses were unaffected, because Claude Code renews their tokens.
2
+
3
+ ## Changes
4
+
5
+ - Fix OAuth token refresh, which Anthropic rejected ([`5f4bad5`](https://github.com/wakamex/ccusage/commit/5f4bad521f16154864aafc7b71b328de279e6176))
6
+
7
+ ## History
8
+
9
+ [v0.3.0...v0.3.1](https://github.com/wakamex/ccusage/compare/v0.3.0...v0.3.1)
@@ -84,7 +84,9 @@ USAGE_FILE = _resolve_claude_path("usage-limits.json")
84
84
  ACCOUNTS_DIR = USAGE_FILE.parent / "usage-limits"
85
85
  DAEMON_INTERVAL = 300 # 5 minutes
86
86
  UNAVAILABLE_RETRY_INTERVAL = 3600 # 1 hour
87
- TOKEN_URL = "https://console.anthropic.com/v1/oauth/token"
87
+ TOKEN_URL = "https://api.anthropic.com/v1/oauth/token"
88
+ # Anthropic's edge rejects the default urllib User-Agent with a 403.
89
+ USER_AGENT = "claude-code/2.1.71"
88
90
  CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e" # Claude Code's public OAuth client
89
91
 
90
92
 
@@ -203,7 +205,7 @@ def refresh_credentials(creds: dict) -> dict:
203
205
  req = urllib.request.Request(
204
206
  TOKEN_URL,
205
207
  data=payload,
206
- headers={"Content-Type": "application/json"},
208
+ headers={"Content-Type": "application/json", "User-Agent": USER_AGENT},
207
209
  )
208
210
  try:
209
211
  with urllib.request.urlopen(req, timeout=10) as resp:
@@ -267,7 +269,7 @@ def fetch_usage() -> dict:
267
269
  headers={
268
270
  "Authorization": f"Bearer {token}",
269
271
  "Content-Type": "application/json",
270
- "User-Agent": "claude-code/2.1.71",
272
+ "User-Agent": USER_AGENT,
271
273
  "anthropic-beta": "oauth-2025-04-20",
272
274
  },
273
275
  )
@@ -117,6 +117,7 @@ class FetchUsageTests(unittest.TestCase):
117
117
  "client_id": ccusage.CLIENT_ID,
118
118
  },
119
119
  )
120
+ self.assertEqual(req.get_header("User-agent"), ccusage.USER_AGENT)
120
121
  return _json_response(REFRESH_RESULT)
121
122
  self.assertEqual(req.headers["Authorization"], "Bearer new-token")
122
123
  return _json_response({"five_hour": {"utilization": 4.0}})
@@ -4,5 +4,5 @@ requires-python = ">=3.12"
4
4
 
5
5
  [[package]]
6
6
  name = "ccusage"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  source = { editable = "." }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes