maxpool 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KarpelesLab
4
+ Copyright (c) 2026 Max Krasnykh
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,314 @@
1
+ # Maxpool
2
+
3
+ Multi-account proxy for [Claude Code](https://claude.ai/claude-code) that spreads your work across several Claude accounts with adaptive, rate-aware load balancing.
4
+
5
+ Sits transparently between Claude Code and the Anthropic API, managing multiple Claude Max (or API key) accounts. Instead of waiting until one account hits a wall, it continuously balances requests by how much quota each account has *and* how soon that quota resets — so an account about to refresh gets used, and one burning through its budget early gets spared.
6
+
7
+ ![Maxpool TUI](screenshots/maxpool.png)
8
+
9
+ ## Features
10
+
11
+ - **Rate-aware load balancing** — ranks accounts by remaining quota ÷ time-to-reset, not raw usage %, so a near-reset account with quota left is drained (use-it-or-lose-it) and one burning fast early in its window is spared; sequential traffic rotates across accounts instead of funnelling onto one
12
+ - **Interactive account & routing management** — add/import/enable-disable accounts and switch between automatic load balancing and a preferred (manual) account, all from the TUI
13
+ - **Quota-aware routing** — avoids accounts when session (5h) or weekly (7d) quota reaches the configured threshold (default 90%)
14
+ - **Session affinity** — optional session headers keep one Claude Code session on the same account until that account becomes unavailable
15
+ - **Fast failover on 429/overload** — parks the affected account and retries another account before response bytes are sent
16
+ - **Provider fallback profile** — optional `all` profile can use Claude accounts first, then GLM, then Kimi via local custom headers
17
+ - **Provider telemetry** — GLM/Kimi rows show active requests, completed/failed counts, last status/latency, and standard rate-limit headers when providers return them
18
+ - **Rolling load view** — each row shows current in-flight load plus request counts/average latency over the last 15 minutes and 1 hour
19
+ - **Interactive TUI** — real-time dashboard with color-coded quota bars, reset countdowns, activity log, and keyboard controls
20
+ - **Graceful drain on restart** — restart/quit/Ctrl-C stops new requests and waits for active streams to finish before exiting or relaunching
21
+ - **OAuth token management** — automatically refreshes tokens nearing expiry and persists them to config; client token refreshes pass through untouched
22
+ - **Hot-sync accounts** — add accounts via `import` or `login` while the server is running; the server auto-syncs config and **s** can sync immediately
23
+ - **Account deduplication** — detects duplicate accounts by UUID and keeps the most recent
24
+ - **Request logging** — optional full request/response logging for debugging
25
+ - **Zero dependencies** — uses only Node.js built-in modules
26
+
27
+ ## Quick Start
28
+
29
+ Requires Node.js 18+.
30
+
31
+ ```bash
32
+ # Install
33
+ npm install -g maxpool
34
+
35
+ # Add your first account (opens browser for OAuth)
36
+ maxpool login
37
+
38
+ # Add a second account
39
+ maxpool login
40
+
41
+ # Start the proxy
42
+ maxpool server
43
+
44
+ # In another terminal, run Claude Code through the proxy
45
+ maxpool run
46
+ ```
47
+
48
+ You can also import existing Claude Code credentials instead of logging in:
49
+
50
+ ```bash
51
+ claude /login # Log into an account in Claude Code
52
+ maxpool import # Import its credentials
53
+ ```
54
+
55
+ ## Adding Accounts
56
+
57
+ ### OAuth Login (recommended)
58
+
59
+ The easiest way to add accounts — opens your browser for authentication:
60
+
61
+ ```bash
62
+ maxpool login
63
+ ```
64
+
65
+ Uses the same OAuth flow as Claude Code. Auto-detects the account email and subscription tier. Logging in with the same account again updates its credentials.
66
+
67
+ You can add accounts while the server is running — press **s** in the TUI to sync immediately, or wait for automatic sync.
68
+
69
+ ### Import from Claude Code
70
+
71
+ If you already have Claude Code set up, you can import its credentials directly:
72
+
73
+ ```bash
74
+ claude /login # Log into an account in Claude Code
75
+ maxpool import # Import its credentials
76
+ ```
77
+
78
+ Re-importing the same account updates its credentials. You can also import from a custom path:
79
+
80
+ ```bash
81
+ maxpool import --from /path/to/credentials.json
82
+ ```
83
+
84
+ ### API Key
85
+
86
+ For Anthropic API key accounts (billed via Console):
87
+
88
+ ```bash
89
+ maxpool login --api
90
+ ```
91
+
92
+ ## Usage
93
+
94
+ ### Start the proxy server
95
+
96
+ ```bash
97
+ maxpool server
98
+ ```
99
+
100
+ When running from a TTY, shows an interactive TUI with:
101
+ - Account table with session/weekly quota progress bars and reset countdowns
102
+ - Real-time activity log with request tracking
103
+ - Keyboard shortcuts (see below)
104
+
105
+ Falls back to plain log output when not a TTY (e.g. running as a service).
106
+
107
+ #### TUI Keyboard Shortcuts
108
+
109
+ | Key | Action |
110
+ |-----|--------|
111
+ | `a` | Open account management |
112
+ | `m` | Choose automatic routing or a preferred account |
113
+ | `s` | Sync accounts and credentials from config now |
114
+ | `r` | Restart server after draining active requests |
115
+ | `q` | Stop server after draining active requests |
116
+
117
+ State-changing actions show what will happen and require `y` or `n`. In selection mode, use `j`/`k` or arrow keys to navigate, `Enter` to choose, and `Esc` to go back.
118
+
119
+ The Accounts menu can import the current Claude Code login, add an Anthropic API key, enable or disable an account, or permanently delete an idle account. Disabling keeps credentials in config but prevents new requests from using the account. Deletion is blocked while that account has active requests.
120
+
121
+ Routing modes:
122
+
123
+ - **Automatic** spreads requests across healthy accounts using live load, quota pressure, and recent errors.
124
+ - **Manual preference** sends subsequent requests, including the next request from existing idle sessions, to the selected Claude account whenever it is healthy. Maxpool still fails over automatically when necessary and returns to the preferred account after recovery.
125
+
126
+ Routing changes do not move requests already in flight.
127
+
128
+ ### Run Claude Code through the proxy
129
+
130
+ ```bash
131
+ maxpool run
132
+ ```
133
+
134
+ Or manually set the environment:
135
+
136
+ ```bash
137
+ eval "$(maxpool env)"
138
+ claude
139
+ ```
140
+
141
+ `maxpool env` exports only `ANTHROPIC_BASE_URL` by default so Claude Code can keep using your claude.ai subscription login without showing an `ANTHROPIC_API_KEY` auth conflict warning. Use `maxpool env --with-key` only for non-local clients that need to authenticate to the proxy.
142
+
143
+ The proxy also understands an optional internal header profile:
144
+
145
+ - default/absent `x-maxpool-profile`: Claude accounts only
146
+ - `x-maxpool-profile: all`: Claude accounts first, then lower-priority provider fallbacks
147
+
148
+ Provider fallback credentials can be supplied per Claude Code process with `ANTHROPIC_CUSTOM_HEADERS`. Maxpool strips all `x-maxpool-*` headers before forwarding upstream.
149
+
150
+ `x-maxpool-session: <id>` enables session affinity. With this header, the first request for a Claude Code process is routed by the adaptive load balancer, then later requests from the same process keep using that home account while it remains available. If the home account is rate-limited, exhausted, in cooldown, or removed, the session temporarily uses another eligible route. When the home account becomes available again, the session returns to it. For the `all` profile, fallback priority still wins: a session that had to use GLM or Kimi can move back to Claude when a Claude account becomes available again.
151
+
152
+ Provider rows do not use Claude Max session/week bars unless the provider returns compatible quota headers. For GLM/Kimi, Maxpool always tracks operational telemetry (`Act`, `OK`, `Fail`, `Last`) and also parses common `x-ratelimit-*` / `ratelimit-*` headers if present.
153
+
154
+ When GLM/Kimi return 429 without standard retry headers, Maxpool also parses provider-specific JSON error bodies. Z.AI `next_flush_time` / weekly-monthly exhausted messages and Kimi “try again after N seconds” rate-limit messages are converted into provider cooldowns and queue wake-up timing.
155
+
156
+ Every account/provider row also includes load telemetry: `Load current/weight`, `15m <requests> <avg latency>`, and `1h <requests>`. This is based on completed requests retained in memory for the last hour, plus current in-flight requests.
157
+
158
+ ### Restart behavior
159
+
160
+ When you confirm Restart, confirm Stop, press Ctrl-C, or send SIGTERM, Maxpool enters draining shutdown:
161
+
162
+ 1. The proxy stops accepting new requests.
163
+ 2. Existing in-flight streams keep running.
164
+ 3. The process exits when active requests finish.
165
+ 4. If you selected Restart, Maxpool starts a fresh `maxpool server` process in the same terminal.
166
+ 5. Press Ctrl-C again to force exit.
167
+
168
+ Idle Claude Code sessions are not tied to the server process. If the server is restarted while a Claude Code session is idle, its next request reconnects to the new server. If the server is forced closed while a stream is actively running, that stream can still fail because the TCP connection disappears.
169
+
170
+ ### Other commands
171
+
172
+ ```bash
173
+ maxpool accounts # List accounts with subscription tier and token status
174
+ maxpool accounts -v # Also show token expiry times
175
+ maxpool status # Show live proxy status (requires running server)
176
+ maxpool remove <name> # Remove an account
177
+ maxpool api <path> # Call an API endpoint with account credentials
178
+ maxpool help # Show all commands
179
+ ```
180
+
181
+ ### Request logging
182
+
183
+ Log full request/response details to a directory (one file per request):
184
+
185
+ ```bash
186
+ maxpool server --log-to /tmp/requests
187
+ ```
188
+
189
+ Request logging includes prompt and response bodies. Use it only for short debugging windows and delete logs afterwards.
190
+
191
+ ## Configuration
192
+
193
+ Config is stored at `~/.config/maxpool.json` (or `$XDG_CONFIG_HOME/maxpool.json`). A random proxy API key is generated on first use.
194
+
195
+ Override the config path with `TEAMCLAUDE_CONFIG`:
196
+
197
+ ```bash
198
+ TEAMCLAUDE_CONFIG=./my-config.json maxpool server
199
+ ```
200
+
201
+ ### Config format
202
+
203
+ ```json
204
+ {
205
+ "proxy": {
206
+ "host": "127.0.0.1",
207
+ "port": 3456,
208
+ "apiKey": "tc-auto-generated-key"
209
+ },
210
+ "upstream": "https://api.anthropic.com",
211
+ "switchThreshold": 0.90,
212
+ "scheduler": {
213
+ "mode": "adaptive-least-loaded",
214
+ "safetyMaxActivePerAccount": 50,
215
+ "safetyMaxGlobalActive": 150,
216
+ "cooldownMs": 30000,
217
+ "maxCooldownMs": 900000,
218
+ "weeklySoftThreshold": 0.65,
219
+ "weeklyReserveThreshold": 0.85,
220
+ "weeklyCriticalThreshold": 0.95,
221
+ "weeklyExhaustedThreshold": 0.985,
222
+ "weeklyBurnDebtWeight": 0.6
223
+ },
224
+ "retry": {
225
+ "maxAttemptsPerRequest": 0,
226
+ "maxRetryBufferBytes": 10485760
227
+ },
228
+ "queue": {
229
+ "enabled": true,
230
+ "maxWaitMs": 86400000,
231
+ "autoMaxWaitMs": null,
232
+ "capacityMaxWaitMs": 900000,
233
+ "maxQueuedBodyBytes": 268435456,
234
+ "weeklyMaxWaitMs": 0,
235
+ "pollMs": 1000
236
+ },
237
+ "shutdown": {
238
+ "drainTimeoutMs": 600000
239
+ },
240
+ "accounts": [
241
+ {
242
+ "name": "user@example.com",
243
+ "type": "oauth",
244
+ "accountUuid": "...",
245
+ "accessToken": "sk-ant-oat01-...",
246
+ "refreshToken": "sk-ant-ort01-...",
247
+ "expiresAt": 1774384968427
248
+ }
249
+ ]
250
+ }
251
+ ```
252
+
253
+ | Field | Description |
254
+ |-------|-------------|
255
+ | `proxy.host` | Local interface the proxy listens on; defaults to `127.0.0.1` |
256
+ | `proxy.port` | Local port the proxy listens on |
257
+ | `proxy.apiKey` | API key clients use for status/admin requests |
258
+ | `upstream` | Upstream API base URL |
259
+ | `switchThreshold` | Quota utilization (0–1) at which an account is avoided |
260
+ | `scheduler.safetyMaxActivePerAccount` | Emergency circuit breaker, not a normal capacity cap |
261
+ | `scheduler.safetyMaxGlobalActive` | Emergency global circuit breaker |
262
+ | `retry.maxAttemptsPerRequest` | Retry attempts before returning an error; `0` means one pass over accounts |
263
+ | `retry.maxRetryBufferBytes` | Maximum buffered request body eligible for cross-account retry |
264
+ | `queue.enabled` | Hold requests instead of returning 429 when every eligible route is temporarily unavailable |
265
+ | `queue.maxWaitMs` | Hard maximum time a request can wait in the proxy queue before returning an error; defaults to 24h for long-running agent loops |
266
+ | `queue.autoMaxWaitMs` | Optional shorter auto-queue cap. Set to `null` or omit it to use `queue.maxWaitMs`; set a number for interactive sessions where you prefer fast errors |
267
+ | `queue.capacityMaxWaitMs` | Separate cap for repeated upstream 5xx/overload failures; defaults to 15m so broken providers do not park requests for 24h |
268
+ | `queue.maxQueuedBodyBytes` | Maximum request body Maxpool will hold in memory while waiting for capacity before the request has been sent upstream; defaults to 256 MiB |
269
+ | `queue.weeklyMaxWaitMs` | Optional cap for weekly-limit waits. Defaults to `0`, so weekly exhaustion fails fast instead of parking requests for days |
270
+ | `queue.pollMs` | How often queued requests check for a recovered account/provider |
271
+ | `queue.heartbeatMs` | SSE heartbeat interval for queued streaming requests; defaults to 10s so Claude Code keeps the queued connection alive |
272
+ | `shutdown.drainTimeoutMs` | Maximum time quit/Ctrl-C waits for active requests before exiting |
273
+
274
+ Weekly Claude quota is treated as long-horizon budget, not the same as the 5-hour session cap:
275
+
276
+ - `normal`: accepts new and sticky sessions.
277
+ - `soft`: remains available, but new sessions prefer cooler accounts.
278
+ - `reserve`: existing sticky sessions can continue; new sessions use other Claude accounts when possible.
279
+ - `critical`: avoided unless no healthier eligible route exists. This can be raw weekly usage or reset-aware pace pressure.
280
+ - `exhausted`: unavailable until weekly reset or upstream recovery.
281
+
282
+ The weekly usage bar shows raw upstream utilization and reset timing. Reset-aware burn rate is a separate pace signal used for routing pressure; it can mark an account as `Pace critical`, but only raw near-exhaustion or upstream rejection can show/block as `Wk exhausted`.
283
+
284
+ ## How It Works
285
+
286
+ 1. Claude Code connects to the local proxy instead of `api.anthropic.com`
287
+ 2. The proxy selects the least-loaded healthy account and forwards requests with that account's credentials
288
+ 3. If the client sends `x-maxpool-session`, the session is pinned to that account while it stays available
289
+ 4. OAuth tokens expiring within 5 minutes are automatically refreshed and persisted to config
290
+ 5. Rate limit headers from the API (`anthropic-ratelimit-unified-*`) track session (5h) and weekly (7d) quota utilization
291
+ 6. 5-hour quota controls immediate availability; weekly quota controls new-session admission and preservation; weekly `critical` is last-resort, while weekly `exhausted` is blocked
292
+ 7. Account quota 429s cool down only that account and fail over before response bytes are sent
293
+ 8. Anthropic's server-side 429 and 529 responses first fail over across every eligible Claude account; only a request-wide set of matching failures with no concurrent Claude success promotes to the shared circuit breaker. One real request probes recovery after `retry-after`, then queued work resumes automatically
294
+ 9. Queued streaming requests receive SSE heartbeats, preventing Claude Code's client timeout from abandoning temporary waits
295
+ 10. Transient network errors (connection reset, timeout) fail over before the stream starts; if every eligible route has a network failure, the proxy returns `503 connection_unavailable` instead of a quota error
296
+ 11. In the `all` profile only, if all Claude accounts are unavailable, provider fallbacks are tried by priority: GLM before Kimi
297
+ 12. If all eligible accounts/providers are temporarily unavailable for a temporary reason (5h/session limit, provider cooldown, short 429), the proxy queues the request and retries when one recovers
298
+ 13. Repeated upstream 5xx/overload failures use the shorter `capacityMaxWaitMs` cap, not the long quota wait
299
+ 14. Weekly exhaustion and non-retryable 4xx errors fail fast by default; if the queue wait expires, returns 429 with the soonest retry time
300
+ 15. Temporary OAuth refresh failures cool the account down and queue/fail over; invalid refresh credentials disable only that account and require login
301
+ 16. In an interactive terminal, the server runs under a foreground supervisor so confirmed Restart (`r`) can drain and restart without detaching the replacement TUI
302
+ 17. When Restart is confirmed, new upstream admission pauses immediately. Existing upstream requests finish, queued requests cannot deadlock restart, and their sockets close during relaunch so Claude Code reconnects automatically
303
+ 18. Client token refresh requests (`/v1/oauth/token`) are relayed to upstream untouched — the proxy and client manage their own token lifecycles independently
304
+
305
+ ## Credits
306
+
307
+ maxpool is a fork of [KarpelesLab/teamclaude](https://github.com/KarpelesLab/teamclaude)
308
+ by Mark Karpelès, substantially extended with rate-aware (use-it-or-lose-it)
309
+ load balancing, interactive account & routing management, atomic credential
310
+ storage, and resilient upstream-error handling. Thanks to the original authors.
311
+
312
+ ## License
313
+
314
+ MIT — see [LICENSE](LICENSE). Copyright © 2026 KarpelesLab and Max Krasnykh.
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "maxpool",
3
+ "version": "1.0.0",
4
+ "description": "Multi-account Claude Code proxy with adaptive, rate-aware load balancing across Claude accounts",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "maxpool": "src/index.js"
9
+ },
10
+ "files": [
11
+ "src/",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "start": "node src/index.js",
16
+ "test": "node --test",
17
+ "lint": "eslint src/ test/"
18
+ },
19
+ "keywords": [
20
+ "claude",
21
+ "claude-code",
22
+ "anthropic",
23
+ "proxy",
24
+ "load-balancer",
25
+ "rate-limit",
26
+ "multi-account"
27
+ ],
28
+ "author": "Max Krasnykh",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/2solarmax/maxpool.git"
33
+ },
34
+ "homepage": "https://github.com/2solarmax/maxpool#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/2solarmax/maxpool/issues"
37
+ },
38
+ "engines": {
39
+ "node": ">=18.0.0"
40
+ }
41
+ }
@@ -0,0 +1,30 @@
1
+ import { importCredentials } from './oauth.js';
2
+
3
+ export async function resolveAccounts(config) {
4
+ const accounts = [];
5
+ for (const acct of config.accounts) {
6
+ if (acct.type === 'oauth') {
7
+ if (acct.importFrom) {
8
+ try {
9
+ const creds = await importCredentials(acct.importFrom);
10
+ accounts.push({ ...acct, ...creds });
11
+ console.log(`Imported "${acct.name}" from ${acct.importFrom}`);
12
+ } catch (err) {
13
+ console.error(`Failed to import "${acct.name}": ${err.message}`);
14
+ // Fall back to a previously-stored token rather than dropping the
15
+ // account entirely when the import source is unreadable.
16
+ if (acct.accessToken) accounts.push(acct);
17
+ }
18
+ } else if (acct.accessToken) {
19
+ accounts.push(acct);
20
+ } else {
21
+ console.error(`No token for "${acct.name}", skipping`);
22
+ }
23
+ } else if (acct.type === 'apikey' && acct.apiKey) {
24
+ accounts.push(acct);
25
+ } else if (acct.type === 'provider' && (acct.authToken || acct.apiKey)) {
26
+ accounts.push(acct);
27
+ }
28
+ }
29
+ return accounts;
30
+ }