feat(pool): one board of pools with split/merge by drag; plan names from tier and seat

- The Users page account pool is one board: a column per pool holding its
  Claude Code, Codex and Cursor accounts (plan, quota, live panes) and its
  members. Accounts sit in one pool; drag moves them. Drop on New pool to
  split one off; drag a pool's only account into another pool to merge
  (members follow, the emptied pool is deleted on Save). One password Save.
- Claude plan label (planLabel in cc-profiles.js) reads rateLimitTier and
  seatTier: Max 5x/20x, Team Standard/Premium — subscriptionType alone said
  'pro' after an upgrade and 'team' for both seats. Shown on profiles, the
  status bar quota badge and pool cards; Codex pool cards show the ChatGPT plan.
This commit is contained in:
2026-10-28 11:47:19 +07:00
parent 9d07f64154
commit 26aabd6660
18 changed files with 1638 additions and 642 deletions
+14
View File
@@ -245,6 +245,7 @@ function poolStatus() {
return {
name: a.name,
kind: a.kind,
plan: a.kind === "chatgpt" ? planOf(a.auth) : null,
addedAt: a.addedAt,
livePanes: livePanes(a.name),
...usageOf(a.name),
@@ -285,6 +286,19 @@ function identity(auth) {
}
}
/** The ChatGPT plan its id_token names, title-cased ("Plus", "Pro", "Team"), or null. */
function planOf(auth) {
try {
const c = JSON.parse(
Buffer.from(String(auth.tokens.id_token).split(".")[1], "base64url").toString("utf8")
);
const p = c[AUTH_CLAIM]?.chatgpt_plan_type;
return typeof p === "string" && p ? p.charAt(0).toUpperCase() + p.slice(1) : null;
} catch {
return null;
}
}
/**
* Whether `disk` (a member's auth.json) may replace pool account `a`: AgentHub
* placed `a` in that home itself (`placedIn`), its `last_refresh` is no later