From ops
Walks through each account in the rotation config, runs OAuth magic-link flow for accounts missing valid tokens, and writes verified tokens to the keychain.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ops:ops-rotate-setup [--all|--account <email>|--add|--crs][--all|--account <email>|--add|--crs]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Initialize OAuth tokens for the multi-account Claude rotator system (the
Initialize OAuth tokens for the multi-account Claude rotator system (the
account-rotation daemon). For each configured account that does not already
have a valid keychain token, delegate to rotate.mjs --setup --only=<email> --auto --skip-valid, which drives the browser-driver cascade (CDP-attach to a
real Chrome → spawn Chrome with a real profile → bundled Chromium), polls Gmail
for the magic link via gog, verifies the token, and writes it to the OS
keychain under the schema the daemon/rotator consume: service
Claude-Rotation-<key> (key = account label or email), keychain account
$USER (override with $CLAUDE_ROTATOR_KEYCHAIN_ACCOUNT), value
{ "claudeAiOauth": { "accessToken": ... } }.
Why delegate: a freshly launched Playwright Chromium is blocked by claude.ai's Cloudflare Turnstile (the magic link is never sent), and a hand-rolled web-cookie capture writes a credential shape no consumer reads.
rotate.mjssolves both correctly, sosetup-account.mjsis a thin wrapper around it.
Use this skill when:
/ops:setup and skipped the account-rotation stepAskUserQuestion. Paginate at 4 with
[More...] bridges when listing accounts.run_in_background: true and tail the log.account_rotation_enabled after init. The user flips that
switch from /plugins settings.USER_CFG="$HOME/.claude/plugins/data/ops-ops-marketplace/account-rotation-config.json"
REPO_CFG="${CLAUDE_PLUGIN_ROOT}/scripts/account-rotation/config.json"
CFG="$([[ -f "$USER_CFG" ]] && echo "$USER_CFG" || echo "$REPO_CFG")"
jq '.accounts // []' "$CFG"
If accounts is empty AND no --add argument was passed, jump to Step 2 —
Bootstrap. Otherwise jump to Step 3 — Token check.
AskUserQuestion:
No Claude accounts are configured for the rotator yet. Add some now?
[Add now] — collect email/display/plan, then run OAuth
[Use existing keychain] — skip — assume keychain already has tokens
[Skip] — exit, do nothing
[Help] — explain how the rotator works and exit
[Help]: print one-paragraph explainer (rotator purpose, where keychain entries live, how /plugins toggles account_rotation_enabled) and exit.[Use existing keychain]: print "Looking for Claude-Rotation-* entries..." and run CRED="${CLAUDE_PLUGIN_ROOT}/lib/credential-store.sh"; ACCT="${CLAUDE_ROTATOR_KEYCHAIN_ACCOUNT:-$USER}"; bash "$CRED" backends 2>/dev/null && jq -r '.accounts[] | (.label // .email)' "$CFG" 2>/dev/null | while read -r key; do bash "$CRED" get "Claude-Rotation-$key" "$ACCT" >/dev/null 2>&1 && echo "✓ $key" || echo "✗ $key"; done || echo "(no backends available — re-run with [Add now])". Exit.[Skip]: exit.[Add now]: enter the add loop below.For each new account:
*@*.* shape, reject empties.AskUserQuestion for plan:
Plan tier for this account?
[Max] — Claude Max ($100/$200 tier)
[Pro] — Claude Pro
[Team] — Team plan seat
[Other] — type a label
Account added. Next?
[Add another]
[Done — start OAuth]
[Cancel]
Once [Done], write the new accounts into $USER_CFG (create dirs as needed):
mkdir -p "$(dirname "$USER_CFG")"
jq --argjson new "$NEW_ACCOUNTS_JSON" \
'.accounts = ((.accounts // []) + $new)' \
"$CFG" > "$USER_CFG.tmp" && mv "$USER_CFG.tmp" "$USER_CFG"
For each account in the merged config, check the keychain under the consumed
schema (service Claude-Rotation-<key>, account $USER):
CRED="${CLAUDE_PLUGIN_ROOT}/lib/credential-store.sh"
ACCT="${CLAUDE_ROTATOR_KEYCHAIN_ACCOUNT:-$USER}"
jq -r '.accounts[] | (.label // .email)' "$USER_CFG" | while read -r key; do
if bash "$CRED" get "Claude-Rotation-$key" "$ACCT" >/dev/null 2>&1; then
echo "✓ $key"
else
echo "✗ $key (needs OAuth)"
fi
done
If every account is ✓, print a success line and jump to Step 5 — Summary.
(rotate.mjs --setup ... --skip-valid also re-checks token validity itself, so
a stale-but-present entry is re-captured during Step 4.)
For each ✗ account, run the setup script in the background and tail its log.
SCRIPT="${CLAUDE_PLUGIN_ROOT}/scripts/account-rotation/setup-account.mjs"
LOG="$HOME/.claude/logs/account-rotation/setup-${ID}-$(date +%s).log"
mkdir -p "$(dirname "$LOG")"
node "$SCRIPT" \
--email "$EMAIL" \
--display "$ACCOUNT_DISPLAY" \
--plan "$PLAN" \
--account-id "$ID" \
--gmail-poll \
>"$LOG" 2>&1 &
echo "PID=$! LOG=$LOG"
run_in_background: true.Monitor (or Read of the log file) to surface progress lines.setup-account.mjs upserts the account into $USER_CFG, then delegates to
rotate.mjs --setup --only=<email> --auto --skip-valid, which:
--skip-valid).:9222
(passes Cloudflare Turnstile) → else spawn Chrome with a real profile →
else bundled Chromium.gog for the magic link, then
completes login (handling the org chooser).api.anthropic.com/api/oauth/usage.Claude-Rotation-<key> (account $USER),
value { "claudeAiOauth": { "accessToken": ... } }.{"ok":true,"accountId":...,"email":...}
on success, or {"ok":false,...,"error":"oauth_failed"} on failure.rotate.mjs logs a 2FA / verification prompt or times out, surface the log
to the user and let them complete the login in the cascade's visible Chrome.
Do NOT attempt to auto-solve 2FA.After each account completes (success or failure):
Result for <email>:
[✓ Success — continue with next] ← only if more remain
[✗ Failed — view log and retry]
[Stop here]
If success and no more accounts remain, jump to Step 5.
If the user runs a claude-relay-service (CRS) pool (load-balances Claude
requests across many accounts at once, exposing a per-account schedulable
flag), offer to install the priority daemon that auto-deprioritizes
near-maxed accounts and re-enables them on recovery. This is independent of the
keychain rotator above — skip it for keychain-only setups.
AskUserQuestion:
Do you run a claude-relay-service (CRS) pool you want auto-prioritized?
[Yes — configure + install] — set base URL + admin creds, install the 120s daemon
[Not now] — skip (you can run /ops:rotate-setup again later)
[What is this?] — one-paragraph explainer, then re-ask
On [Yes]:
http://127.0.0.1:3000) and admin username (default cradmin). Write them
into the rotator config's crs block (create from config.example.json if
missing), and set crs.enabled=true:
CFG="$USER_CFG"
jq --arg url "$CRS_URL" --arg u "$CRS_USER" \
'.crs = ((.crs // {}) + {enabled:true, baseUrl:$url, adminUser:$u})' \
"$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
CRED="${CLAUDE_PLUGIN_ROOT}/lib/credential-store.sh"
ACCT="${CLAUDE_ROTATOR_KEYCHAIN_ACCOUNT:-$USER}"
printf '%s' "$CRS_ADMIN_PW" | bash "$CRED" set-stdin "CRS-Admin-$CRS_USER" "$ACCT"
(The CRS admin password is printed once in the container's
data/init.json — adminUsername/adminPassword — on first boot.)bash "${CLAUDE_PLUGIN_ROOT}/scripts/account-rotation/crs-priority-daemon.sh" --status
If it errors (login failed / unreachable), surface the message and let the
user re-enter creds — do NOT install a broken daemon.bash "${CLAUDE_PLUGIN_ROOT}/scripts/install-crs-priority-agent.sh"
macOS → launchd every 120s (RunAtLoad fires the first tick). Linux → the
installer prints the equivalent systemd-timer snippet.Tuning (optional, crs block): off5h/off7d (deprioritize thresholds),
on5h/on7d (re-enable thresholds, hysteresis), floor (min usable accounts),
freshMinutes (max age of utilization data trusted for proactive deprioritize).
Weekly-cap reconciliation (crs.weeklyReconcile, default on) is built into the
priority daemon itself — no separate install step. It clears a stale
weeklyRateLimitEndAt hold once live 7d utilization drops back under on7d,
and is a no-op on any deployment that never sets that field. Set
crs.weeklyReconcile=false (or $CRS_WEEKLY_RECONCILE=0) to disable it.
Only offer this if Step 4.5 configured a CRS pool (crs.enabled=true) — these
reconcilers assume one exists. Two independent, opt-in daemons close gaps the
priority daemon (Step 4.5) doesn't cover:
rateLimitStatus.isRateLimited=true from CRS (never a utilization
heuristic). Fast cadence (60s) since it reacts to a live rate limit.AskUserQuestion:
Enable the optional CRS reconcilers?
[Both] — 429-cooldown + 401-refresher (recommended if you run a busy pool)
[429-cooldown only] — fast rate-limit hold, skip proactive token refresh
[401-refresher only] — proactive token refresh, skip rate-limit hold
[Skip] — neither (you can re-run /ops:rotate-setup --crs later)
On anything but [Skip]:
Write the config flags.
CFG="$USER_CFG"
jq --argjson cooldown "$COOLDOWN_ENABLED" --argjson tokenRefresh "$TOKEN_REFRESH_ENABLED" \
'.crs = ((.crs // {}) + {cooldownEnabled:$cooldown, tokenRefreshEnabled:$tokenRefresh})' \
"$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
($COOLDOWN_ENABLED/$TOKEN_REFRESH_ENABLED are true/false per the
selection above.) Optional: also ask for crs.stateDir/crs.logDir if the
user wants non-default paths — see config.example.json for the full
annotated schema. Both reconcilers default to
<plugin-data-dir>/account-rotation if left unset.
Smoke-test before installing (each enabled reconciler, no writes):
[[ "$COOLDOWN_ENABLED" == "true" ]] && node "${CLAUDE_PLUGIN_ROOT}/scripts/account-rotation/crs-429-cooldown.mjs" --status
[[ "$TOKEN_REFRESH_ENABLED" == "true" ]] && node "${CLAUDE_PLUGIN_ROOT}/scripts/account-rotation/crs-401-refresher.mjs" --status
If either errors (login failed / unreachable), surface the message and let the user re-check Step 4.5's CRS creds — do NOT install a broken daemon.
Install (idempotent — installs whichever flags are true, uninstalls whichever were turned back off):
bash "${CLAUDE_PLUGIN_ROOT}/scripts/install-crs-reconcilers-agent.sh"
macOS → launchd, 60s / 300s cadence (RunAtLoad fires the first tick). Linux → the installer prints the equivalent systemd-timer snippets.
Only offer this if Step 4.6's 401-refresher was enabled — this reconciler
reads its needs-reauth flags. Unlike the other reconcilers, this one
dispatches a REAL browser-based re-auth attempt (via rotate.mjs --setup,
the same Gmail-via-gog flow this wizard itself uses in Step 4) — make sure
the user understands that before enabling it.
AskUserQuestion:
Enable unattended re-auth for accounts with a confirmed dead token?
[Yes — enable] — magic-link-autoloop dispatches rotate.mjs --setup automatically
[Not now] — a human handles re-auth manually when 401-refresher flags an account
[What is this?] — one-paragraph explainer, then re-ask
On [Yes]:
Write the config flag.
CFG="$USER_CFG"
jq '.crs = ((.crs // {}) + {enableMagicLinkRecovery:true})' \
"$CFG" > "$CFG.tmp" && mv "$CFG.tmp" "$CFG"
Optional: ask for a non-default crs.magicLinkRetryCooldownMs (default 6h) —
see config.example.json.
Smoke-test (no writes, no dispatch):
node "${CLAUDE_PLUGIN_ROOT}/scripts/account-rotation/magic-link-autoloop.mjs" --status
Install (folded into the same idempotent installer as Step 4.6):
bash "${CLAUDE_PLUGIN_ROOT}/scripts/install-crs-reconcilers-agent.sh"
macOS → launchd, 600s cadence, RunAtLoad=false (does NOT fire immediately
on install, unlike the other reconcilers — first tick waits for the normal
schedule so a fresh install never triggers a surprise browser launch).
Linux → the installer prints the equivalent systemd-timer snippet.
Note on provider scope: this reconciler's own logic (which account, when, how
often) has no email-provider dependency — it only decides what to retry and
delegates the actual OAuth+email-poll work to rotate.mjs --setup, which is
Gmail-via-gog today. A fully provider-agnostic email backend (e.g. IMAP)
would be a rotate.mjs-internals change, out of scope here.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OPS ► ROTATE-SETUP COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Accounts initialized:
✓ <id> (<plan>)
✓ <id> (<plan>)
✗ <id> (failed — re-run /ops:rotate-setup --account <email>)
Config: ~/.claude/plugins/data/ops-ops-marketplace/account-rotation-config.json
Keychain: Claude-Rotation-<key> (account: $USER) · key = label or email
CRS priority daemon: ✓ installed (every 120s) | ✗ not configured
CRS 429-cooldown: ✓ installed (every 60s) | ✗ not enabled
CRS 401-refresher: ✓ installed (every 300s) | ✗ not enabled
Magic-link autoloop: ✓ installed (every 600s) | ✗ not enabled
To enable automatic rotation, open /plugins → claude-ops → settings and
toggle "Multi-account Claude rotator" (account_rotation_enabled).
──────────────────────────────────────────────────────
(Show the CRS priority-daemon line only if Step 4.5 ran; show the two Step 4.6
reconciler lines only if Step 4.6 ran; show the magic-link line only if Step
4.7 ran. All four CRS daemons are independent of the account_rotation_enabled
toggle — each is gated by its own config flag (crs.enabled,
crs.cooldownEnabled, crs.tokenRefreshEnabled, crs.enableMagicLinkRecovery)
plus whether its launchd/systemd timer is installed.)
Exit. Do NOT auto-enable account_rotation_enabled — that decision belongs
to the user, made explicitly through the plugin settings UI.
--all (default): full wizard as described above.--account <email>: skip Step 2; only init the matching account.--add: skip token check; jump straight to Step 2 add loop, then init.--crs: jump straight to Step 4.5 (configure + install the CRS priority daemon), then Step 4.6 (offer the reconciler add-ons), skipping the keychain-account OAuth steps.--reconcilers: jump straight to Step 4.6 (offer/reconfigure the 429-cooldown and 401-refresher reconcilers, then Step 4.7's magic-link autoloop offer) — requires Step 4.5 already configured (crs.enabled=true); if not, print the same message as --crs needing configuration first and exit.| Symptom | Cause | Action |
|---|---|---|
oauth_failed (rotate.mjs exit ≠ 0) | login did not complete — Turnstile, Google SSO/2FA, or timeout | Open $LOG + rotation.log; if the cascade is waiting on a visible Chrome, let the user finish login there, then re-run |
playwright install failed | npm offline / sandbox | Run npx playwright install chromium in ${CLAUDE_PLUGIN_ROOT}/scripts/account-rotation, then retry |
token still ✗ after success | account label/email mismatch vs config | Confirm the config key (label // email) matches the Claude-Rotation-<key> service name |
| no CDP browser available | no Chrome on :9222 and none installed | rotate.mjs falls back to bundled Chromium, which Turnstile may block — install/launch Chrome so the cascade can attach |
| Google SSO / 2FA prompt | Workspace-domain account needs interactive Google login | Let the user complete login in the cascade's visible Chrome; do NOT auto-solve 2FA |
CRS --status "login failed" | wrong admin user/password or CRS not reachable | Re-enter creds (Step 4.5); admin creds are in the CRS container data/init.json; verify curl $CRS_URL/health |
| CRS daemon installed but no effect | crs.enabled=false, or all accounts already correctly flagged | Check crs.enabled in config; tail logs/crs-priority.log; a steady-state tick logs 0 change(s) |
CRS reconciler --status errors | wrong admin creds or CRS unreachable (same creds as priority daemon) | Re-check Step 4.5's CRS creds; curl $CRS_URL/health |
| CRS reconciler installed but no effect | crs.cooldownEnabled/crs.tokenRefreshEnabled false, or no account currently needs it | Check the relevant flag in config; tail logs/crs-429-cooldown.log or crs-401-refresher.log — both are safe no-ops when nothing needs action |
| magic-link-autoloop never dispatches | crs.enableMagicLinkRecovery false, 401-refresher not enabled/hasn't flagged anyone, or account is on magicLinkRetryCooldownMs cooldown | node magic-link-autoloop.mjs --status; confirm 401-refresher is enabled and has a needsReauth entry |
| magic-link-autoloop dispatches too often | magicLinkRetryCooldownMs too low for a persistently-broken account | Raise the cooldown in config; each attempt is a real browser-automation run, not free |
npx claudepluginhub lifecycle-innovations-limited/claude-ops --plugin opsManages multi-account Claude Max rotation with keychain token swapping and CRS relay-pool auto-prioritization. Useful when running multiple Claude accounts with rotation enabled.
Automates ChatGPT Team account rotation, batch free account generation, and Codex OAuth sync. Includes a web dashboard and Docker deployment.
Connects Claude CLI to Clawdbot by reading OAuth tokens from macOS Keychain, writing them to Clawdbot config, and auto-refreshing tokens every 2 hours via launchd.