cdx-manager 0.20.0 → 0.20.1
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/README.md +1 -1
- package/VERSION +1 -1
- package/changelogs/CHANGELOGS_0_20_1.md +6 -0
- package/checksums/release-archives.json +11 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/claude_usage.py +33 -0
- package/src/commands/status.py +3 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br clear="left"/>
|
|
6
6
|
|
|
7
|
-
[](LICENSE) ](LICENSE)  
|
|
8
8
|
|
|
9
9
|
**Stop guessing which AI account still has quota.** `cdx` tracks the rate-limit window of every Codex and Claude account you own, tells you which one is usable right now, and launches it with isolated auth — in one command.
|
|
10
10
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.20.
|
|
1
|
+
0.20.1
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# CDX Manager 0.20.1
|
|
2
|
+
|
|
3
|
+
## Fixes
|
|
4
|
+
|
|
5
|
+
- `cdx status --refresh` no longer reports a false "OAuth access token has expired" alert for a profile that is still logged in. The persisted access token's `expiresAt` is now checked offline before the quota probe runs; once it is known to be expired, the probe is skipped instead of reproducing a predictable 401 (`claude auth status` does not refresh it — only a real Claude Code invocation does). (#11)
|
|
6
|
+
- The stale-quota/local-auth-valid warning path now also recognizes the provider's current `OAuth access token has expired` message, not just the older `invalid authentication credentials` phrasing.
|
|
@@ -309,6 +309,17 @@
|
|
|
309
309
|
"x86_64-pc-windows-msvc": "c5a29140bb9d634f2533084084ca10cb90ca64d34cac1b664998d305b934a7fb",
|
|
310
310
|
"x86_64-unknown-linux-musl": "d1850f3acdede7c2cdcd31b5ca6d5e2372535a32bf75c0e6c8fd0100af7813f2"
|
|
311
311
|
}
|
|
312
|
+
},
|
|
313
|
+
"v0.20.0": {
|
|
314
|
+
"github_tarball_sha256": "8222afa4f8e5fef0291e180450c1b24fb623a6543104f62bd430d27b2157c75b",
|
|
315
|
+
"github_zip_sha256": "c09ab617e1af92bf7c3690fe8d21e497cb772ea270eb8b83ea413b1718d978cd",
|
|
316
|
+
"tray_assets": {
|
|
317
|
+
"aarch64-apple-darwin": "8b6d93b6d67d2b0119c544beca251b5dcaede56b770fc8bed741c36a2f2d868a",
|
|
318
|
+
"aarch64-unknown-linux-musl": "f4bb1bd9a8980f403a7d5af9724dfae4b63345323019e212fe257c4689f7480d",
|
|
319
|
+
"x86_64-apple-darwin": "1e90758238f87c73bf1dadf7d9c3efe2967ab6f65b484d9fd7ed4e3cceae671f",
|
|
320
|
+
"x86_64-pc-windows-msvc": "e88d296e706ae90624a1f82bf14d36d67787d5f73bbb04986c1ca82293178c68",
|
|
321
|
+
"x86_64-unknown-linux-musl": "cf8ec3f5a56bf50ba42d2dfa44f05ecc4471c5a5a819ca8b5180c6d9eff75ff1"
|
|
322
|
+
}
|
|
312
323
|
}
|
|
313
324
|
}
|
|
314
325
|
}
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
package/src/claude_usage.py
CHANGED
|
@@ -129,6 +129,32 @@ def _parse_reset_epoch(value):
|
|
|
129
129
|
return parsed.timestamp()
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
CLAUDE_ACCESS_TOKEN_EXPIRY_SKEW_SECONDS = 60
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _current_epoch_seconds():
|
|
136
|
+
return datetime.now(timezone.utc).timestamp()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _parse_epoch_seconds(value):
|
|
140
|
+
if value in (None, ""):
|
|
141
|
+
return None
|
|
142
|
+
try:
|
|
143
|
+
num = float(value)
|
|
144
|
+
except (TypeError, ValueError):
|
|
145
|
+
return _parse_reset_epoch(value)
|
|
146
|
+
# Claude CLI persists expiresAt in milliseconds since epoch.
|
|
147
|
+
return num / 1000 if num > 1e12 else num
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _access_token_expired(creds, now=None):
|
|
151
|
+
expires_at = _parse_epoch_seconds(creds.get("expiresAt")) if isinstance(creds, dict) else None
|
|
152
|
+
if expires_at is None:
|
|
153
|
+
return False
|
|
154
|
+
now = _current_epoch_seconds() if now is None else now
|
|
155
|
+
return now >= expires_at - CLAUDE_ACCESS_TOKEN_EXPIRY_SKEW_SECONDS
|
|
156
|
+
|
|
157
|
+
|
|
132
158
|
def _safe_float(value):
|
|
133
159
|
if value is None:
|
|
134
160
|
return None
|
|
@@ -241,4 +267,11 @@ def refresh_claude_session_status(session, auth_refresher=None):
|
|
|
241
267
|
creds = _read_claude_credentials(auth_home)
|
|
242
268
|
if not creds or not creds.get("accessToken"):
|
|
243
269
|
return None
|
|
270
|
+
if _access_token_expired(creds):
|
|
271
|
+
# ponytail: `claude auth status` doesn't refresh an expired access
|
|
272
|
+
# token (verified no-op) and only a real Claude Code invocation does.
|
|
273
|
+
# Probing anyway just reproduces a 401 we can already predict
|
|
274
|
+
# offline, so skip it like the "no credentials" case above and leave
|
|
275
|
+
# the cached quota as stale rather than raising a false alert.
|
|
276
|
+
return None
|
|
244
277
|
return fetch_claude_rate_limit_headers(creds["accessToken"])
|
package/src/commands/status.py
CHANGED
|
@@ -790,7 +790,9 @@ def _rows_by_session(rows):
|
|
|
790
790
|
|
|
791
791
|
def _is_invalid_claude_usage_auth(error):
|
|
792
792
|
text = str(error or "").lower()
|
|
793
|
-
|
|
793
|
+
if "http 401" not in text:
|
|
794
|
+
return False
|
|
795
|
+
return "invalid authentication credentials" in text or "oauth access token has expired" in text
|
|
794
796
|
|
|
795
797
|
def _has_valid_local_claude_auth(item, rows_by_name):
|
|
796
798
|
session = item.get("session")
|