claude-multiacc 2.0.23 → 2.0.24
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 +4 -0
- package/lib/__pycache__/audit.cpython-312.pyc +0 -0
- package/lib/__pycache__/keychain.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_policy.cpython-312.pyc +0 -0
- package/lib/__pycache__/selector_primitives.cpython-312.pyc +0 -0
- package/lib/codex_reset.py +5 -5
- package/package.json +1 -1
- package/tests/__pycache__/test_codex_reset.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_polling.cpython-312.pyc +0 -0
- package/tests/__pycache__/test_codex_reset_windows.cpython-312.pyc +0 -0
- package/tests/test_codex_reset_polling.py +94 -0
package/README.md
CHANGED
|
@@ -380,6 +380,10 @@ Codex-specific notes:
|
|
|
380
380
|
`CODEX_MULTIACC_AUTO_RESET=0` only for emergency rollback/testing.
|
|
381
381
|
A finished-limit verdict takes precedence over a conflicting percentage. The
|
|
382
382
|
redemption key excludes unused model windows whose reset clocks move between reads.
|
|
383
|
+
Eligible accounts, including spent ones, check the credit endpoint every five
|
|
384
|
+
minutes even if the usage summary still says zero credits. A newly granted reset
|
|
385
|
+
is redeemed during that pass and clears the old limit; no-credit results are
|
|
386
|
+
checked again next pass, without waiting for the weekly reset date.
|
|
383
387
|
- **API-key logins are rejected** — ChatGPT subscription accounts only, matching the
|
|
384
388
|
addon's no-API-keys rule.
|
|
385
389
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/codex_reset.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Automatic redemption of earned Codex usage-limit reset credits.
|
|
2
2
|
|
|
3
3
|
The Codex CLI exposes reset credits through the same authenticated backend as
|
|
4
|
-
usage telemetry.
|
|
5
|
-
|
|
4
|
+
usage telemetry. Each scheduled eligible usage pass reads the credit endpoint,
|
|
5
|
+
even when the usage response's embedded credit summary is empty or out of date.
|
|
6
|
+
A pending idempotency key is written
|
|
6
7
|
before the mutation so a lost response can be retried without spending twice.
|
|
7
8
|
"""
|
|
8
9
|
|
|
@@ -121,9 +122,8 @@ def try_auto_redeem(account_dir: str, account_id: str, used_percent: int,
|
|
|
121
122
|
return {"status": "cooldown", "outcome": state.get("outcome")}
|
|
122
123
|
list_url, consume_url = _reset_urls(usage_url)
|
|
123
124
|
if disposition == "new":
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return {"status": "no_credit"}
|
|
125
|
+
# Newly granted credits can precede the usage response's embedded summary.
|
|
126
|
+
# The scheduled usage cadence bounds these reads, including spent accounts.
|
|
127
127
|
try:
|
|
128
128
|
available_count, credit_id = _credit_to_redeem(_request(list_url, headers))
|
|
129
129
|
except Exception as error:
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Scheduled passes discover newly granted resets despite an old usage summary."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
import test_codex_reset as fixtures
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ResetPollingTest(fixtures.CodexPoolSandbox, unittest.TestCase):
|
|
13
|
+
def setUp(self):
|
|
14
|
+
super().setUp()
|
|
15
|
+
now = int(time.time())
|
|
16
|
+
self.server.state["usage"] = {"rate_limit": {"allowed": False, "limit_reached": True,
|
|
17
|
+
"primary_window": {"used_percent": 100, "limit_window_seconds": 604800,
|
|
18
|
+
"reset_at": now + 6 * 86400}},
|
|
19
|
+
"rate_limit_reset_credits": {"available_count": 0}}
|
|
20
|
+
self.server.state["credits"] = {"available_count": 0, "credits": []}
|
|
21
|
+
|
|
22
|
+
def run_scheduled(self, enabled="1"):
|
|
23
|
+
base = f"http://127.0.0.1:{self.server.server_port}/wham"
|
|
24
|
+
env = dict(os.environ, CODEX_ACCOUNTS_ROOT=str(self.pool), CODEX_MULTIACC_NO_SYNC="1",
|
|
25
|
+
CODEX_MULTIACC_AUTO_RESET=enabled, CODEX_MULTIACC_USAGE_URL=f"{base}/usage",
|
|
26
|
+
PYTHONDONTWRITEBYTECODE="1")
|
|
27
|
+
for key in ("CODEX_MULTIACC_MIN_FETCH", "CODEX_MULTIACC_RESET_CREDITS_URL",
|
|
28
|
+
"CODEX_MULTIACC_RESET_CONSUME_URL"):
|
|
29
|
+
env.pop(key, None)
|
|
30
|
+
result = subprocess.run([fixtures.REPO / "bin/codex-accounts", "limits", "--quiet"],
|
|
31
|
+
capture_output=True, text=True, env=env, timeout=20, check=False)
|
|
32
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
33
|
+
|
|
34
|
+
def next_pass(self):
|
|
35
|
+
path = self.pool / "acct-01/limits.json"
|
|
36
|
+
usage = json.loads(path.read_text())
|
|
37
|
+
usage["fetched_at"] = int(time.time()) - 301
|
|
38
|
+
path.write_text(json.dumps(usage))
|
|
39
|
+
|
|
40
|
+
def grant_credit(self):
|
|
41
|
+
self.server.state["credits"] = {"available_count": 1, "credits": [
|
|
42
|
+
{"id": "newly-granted", "status": "available", "expires_at": "2099-01-01T00:00:00Z"}]}
|
|
43
|
+
self.server.state["responses"] = [(200, {"code": "reset", "windows_reset": 1})]
|
|
44
|
+
|
|
45
|
+
def test_limited_account_polls_again_and_uses_a_new_credit(self):
|
|
46
|
+
self.run_scheduled()
|
|
47
|
+
self.assertEqual(self.server.state["credit_gets"], 1)
|
|
48
|
+
marker = self.pool / "acct-01/.limited"
|
|
49
|
+
self.assertTrue(marker.exists())
|
|
50
|
+
self.assertEqual(self.server.state["posts"], [])
|
|
51
|
+
self.run_scheduled()
|
|
52
|
+
self.assertEqual(self.server.state["credit_gets"], 1) # Normal fetch cadence still applies.
|
|
53
|
+
self.next_pass()
|
|
54
|
+
self.run_scheduled()
|
|
55
|
+
self.assertEqual(self.server.state["credit_gets"], 2)
|
|
56
|
+
self.assertTrue(marker.exists())
|
|
57
|
+
self.grant_credit()
|
|
58
|
+
self.next_pass()
|
|
59
|
+
self.run_scheduled()
|
|
60
|
+
self.assertEqual(self.server.state["credit_gets"], 3)
|
|
61
|
+
self.assertEqual(self.server.state["posts"][0]["credit_id"], "newly-granted")
|
|
62
|
+
self.assertFalse(marker.exists())
|
|
63
|
+
receipt = json.loads((self.pool / "acct-01/.usage-reset.json").read_text())
|
|
64
|
+
self.assertEqual(receipt["state"], "complete")
|
|
65
|
+
usage = json.loads((self.pool / "acct-01/limits.json").read_text())
|
|
66
|
+
self.assertEqual(usage["fetched_at"], 0) # Pre-reset exhaustion cannot exclude it again.
|
|
67
|
+
|
|
68
|
+
def test_malformed_usage_summary_does_not_hide_a_credit(self):
|
|
69
|
+
self.server.state["usage"]["rate_limit_reset_credits"]["available_count"] = "unknown"
|
|
70
|
+
self.grant_credit()
|
|
71
|
+
self.run_scheduled()
|
|
72
|
+
self.assertEqual(len(self.server.state["posts"]), 1)
|
|
73
|
+
|
|
74
|
+
def test_credit_lookup_failure_preserves_limit_and_retries_next_pass(self):
|
|
75
|
+
self.server.state["credits"] = [] # An unreadable credit response is not a reset.
|
|
76
|
+
self.run_scheduled()
|
|
77
|
+
self.assertTrue((self.pool / "acct-01/.limited").exists())
|
|
78
|
+
self.assertEqual(self.server.state["posts"], [])
|
|
79
|
+
self.grant_credit()
|
|
80
|
+
self.next_pass()
|
|
81
|
+
self.run_scheduled()
|
|
82
|
+
self.assertEqual(self.server.state["credit_gets"], 2)
|
|
83
|
+
self.assertFalse((self.pool / "acct-01/.limited").exists())
|
|
84
|
+
|
|
85
|
+
def test_disabled_automation_does_not_query_or_spend_credits(self):
|
|
86
|
+
self.grant_credit()
|
|
87
|
+
self.run_scheduled(enabled="0")
|
|
88
|
+
self.assertEqual(self.server.state["credit_gets"], 0)
|
|
89
|
+
self.assertEqual(self.server.state["posts"], [])
|
|
90
|
+
self.assertTrue((self.pool / "acct-01/.limited").exists())
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
unittest.main()
|