teamclaude-cloud 1.4.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 ADDED
@@ -0,0 +1,320 @@
1
+ # TeamClaude Cloud
2
+
3
+ Multi-account Claude proxy with automatic quota-based rotation for [Claude Code](https://claude.ai/claude-code) — plus a **cloud SaaS layer** that syncs authenticated tokens to the cloud and controls, per auth key, which accounts each machine may use.
4
+
5
+ ## Cloud SaaS (teamclaude-cloud)
6
+
7
+ The base proxy runs locally. The cloud layer lets you authenticate every account **once on one PC**, sync those tokens to the cloud, and let other machines pull only the accounts you allow them — via an **auth key**, without re-authenticating each account per PC.
8
+
9
+ ```
10
+ [Admin PC] teamclaude login ×N → teamclaude cloud push
11
+
12
+
13
+ [Cloud] teamclaude.cloud: encrypted tokens (AES-256-GCM) + auth keys (hashed) + key→account mapping
14
+ Cloud API: sync push/pull, auth-key issue/revoke/map
15
+
16
+ │ auth key
17
+ [Consumer PC] teamclaude cloud pull --key tck_… → teamclaude server
18
+ ```
19
+
20
+ Security model: tokens are encrypted at rest and token storage is server-only (row-level security denies all client roles); auth keys are stored as a SHA-256 hash (for pull-time lookup) **and** an AES-256-GCM ciphertext under the same key as the tokens, so the owner can re-copy the full `teamclaude cloud pull --key …` command from the dashboard at any time (owner-only `GET /auth-keys/:id/reveal`, revoked keys return 409). Keys issued before this change are hash-only and non-revealable — re-issue to get a copyable key. Access control (which key may use which account) is enforced server-side in the cloud API.
21
+
22
+ ### Web dashboard
23
+
24
+ A browser dashboard (signup/login + auth-key issuance and control) is served on Vercel:
25
+
26
+ **https://teamclaude-cloud.vercel.app**
27
+
28
+ Sign up there, then use the *same* account with `teamclaude cloud login` on the admin PC. The dashboard shows your synced accounts with their **usage** (5h session, weekly, and the model-scoped "Fable" weekly window — as measured by the proxy at push time, with the push timestamp shown), toggles accounts globally, manages **groups** (create/rename/delete, per-group account enable + priority ordering), and issues/moves/revokes auth keys per group. The dashboard is a static SPA (`public/`) plus a health endpoint (`api/health.js`); `GET /api/health` returns service status JSON.
29
+
30
+ ### Cloud commands
31
+
32
+ ```bash
33
+ # Admin PC: authenticate to the cloud, then push local account tokens
34
+ # (the proxy's quota snapshot rides along so the dashboard shows 5h/weekly/Fable usage)
35
+ teamclaude cloud login --email <you@example.com>
36
+ teamclaude cloud push
37
+
38
+ # Groups: auth keys belong to a group (default "main"); accounts are allowed per GROUP.
39
+ teamclaude cloud group list
40
+ teamclaude cloud group create team-a
41
+ teamclaude cloud group allow team-a --accounts <uuid1>,<uuid2> # order = priority (#0 first)
42
+ teamclaude cloud group rename team-a --to team-alpha
43
+ teamclaude cloud group delete team-a # its keys move to "main"
44
+
45
+ # Auth keys (raw key printed once)
46
+ teamclaude cloud key create --label laptop --group team-a
47
+ teamclaude cloud key list
48
+ teamclaude cloud key move <key-id> --group main
49
+ teamclaude cloud key revoke <key-id>
50
+
51
+ # Global account switch (excluded from EVERY group's pull when disabled)
52
+ teamclaude cloud account disable <accountUuid|localName>
53
+ teamclaude cloud account enable <accountUuid|localName>
54
+
55
+ # Consumer PC: pull the key's group's accounts (tokens + priority) into the local config
56
+ # (key-only — no `cloud login`; the endpoint is built in). The key is saved so the
57
+ # running server keeps syncing on its own.
58
+ teamclaude cloud pull --key tck_…
59
+ teamclaude server
60
+ ```
61
+
62
+ **Group exclusivity:** an account can be *enabled* in at most one group at a time (DB-enforced with a partial unique index; the API answers `409 account_in_use` naming the conflicting group) — so two groups can never pull, and therefore never concurrently use, the same account.
63
+
64
+ **Priority & selection:** the group's account order is delivered as each pulled account's `priority`, which the local proxy's existing selection consumes unchanged — explicit priority first, then the same use-or-lose algorithm (weekly reset soonest, session reset, lowest usage) as always.
65
+
66
+ **Live sync (running server ⇄ cloud).** `cloud pull` stores the endpoint + auth key, so a running `teamclaude server` re-pulls on an interval (`config.cloudSyncIntervalMs`, default 30 s; `0` disables) and applies changes **without a restart**: refreshed tokens are updated in place (freshest-token-wins), accounts added to / removed from the group appear / disappear live, and **revoking the key** drops all of its accounts and the proxy refuses new requests (it self-heals the moment you re-pull with a valid key). The rotation/quota/failover *handling* still runs entirely in the local proxy (unchanged algorithm) — the cloud is only the control plane feeding it fresh config. A **cloud outage is non-blocking**: a transient pull error keeps the last-known accounts and retries next interval; only an explicit `403` (revoked/invalid key) removes accounts.
67
+
68
+ **What revocation does and does not do.** A pull hands the consumer both the **access and refresh** tokens for each allowed account. Server-side controls — **revoking the key** (it can no longer pull) and **disabling/removing the account from the group** (it stops being handed out) — only cut off *future pulls*. They do **not** invalidate a token a consumer already pulled: that consumer holds the refresh token and can keep minting new access tokens directly against Anthropic until Anthropic itself revokes it (this proxy cannot remotely invalidate an OAuth grant). So treat a pulled key/account as "access granted until the underlying OAuth credential is rotated at the source." The client-side prune (dropping local accounts a key no longer allows, scoped per key) — applied automatically by the running server's live sync (above), or via TUI **R** / restart — is **hygiene/convenience**, not a hard revocation boundary. One consumer is expected to use **one key**.
69
+
70
+ Sync is **freshest-token-wins**: a push/pull never overwrites a newer token with an older one (compared by normalized `expiresAt`), so a token refreshed on any machine wins. Pulled `priority` applies even when the token itself is kept-local (control-plane vs token freshness). The cloud layer is additive — if the cloud is unreachable the local proxy still runs on whatever accounts are in the local config.
71
+
72
+ The cloud API lives at `auth.teamclaude.cloud` — the CLI ships no backing-infrastructure hosts or keys; consumers authenticate with their auth key, admins with `cloud login`.
73
+
74
+ ---
75
+
76
+ ## Base proxy
77
+
78
+ Sits transparently between Claude Code and the Anthropic API, managing multiple Claude Max (or API key) accounts and automatically switching when one approaches its session or weekly quota limit.
79
+
80
+ ![TeamClaude TUI](screenshots/teamclaude.png)
81
+
82
+ ## Features
83
+
84
+ - **Use-or-lose account priority** — measures each account once at startup, then prioritizes the account whose weekly (7d) quota resets soonest (then soonest session reset, then lowest usage), so quota about to renew unused is drained first; re-evaluates every 5 minutes and switches immediately when the active account reaches the quota threshold (default 98%). Pin explicit ranks in the TUI (`o`) or via `teamclaude priority` for the accounts you want first — everything unranked stays on this automatic (`auto`) ordering
85
+ - **Instant failover on 429** — an exhausted account (token quota hit) is throttled for its `retry-after` (clamped to 1s–5m) and skipped; a rate/concurrency 429 (quota left but hit too fast) fails the request over to another account so concurrent overflow spreads instead of erroring. Either way nothing blocks, and a request-global 429 only passes through after every account has been tried — never throttling the fleet
86
+ - **Interactive TUI** — real-time dashboard with color-coded quota bars showing usage %, reset countdowns, an activity log, and keyboard controls (switch, enable/disable, reorder accounts)
87
+ - **Manual account controls** — enable/disable accounts and pin an explicit account order from the TUI or CLI (`teamclaude disable|enable|priority`); a disabled account is excluded from rotation while its in-flight requests drain, and everything unranked stays on automatic use-or-lose ordering
88
+ - **Quota survives restarts** — per-account quota state *and* the warm-up probe template are snapshotted to `<config>.quota.json` (every minute and on exit) and restored at startup, so a restart doesn't blank the dashboard, blind the account ordering, or leave forced re-measure (**R**) dead until traffic flows again
89
+ - **Active warm-up** — after a (re)start the proxy probes still-unmeasured accounts with a minimal request (reusing the shape of the first real request, restored across restarts), so the whole fleet's quota populates within seconds instead of waiting for traffic to reach each account
90
+ - **Server lifecycle** — `teamclaude stop` / `teamclaude restart` cleanly stop or replace the running server from any terminal
91
+ - **OAuth token management** — automatically refreshes tokens nearing expiry and persists them to config; client token refreshes pass through untouched
92
+ - **Hot-reload accounts** — add accounts via `import` or `login` while the server is running, press **R** to pick them up; **R** also force-re-measures the whole fleet's quota, so the dashboard reflects usage spent outside this proxy (other devices/sessions) — and works right after a restart, since the probe template is restored from the snapshot
93
+ - **Account deduplication** — detects duplicate accounts by UUID and keeps the most recent
94
+ - **Request logging** — optional full request/response logging for debugging
95
+ - **Zero dependencies** — uses only Node.js built-in modules
96
+
97
+ ## Quick Start
98
+
99
+ Requires Node.js 18+.
100
+
101
+ ```bash
102
+ # Install (published to npm — auto-updates via `npm update -g` or `teamclaude update`)
103
+ npm install -g teamclaude-cloud
104
+
105
+ # Add your first account (opens browser for OAuth)
106
+ teamclaude login
107
+
108
+ # Add a second account
109
+ teamclaude login
110
+
111
+ # Start the proxy
112
+ teamclaude server
113
+
114
+ # In another terminal, run Claude Code through the proxy
115
+ teamclaude run
116
+ ```
117
+
118
+ You can also import existing Claude Code credentials instead of logging in:
119
+
120
+ ```bash
121
+ claude /login # Log into an account in Claude Code
122
+ teamclaude import # Import its credentials
123
+ ```
124
+
125
+ ## Adding Accounts
126
+
127
+ ### OAuth Login (recommended)
128
+
129
+ The easiest way to add accounts — opens your browser for authentication:
130
+
131
+ ```bash
132
+ teamclaude login
133
+ ```
134
+
135
+ 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.
136
+
137
+ You can add accounts while the server is running — press **R** in the TUI to reload.
138
+
139
+ ### Import from Claude Code
140
+
141
+ If you already have Claude Code set up, you can import its credentials directly:
142
+
143
+ ```bash
144
+ claude /login # Log into an account in Claude Code
145
+ teamclaude import # Import its credentials
146
+ ```
147
+
148
+ Re-importing the same account updates its credentials. You can also import from a custom path:
149
+
150
+ ```bash
151
+ teamclaude import --from /path/to/credentials.json
152
+ ```
153
+
154
+ ### API Key
155
+
156
+ For Anthropic API key accounts (billed via Console):
157
+
158
+ ```bash
159
+ teamclaude login --api
160
+ ```
161
+
162
+ ## Usage
163
+
164
+ ### Start the proxy server
165
+
166
+ ```bash
167
+ teamclaude server
168
+ ```
169
+
170
+ When running from a TTY, shows an interactive TUI with:
171
+ - Account table with **numbered rows** and session/weekly quota progress bars (usage % overlaid, plus a reset countdown when space allows); wide terminals add a third `Fbl` bar with the model-scoped weekly limit (the separate "Fable" weekly limit from Claude's usage UI). Ranked accounts are listed first, then the `auto` accounts in their actual drain order (weekly reset soonest first)
172
+ - Real-time activity log with request tracking
173
+ - Keyboard shortcuts (see below)
174
+
175
+ Falls back to plain log output when not a TTY (e.g. running as a service).
176
+
177
+ If the configured port is already in use — for example another TeamClaude proxy is already running — the server prints a clear message and exits instead of crashing with an unhandled error. Inspect the existing one with `teamclaude status`, or find the listener with `lsof -nP -iTCP:<port> -sTCP:LISTEN`.
178
+
179
+ #### TUI Keyboard Shortcuts
180
+
181
+ | Key | Action |
182
+ |-----|--------|
183
+ | `↑`/`↓` | Move the selection cursor over the accounts |
184
+ | `s` | Switch active account (to the selected one) |
185
+ | `e` | Enable / disable the selected account |
186
+ | `o` | Order the selected account: `↑`/`↓` move its rank, `a` resets the WHOLE order to `auto` (weekly-reset ordering), `c` clears just this account's rank |
187
+ | `a` | Add account (import or API key) |
188
+ | `d` | Delete an account (with confirmation) |
189
+ | `R` | Reload accounts from config **and re-measure every account's quota** — revives lapsed OAuth tokens first, includes the model-scoped Fable window, and reports an honest `M/N` when some accounts fail or are skipped |
190
+ | `q` | Quit |
191
+
192
+ In selection mode, use `j`/`k` or arrow keys to navigate, `Enter` to confirm, `Esc` to cancel.
193
+
194
+ ### Stop / restart the server
195
+
196
+ ```bash
197
+ teamclaude stop # SIGTERM the running server (escalates to SIGKILL if needed)
198
+ teamclaude restart # stop the running server (if any) and start a fresh one
199
+ ```
200
+
201
+ The running server is discovered via its state file (`<config>.server.json`) with a port-probe fallback, so `stop`/`restart` work from any terminal — even after a config port change. Quota state is restored on restart (see below), so a restart doesn't lose the dashboard.
202
+
203
+ > **Note:** if a Claude Code session is itself routed through the proxy (`teamclaude run`), running `teamclaude stop` *inside that session* severs its own API connection (`Unable to connect to API (ConnectionRefused)`). Stop or restart the proxy from a separate terminal instead — with `restart`, an in-flight session recovers on its own retries.
204
+
205
+ ### Account order & manual controls
206
+
207
+ By default every account is on **`auto`** ordering (use-or-lose: weekly reset soonest is drained first). You can layer manual controls on top:
208
+
209
+ ```bash
210
+ teamclaude disable <name> # exclude from rotation (in-flight requests drain)
211
+ teamclaude enable <name> # re-enable
212
+ teamclaude priority <name> <n|auto> # pin explicit order (lower = preferred); "auto" clears it
213
+ ```
214
+
215
+ In the TUI, `↑`/`↓` select an account, `e` toggles enable/disable, and `o` grabs the selected account into order mode: `↑`/`↓` move its rank, `a` resets the WHOLE order back to `auto`, `c` clears just that account's rank, `Enter`/`Esc` done. Ranked accounts render as `#1 #2 …` and are preferred first; everything unranked stays on the automatic ordering — so you can pin a few accounts and let the rest rotate.
216
+
217
+ CLI changes made while the server is running are picked up with **R** (reload) in the TUI or `teamclaude restart`.
218
+
219
+ ### Run Claude Code through the proxy
220
+
221
+ ```bash
222
+ teamclaude run
223
+ ```
224
+
225
+ Or manually set the environment:
226
+
227
+ ```bash
228
+ eval $(teamclaude env)
229
+ claude
230
+ ```
231
+
232
+ ### Other commands
233
+
234
+ ```bash
235
+ teamclaude accounts # List accounts with subscription tier and token status
236
+ teamclaude accounts -v # Also show token expiry times
237
+ teamclaude status # Show live proxy status (requires running server)
238
+ teamclaude stop # Stop the running proxy server
239
+ teamclaude restart # Stop the running server and start a fresh one
240
+ teamclaude remove <name> # Remove an account
241
+ teamclaude disable <name> # Disable an account (excluded from rotation)
242
+ teamclaude enable <name> # Re-enable a disabled account
243
+ teamclaude priority <name> <n|auto> # Pin selection order (lower = preferred; "auto" clears)
244
+ teamclaude api <path> # Call an API endpoint with account credentials
245
+ teamclaude help # Show all commands
246
+ ```
247
+
248
+ ### Request logging
249
+
250
+ Log full request/response details to a directory (one file per request):
251
+
252
+ ```bash
253
+ teamclaude server --log-to /tmp/requests
254
+ ```
255
+
256
+ ## Configuration
257
+
258
+ Config is stored at `~/.config/teamclaude.json` (or `$XDG_CONFIG_HOME/teamclaude.json`). A random proxy API key is generated on first use.
259
+
260
+ Override the config path with `TEAMCLAUDE_CONFIG`:
261
+
262
+ ```bash
263
+ TEAMCLAUDE_CONFIG=./my-config.json teamclaude server
264
+ ```
265
+
266
+ ### Config format
267
+
268
+ ```json
269
+ {
270
+ "proxy": {
271
+ "port": 3456,
272
+ "apiKey": "tc-auto-generated-key"
273
+ },
274
+ "upstream": "https://api.anthropic.com",
275
+ "switchThreshold": 0.98,
276
+ "accounts": [
277
+ {
278
+ "name": "user@example.com",
279
+ "type": "oauth",
280
+ "accountUuid": "...",
281
+ "accessToken": "sk-ant-oat01-...",
282
+ "refreshToken": "sk-ant-ort01-...",
283
+ "expiresAt": 1774384968427,
284
+ "enabled": true,
285
+ "priority": 0
286
+ }
287
+ ]
288
+ }
289
+ ```
290
+
291
+ | Field | Description |
292
+ |-------|-------------|
293
+ | `proxy.port` | Local port the proxy listens on |
294
+ | `proxy.apiKey` | API key clients use to authenticate with the proxy |
295
+ | `upstream` | Upstream API base URL |
296
+ | `switchThreshold` | Quota utilization (0–1) at which an account is considered full and skipped |
297
+ | `reevalIntervalMs` | How often (ms) to re-rank accounts by priority while the active one is healthy (optional, default `300000` = 5 min). Set to `0` to disable the timer entirely — the active account then only changes when it becomes unavailable or via per-request 429 failover |
298
+ | `activeWarmup` | Probe unmeasured accounts after a restart to populate quota (optional, default `true`) |
299
+ | `warmupIntervalMs` | How often (ms) the active warm-up re-probes accounts whose quota window reset (optional, default `300000` = 5 min; `0` = startup-only) |
300
+ | `accounts[].enabled` | Set `false` to exclude the account from rotation (optional, default `true`) |
301
+ | `accounts[].priority` | Explicit selection rank (lower = preferred first; optional — unset means automatic use-or-lose ordering) |
302
+
303
+ ## How It Works
304
+
305
+ 1. Claude Code connects to the local proxy instead of `api.anthropic.com`
306
+ 2. The proxy selects the active account and forwards requests with that account's credentials
307
+ 3. OAuth tokens expiring within 5 minutes are automatically refreshed and persisted to config
308
+ 4. Rate limit headers from the API (`anthropic-ratelimit-unified-*`) track session (5h) and weekly (7d) quota utilization. Model-scoped weekly windows (`7d_oi` — the separate "Fable" weekly limit) are tracked and displayed too, but never affect routing: an account over its Fable weekly limit still serves every other model
309
+ 5. **Cold-start warm-up**: quota is only known after a request flows through an account, so at startup the proxy first routes requests to any unmeasured account until every account has been measured once. An **active warm-up** additionally probes unmeasured accounts directly — a minimal 1-token request reusing the shape of the first real request — so the whole fleet is measured within seconds of the first post-restart request instead of waiting for traffic to reach each account (`activeWarmup: false` disables it). Then account selection becomes **use-or-lose**: among accounts still under the threshold, it prefers the one whose weekly (7d) quota resets soonest (tie-breaks: soonest session reset, then lowest usage), so quota about to renew unused is drained first. Explicitly ranked accounts (`priority` / TUI `o`) are preferred before all of that; disabled accounts are excluded entirely. The active account stays sticky to keep its prompt cache warm; priority is re-evaluated every `reevalIntervalMs` (default 5 min; set `0` to disable timer-based switching), and on reaching the threshold it switches immediately to the next-highest-priority account
310
+ 6. On a 429 the proxy classifies it (never sleeping holding the client connection):
311
+ - **Account-quota exhaustion** (upstream reports the account is over its limit) → marks that account rate-limited for its `retry-after` (clamped to 1s–5m) and immediately re-dispatches to the next available account. If every account is throttled it returns 429 with a computed `retry-after`. (This also keeps cold-start warm-up fast: an exhausted account is skipped in one round-trip.)
312
+ - **Rate/concurrency or transient 429** (account has token quota left but was hit too fast, or a transient limit) → the request fails over to another available account (per-request, without throttling the account), so concurrent overflow spreads to an idle account instead of erroring. If *every* account has been tried for the request (→ effectively global), the 429 is passed through — still without throttling any account, so the fleet isn't poisoned.
313
+ 7. Transient network errors (connection reset, timeout) drop the connection so the client can retry
314
+ 8. If all accounts are exhausted, returns 429 with a `retry-after` computed from the soonest account reset — the real unified 5h/7d (or standard) reset of whichever over-threshold window is actually blocking each account, so clients back off until quota genuinely frees instead of retrying against a fixed 60s fallback. A merely concurrency-capped (but quota-healthy) fleet still gets the short fallback, since a freed slot is seconds away
315
+ 9. **Quota survives restarts**: the server snapshots per-account quota/throttle state — plus the committed warm-up probe template — to `<config>.quota.json` (every minute and on exit) and restores both at startup, so a restart doesn't blank the dashboard, blind the use-or-lose ordering, or leave warm-up probes and forced re-measure (TUI **R**) without a known-accepted request shape until traffic flows again. A restored template is provisional: the first freshly accepted request shape replaces it (the snapshot's model may have been retired since). Expired windows are swept lazily and re-measured from live traffic
316
+ 10. Client token refresh requests (`/v1/oauth/token`) are relayed to upstream untouched — the proxy and client manage their own token lifecycles independently
317
+
318
+ ## License
319
+
320
+ MIT
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "teamclaude-cloud",
3
+ "version": "1.4.1",
4
+ "description": "Multi-account Claude proxy with quota-based rotation + cloud token sync and auth-key control",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "teamclaude": "src/index.js"
9
+ },
10
+ "files": [
11
+ "src/"
12
+ ],
13
+ "scripts": {
14
+ "start": "node src/index.js",
15
+ "test": "node --test",
16
+ "lint": "eslint ."
17
+ },
18
+ "keywords": [
19
+ "claude",
20
+ "anthropic",
21
+ "proxy",
22
+ "load-balancer",
23
+ "multi-account",
24
+ "cloud-sync"
25
+ ],
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/jung-wan-kim/teamclaude-cloud.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/jung-wan-kim/teamclaude-cloud/issues"
32
+ },
33
+ "homepage": "https://teamclaude-cloud.vercel.app",
34
+ "license": "MIT",
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ }
38
+ }