agy-cli-manager is a Python account manager for Antigravity CLI (agy) with active-standby failover, quota-aware switching, and machine-readable automation APIs.
It helps you run multiple Antigravity CLI accounts more safely by:
- switching away from low-quota or failed accounts
- watching live Antigravity CLI logs for
Individual quota reachedand failing over automatically - keeping a managed runtime profile in sync with the active account
- exposing CLI and Python APIs for bots, schedulers, and external apps
- supporting manual or automatic account rotation policies
Keywords: Antigravity CLI account manager, Antigravity CLI multi account manager, Antigravity multi account auth, Antigravity login manager, Antigravity auth manager, Antigravity account switcher, agy multi account manager, agy multi account auth, agy login manager, agy auth manager, agy failover, agy quota switching, Gemini CLI multi account auth, Gemini CLI account rotation.
It is designed for one active account at a time:
- keep multiple saved
agyprofiles - switch the active profile explicitly or after failure
- expose machine-readable state for external callers
- stay usable as a CLI app, TUI dashboard, or Python library
It is application-agnostic. A Telegram bot can call it, but the manager itself is not Telegram-specific.
Project links:
- Repo:
https://github.com/zcop/agy-cli-manager - Release wheel:
https://github.com/zcop/agy-cli-manager/releases - GitHub Pages site:
https://zcop.github.io/agy-cli-manager/
- stores multiple account profiles safely
- keeps one account active while others stay standby/cooldown/disabled
- supports isolated interactive
agylogin - can import an existing
~/.geminior similar live home - supports both manual-only and automatic failover switching modes
- prefers fuller, healthier standby accounts when auto-switching
- tracks cached identity, health, and usage metadata
- tracks live switch coordinator state for callers that need to wait on failover
- exposes CLI commands and JSON output for automation
- supports account failover with cooldowns and lock-protected state changes
- tails Antigravity CLI logs so a running
agyTUI can trigger failover without a bot caller
- Python 3.10+
- a working
agybinary available inPATH, or passed explicitly with--agy-binary - a terminal if you want to use
loginor the full-screen dashboard
From a GitHub release wheel:
python3 -m venv .venv
. .venv/bin/activate
pip install https://github.com/zcop/agy-cli-manager/releases/download/v0.2.1/agy_cli_manager-0.2.1-py3-none-any.whlTo move to a newer release later, replace v0.2.1 and the wheel filename with the newer version:
pip install --upgrade https://github.com/zcop/agy-cli-manager/releases/download/v0.2.1/agy_cli_manager-0.2.1-py3-none-any.whlFrom this repo:
cd agy-cli-manager
python3 -m venv .venv
. .venv/bin/activate
pip install -e .After that, you can use either:
agy-cli-manager --helpor:
PYTHONPATH=src python3 -m agy_cli_manager.cli --helpagy-cli-manager initBy default, state lives under:
~/.agy-cli-manager
You can override that with --root /path/to/root.
If you already have a live Antigravity home:
agy-cli-manager import-current my-account ~/.geminiIf you want the manager to drive a fresh interactive login itself:
agy-cli-manager login my-account --agy-binary /path/to/agylogin will hand your terminal to a real agy session. Complete the normal Antigravity onboarding/login there, then exit agy. The manager will save the resulting profile snapshot.
agy-cli-manager status
agy-cli-manager current
agy-cli-manager listagy-cli-managerWith no subcommand, the full-screen dashboard opens by default.
When agy hits Individual quota reached, the manager switches to the next account. Restart agy after that. The old process may keep writing quota errors; those lines do not rotate again until you acknowledge the restart or a new session log appears.
agy-cli-manager switch-mode auto
agy-cli-manager watchAfter restarting agy:
agy-cli-manager ack-restartLeave the dashboard open instead of watch if you prefer (Y acknowledges the restart). Do not pass --from-start unless you intend to replay old quota errors.
agy-cli-manager status --json
agy-cli-manager whoami
agy-cli-manager models --json
agy-cli-manager ensure-active --json
agy-cli-manager switch-mode
agy-cli-manager switch-mode manual
agy-cli-manager switch-mode auto
agy-cli-manager switch-policy --json
agy-cli-manager switch-policy --short-threshold 10 --refresh-failure-threshold 2 --candidate-strategy balanced
agy-cli-manager refresh-usage --json
agy-cli-manager switch-next
agy-cli-manager rotate-after-failure --reason quota --cooldown-minutes 60 --json
agy-cli-manager watch
agy-cli-manager watch --once --json
agy-cli-manager ack-restartThe current switch policy is stored in manager state and can be controlled by either:
- CLI:
switch-mode,switch-policy,ensure-active - Python API:
get_status_snapshot(),get_switch_policy(),update_switch_policy(),ensure_active_account()
Directory layout:
~/.agy-cli-manager/
├── accounts/
│ └── <account-name>/
│ └── .gemini/
│ └── ...
├── runtime/
│ └── .gemini/
└── state.json
Optional integration:
live_dircan point at a real Antigravity/Gemini CLI home such as~/.gemini- when set, switches sync the managed active profile into that live CLI home
Example:
agy-cli-manager set-live-dir ~/.gemini
agy-cli-manager apply-activeThis is useful when another process launches agy and you want that live home to always reflect the currently active saved profile.
Commands:
agy-cli-manager
agy-cli-manager dashboard
agy-cli-manager menu
agy-cli-manager init
agy-cli-manager list
agy-cli-manager current
agy-cli-manager status
agy-cli-manager status --json
agy-cli-manager ensure-active
agy-cli-manager switch-mode
agy-cli-manager switch-mode manual
agy-cli-manager switch-mode auto
agy-cli-manager switch-policy
agy-cli-manager refresh-usage
agy-cli-manager refresh-usage account1 --json
agy-cli-manager refresh-due
agy-cli-manager refresh-due --json
agy-cli-manager models
agy-cli-manager models --json
agy-cli-manager models account1 --json
agy-cli-manager whoami
agy-cli-manager whoami account1 --refresh
agy-cli-manager whoami account1 --probe-usage --agy-binary /path/to/agy
agy-cli-manager add account1 /path/to/source
agy-cli-manager import-current account1
agy-cli-manager import-current account1 /path/to/.gemini
agy-cli-manager login
agy-cli-manager login account1 --agy-binary /path/to/agy
agy-cli-manager activate account1
agy-cli-manager switch account1
agy-cli-manager rotate
agy-cli-manager switch-next
agy-cli-manager disable account1
agy-cli-manager enable account1
agy-cli-manager mark-bad account1 --reason quota --cooldown-minutes 60
agy-cli-manager clear-bad account1
agy-cli-manager set-live-dir ~/.gemini
agy-cli-manager apply-active
agy-cli-manager switch-mode manual
agy-cli-manager rotate-after-failure --reason quota --cooldown-minutes 60 --json
agy-cli-manager rotate-after-failure --reason quota --cooldown-minutes 60 --force-switch --json
agy-cli-manager watch
agy-cli-manager watch --once --json
agy-cli-manager watch --no-rotate --once
agy-cli-manager update-meta account1 --usage-status known --usage-value 42 --reset-at 2026-07-01T00:00:00+00:00 --health-status healthy --last-live-check-at 2026-06-30T06:00:00+00:00 --next-live-check-at 2026-06-30T06:30:00+00:00 --refresh-policy-seconds 1800
agy-cli-manager update-meta account1 --short-usage-status known --short-usage-value 97.57 --short-reset-at 2026-07-01T00:00:00+00:00 --weekly-usage-status unknownadd accepts either:
- a directory that is already a
.geminiprofile root - or a parent directory containing
.gemini/
For automation, prefer the JSON-capable commands:
agy-cli-manager status --json
agy-cli-manager current --json
agy-cli-manager list --json
agy-cli-manager ensure-active --json
agy-cli-manager switch-policy --json
agy-cli-manager switch-policy --short-threshold 12.5 --refresh-failure-threshold 3 --candidate-strategy highest-short --json
agy-cli-manager refresh-usage account1 --json
agy-cli-manager refresh-due --json
agy-cli-manager models --json
agy-cli-manager rotate-after-failure --reason quota --cooldown-minutes 60 --json
agy-cli-manager watch --once --jsonTypical external-app flow:
- read current state with
status --json - call
ensure-active --jsonbefore sending real work if you want the manager to preflight the active account - read
switch_modeandswitch_policyto decide how aggressively your caller should auto-fail over - use
models --jsonif the caller needs model choices for the active account - call
refresh-usage --jsonorrefresh-due --jsononly when needed - if a real request fails due to auth/quota, call
rotate-after-failure --json - inspect
switch_runtimeor wait briefly until it leavesswitching - retry the real request once on the new active account
- persist caller-side observations back with
update-meta
Notes:
- running
agy-cli-managerwith no subcommand opens the full-screen dashboard dashboardis a TTY-only full-screen view with a fast local-only UI refresh and manual account actionslist,current,activate, androtateare convenience commands for standalone use; they map to the same manager state as the lower-level commands.- local operator notes such as
AGENTS.mdare intentionally kept untracked and are not part of the public repo contract. agy-cli-manager loginprompts for the account name if you do not pass oneswitch-nextskips accounts in cooldown.mark-badclears the active pointer if that account was active.ensure-activeevaluates the current policy and can automatically recover from no active account, known low 5-hour quota, auth missing, or repeated refresh failures.ensure-activereturns JSON withswitch_runtime, so callers can see whether the manager is idle, switching, ready, or has no standby account available.switch-modecontrols whetherrotate-after-failureautomatically moves to the next eligible standby account or stops after marking the active account bad.switch-policycontrols the proactive short-window threshold, refresh-failure threshold, and standby candidate ranking strategy.- state and switching are protected by a single lock file so a caller can safely trigger failover from another process.
set-live-dirlets the manager drive a real CLI home in addition to its own internalruntime/.- the manager currently copies the managed profile under
.gemini/, centered on the Antigravity auth/token artifacts it needs for switching. - it supports Antigravity-style
antigravity-cli/antigravity-oauth-tokenauth storage and related identity extraction. loginhands the terminal directly to a realagysession in the configured runtime home; complete onboarding/login there, exitagy, and the manager then saves the captured profile snapshot.loginstores the profile under the detected account name when available, not just the typed label.- if that detected account already exists,
loginwarns and asks whether to overwrite the saved profile. whoamireports the detected signed-in account name from profile metadata, and--probe-usagecan additionally runagy -p /usageagainst that profile as a live check.modelsrunsagy modelsfor the active account or a named saved profile and can return structured JSON for external callers.- the manager intentionally does not use scripted PTY startup probing for
agy; profile switching is filesystem-based. Runtime health still comes from real request success/failure, including Antigravity CLI log lines. watchtailslive_dir/antigravity-cli/log/(andcli.log) forRESOURCE_EXHAUSTED (code 429): Individual quota reachedand weekly quota lines. It starts at end-of-file so historical quota errors are not replayed.- in
automode,watchand the dashboard log poll callrotate-after-failurewithtrigger=log-watch. Inmanualmode they report the error and leave the active account in place unless--force-switchis set. - a switched profile is on disk (and in the live CLI home) immediately; a running
agyprocess must be restarted to pick up the new token. - in
automode,ensure-activeandrefresh-usage/refresh-duecan proactively switch away from an active account when the cached 5-hour window falls to the configuredshort_usage_threshold_percent, auth is missing, or refresh failures reach the configured threshold. - cached quota is advisory; real runtime failure is still the final authority for callers such as bots.
- when auto-switching, the manager ranks the standby pool and prefers accounts with better health and more remaining short-window quota instead of simply taking the first account by name.
- the default switch policy is
short_usage_threshold_percent=10,refresh_failure_threshold=2,candidate_strategy=balanced. rotate-after-failureis the public failover operation for external apps: mark the current active account bad, optionally put it in cooldown, then switch to the next eligible standby account.rotate-after-failureis idempotent across a short dedupe window and reports anoutcomesuch asswitched,already_switched, orno_candidate.switch_runtimeis persisted in state so a caller can coordinate retry logic without racing another caller into a second switch.rotate-after-failurefollows the persisted switch mode by default:autoattempts failover,manualleaves the manager inactive until an operator or caller explicitly switches accounts. Use--force-switchto override that for one run.update-metalets an external app persist cached runtime metadata such as usage, reset time, health, last check, and next refresh time.refresh-dueis the non-interactive refresh entrypoint for cron/systemd/external callers; it refreshes the active account first when due, otherwise the first due eligible standby account.- usage metadata is stored under
usage_windows.shortandusage_windows.weekly; the old flatusage_*andreset_atfields remain as compatibility aliases for the short window. - dashboard keybindings:
Up/Downorj/kmove,nlogin,iimport,Enteroraactivate,rrotate,wtoggle switch mode (auto/manual),eenable/disable,cclear bad,mmark bad,scycle sort (added,usage,countdown),ulocal refresh,tcycle UI refresh (5s/10s/15s/30s),qquit. - dashboard overview now shows both account quota state and switch coordinator state.
- while the dashboard is open it also tails live Antigravity CLI logs every second and can fail over in
automode. The header showsLogWatch: restart agyuntil you restartagyafter a log-triggered switch.
Cached runtime metadata:
- usage/reset/health data is persisted in manager state
- the dashboard list currently uses the short window for its usage and countdown columns
- the selected-account panel shows both the short window and a reserved weekly window slot
- on relaunch, the dashboard reuses cached metadata immediately
- countdowns and freshness are recalculated locally from saved timestamps
- external apps should update this metadata after real checks or real requests
- fast dashboard refresh does not itself perform live checks
Python usage:
from pathlib import Path
from agy_cli_manager import (
build_paths,
get_status_snapshot,
get_switch_policy,
list_models,
poll_quota_logs,
rotate_after_failure,
update_switch_policy,
)
paths = build_paths(Path.home() / ".agy-cli-manager")
snapshot = get_status_snapshot(paths)
policy = get_switch_policy(paths)
update_switch_policy(paths, short_usage_threshold_percent=12.5, candidate_strategy="highest-short")
models = list_models(paths)
result = rotate_after_failure(paths, reason="quota", cooldown_minutes=60)
print(snapshot["active"], "->", result.switched_to)
print(policy)
print([model["name"] for model in models["models"]])Public Python API:
build_paths(root)ensure_layout(paths)get_status_snapshot(paths)get_switch_policy(paths)update_switch_policy(paths, ...)ensure_active_account(paths, force=False)list_models(paths, name=None, ...)refresh_account_usage(paths, name=None, ...)refresh_due_account(paths, ...)switch_account(paths, name)switch_next(paths)rotate_after_failure(paths, reason, cooldown_minutes=60, live_dir=None, force_switch=False)poll_quota_logs(paths, ...)watch_quota_logs(paths, ...)parse_quota_log_line(line)set_switch_mode(paths, mode)set_live_dir(paths, live_dir)update_account_runtime_metadata(paths, name, ...)
Important returned state:
get_status_snapshot(paths)includesswitch_runtimeandlog_watchensure_active_account(...)reports the active account decisionrotate_after_failure(...)returns aRotationResultwithoutcome
switch_runtime has these practical states:
idle: no failover is happeningswitching: a caller has started coordinated failoverready: failover finished and an active account is setno_account: failover finished but no eligible standby account was available
More explicit example:
from pathlib import Path
from agy_cli_manager import build_paths, ensure_layout, list_models
paths = build_paths(Path.home() / ".agy-cli-manager")
ensure_layout(paths)
payload = list_models(paths)
for model in payload["models"]:
print(model["name"], model["variant"])