@rikcodes/teamclaude 1.1.19-rik.3 → 1.1.20-rik.10

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.
Files changed (50) hide show
  1. package/README.md +62 -4
  2. package/package.json +7 -3
  3. package/src/account-id.js +1 -0
  4. package/src/account-manager.js +399 -100
  5. package/src/account-uuid-rewrite.js +29 -4
  6. package/src/admission-gate.js +12 -1
  7. package/src/alias.js +10 -1
  8. package/src/backend-quota.js +16 -4
  9. package/src/band-decision.js +8 -0
  10. package/src/cache-control-sanitize.js +17 -1
  11. package/src/classification-path.js +8 -0
  12. package/src/claude-env.js +26 -2
  13. package/src/codex-auth.js +3 -2
  14. package/src/codex-quota.js +35 -3
  15. package/src/codex-usage.js +138 -0
  16. package/src/config.js +1 -0
  17. package/src/crash-log.js +40 -5
  18. package/src/dashboard.js +44 -4
  19. package/src/egress-guard.js +24 -5
  20. package/src/event-loop-monitor.js +2 -1
  21. package/src/forward-target.js +2 -1
  22. package/src/identity.js +46 -0
  23. package/src/index.js +99 -13
  24. package/src/json-format-stream.js +6 -0
  25. package/src/mitm.js +31 -3
  26. package/src/oauth.js +5 -4
  27. package/src/prober.js +39 -6
  28. package/src/provider.js +42 -0
  29. package/src/quota-projection.js +3 -0
  30. package/src/request-log.js +11 -2
  31. package/src/resolve-accounts.js +2 -1
  32. package/src/rollover.js +59 -8
  33. package/src/route-warnings.js +60 -0
  34. package/src/safe-text.js +7 -1
  35. package/src/server.js +193 -24
  36. package/src/session-titles.js +26 -1
  37. package/src/session-tracker.js +63 -14
  38. package/src/sidecar.js +112 -4
  39. package/src/status-renderer.js +16 -1
  40. package/src/sx.js +1 -0
  41. package/src/sync-accounts.js +26 -7
  42. package/src/terminal-title.js +7 -0
  43. package/src/tool-pair-sanitize.js +23 -0
  44. package/src/tui-remote.js +28 -3
  45. package/src/tui.js +171 -19
  46. package/src/types.js +14 -0
  47. package/src/updater.js +78 -2
  48. package/src/upstream-fetch.js +4 -3
  49. package/src/upstream-proxy.js +2 -2
  50. package/src/warmer.js +18 -1
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # TeamClaude
2
2
 
3
3
  > **Fork notice (rikbrown).** This fork adds two features on top of
