Repository navigation
feat(tui): draw locally-served accounts last - #376
Merged
MagicalTux merged 2 commits intoSep 15, 2026
Merged
Conversation
An account whose upstream is a process on this machine — a local translating proxy in front of another vendor, say — is infrastructure rather than a seat the fleet rotates between, so it reads as noise wedged among the accounts that do rotate. Config order cannot hold it at the end on its own: a newly added account is appended AFTER it and puts it back in the middle. Sort on whether the account's upstream resolves to loopback. A remote third-party backend (DeepSeek, GLM) keeps a public host and is not caught, so only a genuinely local backend moves. Display only: selIdx, currentIndex, session pins and route entries all stay manager indices, so nothing about selection or routing moves with the rows. Up/down walk the order as drawn while still storing an index. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rikbrown
added a commit
to rikbrown/teamclaude
that referenced
this pull request
Sep 15, 2026
The TUI ordering this commit used to carry went upstream as KarpelesLab#376 (c9d1b58) and now arrives with the rebase instead, review annotations included. The fork-only docs/openai.md section is the part upstream never took, so it is all that remains here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rikbrown
added a commit
to rikbrown/teamclaude
that referenced
this pull request
Sep 15, 2026
Rebases the fork onto upstream master 1779db6 (1.1.20 plus KarpelesLab#374-KarpelesLab#403). No new fork feature of its own: this ships upstream's 13 commits and the reshaping the rebase required. Upstream brings a version label in the dashboard header, provider names on the account rows of a mixed pool, Codex quota fixes across the 5h and weekly windows, spreading for requests that carry no session id, and outcome accounting on the same classification path every routing decision already read. Two fork commits dissolved into upstream, both merged there today: the dashboard key prompt (KarpelesLab#381), and the expiring Codex header fixture, which upstream fixed independently as KarpelesLab#402. The locally-served account ordering went up as KarpelesLab#376, so only its fork-only documentation remains here; and the header commit keeps the branding alone, now that upstream resolves and draws the version itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rikbrown
added a commit
to rikbrown/teamclaude
that referenced
this pull request
Sep 19, 2026
The TUI ordering this commit used to carry went upstream as KarpelesLab#376 (c9d1b58) and now arrives with the rebase instead, review annotations included. The fork-only docs/openai.md section is the part upstream never took, so it is all that remains here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rikbrown
added a commit
to rikbrown/teamclaude
that referenced
this pull request
Sep 19, 2026
Rebases the fork onto upstream master 1779db6 (1.1.20 plus KarpelesLab#374-KarpelesLab#403). No new fork feature of its own: this ships upstream's 13 commits and the reshaping the rebase required. Upstream brings a version label in the dashboard header, provider names on the account rows of a mixed pool, Codex quota fixes across the 5h and weekly windows, spreading for requests that carry no session id, and outcome accounting on the same classification path every routing decision already read. Two fork commits dissolved into upstream, both merged there today: the dashboard key prompt (KarpelesLab#381), and the expiring Codex header fixture, which upstream fixed independently as KarpelesLab#402. The locally-served account ordering went up as KarpelesLab#376, so only its fork-only documentation remains here; and the header commit keeps the branding alone, now that upstream resolves and draws the version itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MagicalTux
added a commit
that referenced
this pull request
Sep 20, 2026
* feat(tui): set the order the account list draws in New accounts land at the bottom of the list, and the only way to move one was to hand-edit the `accounts` array in the config — which is the one edit that array does not tolerate. A manager index addresses an account in route pins, session pins, `currentIndex`, `TC_ACCT` and the disable/switch CLI paths, so permuting the array silently repoints every one of them at a different account. So the rows get a sort key instead. `displayOrder` on an account is presentation and nothing else: each account keeps the slot it has held since startup, and _displayOrder sorts the rows by the field before drawing them. Settings → Reorder accounts opens the list with ←→ moving the selected ACCOUNT rather than the cursor, each move saved as it is made. Emphatically not `priority`, which is one field over and answers a different question: which account rotation spends next. A fleet usually carries one non-default value there — a deliberately deprioritised local backend — and deriving it from where a row sits on screen would re-rank routing as a side effect of tidying the display. Locally-served accounts are left out of it. #376 draws those last because a translating proxy in front of another vendor is infrastructure rather than a seat to rotate between, and that is a category rather than a preference, so it stays ahead of the arrangement: _displayOrder still partitions on isLocalUpstream first, those accounts hold no `displayOrder`, and the reorder cursor steps over the rows it cannot move. An account with no value has never been placed, and sorts after every account that has one. That is where a new account already appeared, so the answer to "where does the one I just logged in with go" does not change with the feature and there is nothing to migrate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(tui): hold the reorder to the rows, and to nothing else Most of what this feature must do is leave things alone, so most of these assert what did not move: the account array is not permuted, no manager index changes, the current account stays current, an index standing in for a route pin still names the account it named, and `priority` is never written — on the account, on its config entry, or in anything the save puts on disk. A locally-served account gets two of its own. It stays at the end of the list however the rows above it are arranged, it is never given a `displayOrder`, and the reorder cursor does not stop on its row, since ←→ there would do nothing. The round trip goes through mergeAccountsForSave rather than around it. That function merges an in-memory entry over its disk row, and a field it did not know about is exactly the kind that gets dropped there, so a reorder that vanished on restart would have looked like the TUI never saved it. The two ends of the same trip — makeAccount normalising what it reads and syncAccountsFromDisk carrying a hand edit onto a running account — are asserted against the real AccountManager rather than the harness stand-in. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs: the account list has an order of its own now The distinction worth writing down is the one the code spends its comments on: this sets the order the list is DRAWN in, and rotation order is still `priority` alone. Said in accounts.md where the settings screen is described, in usage.md where its keys are, and in the config table, where the two fields now sit one row apart and the reader is entitled to ask what the difference is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(tui): keep account reordering inside provider groups, debounce its save, carry it to attach After #406 the list is grouped by provider before anything else, so _displayOrder now sorts by provider, then local-upstream-last, then displayOrder, then manager index, and a move that would cross a provider group is a no-op that neither renumbers nor saves. The reorder save is debounced so a held arrow key is one locked config write, flushed on leaving the screen and in stop(). syncAccountsFromDisk mirrors a disk displayOrder onto the in-memory config entry, since the save stencil is {...diskAcct, ...live} and the stale key otherwise reverted a hand edit on the next save. getStatus emits displayOrder per account so `teamclaude attach` draws the same order as the server's own TUI. Tests cover the refused cross-group move as rendered, the single write per run of moves, the status field and the reload-save-reload round trip. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Mark Karpeles <magicaltux@gmail.com>
Merged
MagicalTux
added a commit
that referenced
this pull request
Sep 20, 2026
Thirty-six commits since 1.1.20. Several change what a client sees on a failure, so read the first section before upgrading a shared deployment. Behaviour changes #439 a 401 from upstream is no longer relayed to the client: the request fails over like a 403, and an API-key account or an OAuth account with no refresh token leaves rotation (it used to be picked again on every request). With nothing left the client gets the proxy's own error #439 a path under /teamclaude/ that no control route claims — a typo, or the wrong verb — answers 404 locally instead of being forwarded upstream under a fleet credential #429 the synthetic 429's retry-after is the real reset of the windows blocking the request's candidate accounts, not a flat 60s, and the message counts only those candidates; #408 names accounts that need a re-login instead of calling them "at quota" #438 session pins are per conversation (session id plus a digest of the first message), so a session's subagents spread across accounts. `sessions.items[].id` in status is the composite key, load is counted per conversation, and a persisted concurrency cap re-learns #378 a `thread: continue` bound for a per-account third-party upstream is refused with the 400 Anthropic gives, so the client resends the whole conversation; `messageThreads: true` opts a relay out #434 a 429 whose x-codex-* headers show a spent account-wide window holds the account like an Anthropic rejection; a spent model-scoped bucket only moves the request #437 a Codex response head is awaited for five minutes (Anthropic unchanged) #411 idle keep-alive connections are held 120s on both listeners #389 with session distribution on, requests carrying no session id rotate on a cursor of their own instead of all resting on the current account #405 `defaultClientMode` ("mitm" | "base-url") sets what `run` and `env` do without a flag; `--mitm` / `--no-mitm` decide per launch, and in base-URL mode `env` unsets a stale proxy export naming this proxy #439 route `--bucket` is validated; an array `switchThreshold` reads as the default with one line saying so Features #419 an MCP management endpoint at POST /teamclaude/mcp, off unless `proxy.mcp` is "read" or "full"; a named client key is read-only even in full mode, and with no proxy key it serves only this machine #428 per-account `switchThreshold`, a number or a per-bucket table #406 a Claude+Codex pool is drawn as two panes on a wide terminal, each with its own current marker; #392 names the provider in a mixed list; #418 lets the operator arrange the list (`displayOrder`); #376 draws loopback-served accounts last; #435 shows the percentage beside a bar's countdown; #394 shows the running version in the header #430 free Codex rate-limit reset credits in status, the TUI and the dashboard #385 `proxy.terminalOnly` tunnels chatgpt.com so ChatGPT Desktop stays out Fixes #404 a TUI paint can no longer block the proxy (stdout non-blocking, frames dropped while the terminal is behind); #410 a dead terminal no longer takes the proxy with it, and SIGHUP shuts down cleanly #433 token usage is booked from Codex Responses streams #386 #387 #388 the Codex five-hour window is read from the model-scoped family and the usage probe, and extra limits are named from their entries #431 a headerless 429 that follows the request is retried once #432 #439 startup and collaborator log lines reach the TUI's activity pane and log file instead of the covered terminal #415 #403 #439 reload mirrors `priority`, `disabled`, `stripRequestFields` onto the config entry, and a reload during a removal does not re-add it #439 the Host check uses the address actually bound; sx.org calls time out #381 the dashboard polls status before asking for a key #403 session outcome accounting classifies the decoded path; account names in daemon log lines are sanitised Tooling #371 #372 #373 `npm run typecheck` (tsc over the JS sources) in CI, with a strict-mode ratchet: per-file strict diagnostics may not grow past the pre-merge commit (2006 at introduction, 1735 now) #401 docker workflow actions bumped Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
rikbrown
added a commit
to rikbrown/teamclaude
that referenced
this pull request
Oct 7, 2026
The TUI ordering this commit used to carry went upstream as KarpelesLab#376 (c9d1b58) and now arrives with the rebase instead, review annotations included. The fork-only docs/openai.md section is the part upstream never took, so it is all that remains here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An account whose upstream is a process on this machine — a local translating proxy in front of another vendor — is infrastructure rather than a seat the fleet rotates between, so it reads as noise wedged among the accounts that do rotate. Config order cannot hold it at the end on its own, because a newly added account is appended after it and puts it back in the middle.
isLocalUpstream(provider.js, next toupstreamFor) keys on the account's upstream resolving to loopback. A remote third-party backend — DeepSeek, GLM — keeps a public host and is not caught, so only a genuinely local backend moves.Display only.
selIdx,currentIndex, session pins and route entries all stay manager indices, so nothing about selection or routing moves with the rows._keySelectwalks the order as drawn while still storing an index, and there is a test pinning exactly that: with a local account in the middle, pressing down from the first row lands on the account below it, not on the one that moved.Alternatives considered: pairing accounts against a declared local process does not generalise, since such a declaration carries a command rather than a port, and the port sits inside its argv where every program spells it differently. A loopback upstream says the same thing directly, and says it for a hand-started process too.
Tests: 6 new in
test/tui-account-order.test.js, rendered rather than unit-tested — the order rows are drawn in is the property, and onlyrender()decides it. Full suite green;npm run typecheckclean; strict-mode diagnostics unchanged against master for both touched files (provider.js 16, tui.js 225), so the ratchet stays green.🤖 Generated with Claude Code