4
- > [KarpelesLab/teamclaude](https://github.com/KarpelesLab/teamclaude), currently based on upstream 1.1.19:
4
+ > [KarpelesLab/teamclaude](https://github.com/KarpelesLab/teamclaude), currently based on upstream 1.1.20:
5
5
  >
6
6
  > - **[OpenAI models via a Codex sidecar](docs/openai.md)** (`sidecars` + `customModels`, opt-in):
7
7
  > route `gpt-*` requests through a supervised local translating proxy to a ChatGPT subscription,
@@ -150,7 +150,7 @@ At launch, `teamclaude run` — and the `claude` alias, which passes through `ru
150
150
  | Row field | Where it ends up |
151
151
  | --- | --- |
152
152
  | `model`, `label`, `description` | A `/model` picker row under the **real** model id (`--settings`), so `/model gpt-5.6-sol` works picked or typed |
153
- | `model` | A dispatchable subagent named after the model (`--agents`), so "dispatch a `gpt-5.6-terra` subagent" works from a Claude parent |
153
+ | `model` | A dispatchable subagent named after the model (`--agents`), so "dispatch a `gpt-5.6-terra` subagent" works from a Claude parent. Set `"customModelAgents": false` to skip these if you define your own agents in `~/.claude/agents/` |
154
154
  | `contextTokens` | `CLAUDE_CODE_MAX_CONTEXT_TOKENS`, set to the largest value across rows, so Claude Code compacts at the real window instead of assuming 200k |
155
155
 
156
156
  For tools that spawn `claude` themselves, `teamclaude env` can set only environment variables. It carries the window and `ANTHROPIC_CUSTOM_MODEL_OPTION` for the **first** row. For GPT subagents under `env`, create `~/.claude/agents/<name>.md` with `model: gpt-5.6-terra` in its frontmatter.
@@ -163,9 +163,67 @@ Each request is routed by the model name in its body, so one session can freely
163
163
  2. Add a `customModels` row. Codex publishes the window for each model as `context_window` in `~/.codex/models_cache.json`; copy it to `contextTokens`.
164
164
  3. Start a new `teamclaude run` session. The rows are read at launch, so you do not need to restart the server. If you upgraded the sidecar binary, restart the server — or send `SIGTERM` to the sidecar process and let the supervisor restart it with the new binary.
165
165
 
166
- Claude Code prints one `[claude-code:unrecognized_model]` line to stderr for each custom model. This is expected; suppressing it would lose the correct context window. The quota bars for the sidecar account show `unknown` unless the sidecar forwards Codex's rate-limit headers — see [Quota](docs/openai.md#quota). Keep the sidecar on loopback, and use **one** ChatGPT subscription for each person. Pooling several subscriptions is the pattern that OpenAI's fraud systems target ([terms of service](docs/openai.md#terms-of-service)).
166
+ Claude Code prints one `[claude-code:unrecognized_model]` line to stderr for each custom model. This is expected; suppressing it would lose the correct context window. The quota bars for the sidecar account show `unknown` unless the sidecar forwards Codex's rate-limit headers — see [Quota](docs/openai.md#quota). Keep the sidecar on loopback.
167
167
 
168
- Full details: [docs/openai.md](docs/openai.md).
168
+ The sidecar appears under the account table as a `⚙` line rather than a row because it holds no subscription, is the only account its route can use, and never rotates. The line also shows its supervised process state (`up pid 98018`, or `down (code 1) 3 restarts`).
169
+
170
+ #### Several ChatGPT accounts
171
+
172
+ > **Read the [terms of service](docs/openai.md#terms-of-service) before setting this up.** OpenAI's Terms of Use prohibit rotating ChatGPT subscriptions past a spent window, and account suspension is a plausible consequence. This is a sharper trade-off than pooling Claude subscriptions because the first-party client lets you switch Claude subscriptions by hand.
173
+
174
+ One sidecar holds one ChatGPT login, so GPT requests do not rotate and its quota belongs to a login that TeamClaude does not own. Point the sidecar's **back leg** at TeamClaude so native Codex accounts serve it instead:
175
+
176
+ ```
177
+ Claude Code ──▶ TC /v1/messages (gpt-*) ──▶ sidecar account ──▶ sidecar translates
178
+ ──▶ TC /backend-api/codex/responses ──▶ ChatGPT account pool ──▶ chatgpt.com
179
+ ```
180
+
181
+ Each hop is classified by its path, and the subscription partition keeps the pools apart: on the way in only the sidecar account is eligible, on the way back only the ChatGPT accounts. One route lists both.
182
+
183
+ **1. Add the accounts** — run `teamclaude login --codex` once for each one. A Codex login takes its email as its name. Your Anthropic account probably uses the same name, so the Codex name gets a prefix to keep it unambiguous:
184
+
185
+ ```
186
+ $ teamclaude login --codex
187
+ Named "codex:you@example.com" — "you@example.com" is already an account on another provider.
188
+ ```
189
+
190
+ **2. Redirect the sidecar** and stub its own login, so TeamClaude supplies the credential instead:
191
+
192
+ ```json
193
+ { "name": "codex",
194
+ "command": ["claude-code-proxy", "serve", "--no-monitor", "--port", "18765"],
195
+ "env": {
196
+ "CCP_CODEX_BASE_URL": "http://127.0.0.1:3456/backend-api/codex/responses",
197
+ "CCP_CODEX_TRANSPORT": "http"
198
+ } }
199
+ ```
200
+
201
+ ```bash
202
+ cd ~/.config/claude-code-proxy/codex
203
+ cp auth.json auth.json.bak # the real login — keep it
204
+ echo '{ "access": "delegated-to-teamclaude", "refresh": "", "expires": 4102444800000 }' > auth.json
205
+ ```
206
+
207
+ The sidecar refuses to start with an empty store but never refreshes a far-future token, and TeamClaude replaces both the bearer and the account header on the way out. Leave `accountId` unset so none of the sidecar's own identity can leak.
208
+
209
+ **3. Put them all on the `gpt-*` route**, sidecar included, and give each account a `headersTimeoutMs` — the 120s fleet default is shorter than a long reasoning turn:
210
+
211
+ ```json
212
+ { "name": "codex", "match": ["gpt-*"],
213
+ "accounts": ["codex", "codex:you@example.com", "codex:you@work.example"] }
214
+ ```
215
+
216
+ **4. Restart the server.** A `sidecars[].env` change is read once at startup, so a reload is not enough.
217
+
218
+ Three details matter:
219
+
220
+ - **Leave the sidecar account on the route.** It can look removable because it is not a subscription or an account row, but it is the routing target for the way *in*. Without it, every `gpt-*` request fails to find an account while `teamclaude status` shows two healthy ChatGPT accounts on the route.
221
+ - **`CCP_CODEX_TRANSPORT=http` is required.** A WebSocket upgrade is relayed with the caller's own headers and draws no account, so the WebSocket transport cannot be pooled.
222
+ - **Do not reuse a name across providers.** Routes address accounts by name, so a shared name admits both — including the Claude account that cannot serve `gpt-*`, which outranks the sidecar on priority and wins. TeamClaude warns at startup when it sees one.
223
+
224
+ Two things differ from the single-account setup: each turn appears **twice** in the activity list, once per hop, and tokens are booked against the sidecar account, so a ChatGPT account reads `N req · 0 tok`. Its quota bars are unaffected because they come from the `x-codex-*` headers on the second hop, where the subscription is.
225
+
226
+ Full details, including what happens to quota on each hop: [Several ChatGPT accounts behind one sidecar](docs/openai.md#several-chatgpt-accounts-behind-one-sidecar).
169
227
 
170
228
  ### Burn-rate projection
171
229
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rikcodes/teamclaude",
3
- "version": "1.1.19-rik.3",
3
+ "version": "1.1.20-rik.10",
4
4
  "description": "Multi-account proxy for Claude Code and Codex: pools Claude Max, ChatGPT/Codex, API-key and third-party backend accounts, and rotates on quota",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -13,7 +13,9 @@
13
13
  "scripts": {
14
14
  "start": "node src/index.js",
15
15
  "test": "node --test --test-timeout=120000",
16
- "lint": "eslint ."
16
+ "lint": "eslint .",
17
+ "typecheck": "tsc -p tsconfig.json",
18
+ "typecheck:strict": "node scripts/typecheck-strict.mjs"
17
19
  },
18
20
  "keywords": [
19
21
  "claude",
@@ -43,7 +45,9 @@
43
45
  "node": ">=20.0.0"
44
46
  },
45
47
  "devDependencies": {
46
- "eslint": "^9.0.0"
48
+ "@types/node": "20.19.43",
49
+ "eslint": "^9.0.0",
50
+ "typescript": "5.9.2"
47
51
  },
48
52
  "publishConfig": {
49
53
  "access": "public"
package/src/account-id.js CHANGED
@@ -40,6 +40,7 @@ export function mintAccountId() {
40
40
  * copies its id along with it, and two entries answering to one id collapse
41
41
  * onto whichever comes first — the later one would be handed the earlier one's
42
42
  * credential, which is the crossing this field exists to prevent.
43
+ * @param {Array<Record<string, any>>} accounts
43
44
  */
44
45
  export function ensureAccountIds(accounts) {
45
46
  const seen = new Set();