antigravity-swap
Multi-account switcher for the Antigravity CLI (agy). Switch between Google accounts without signing out, let aswap switch for you before an account runs out, see every account's quota in one table, and run long print-mode tasks that carry on to the next account when one hits its limit.
Modeled on claude-swap: same commands, same defaults where they make sense for agy.
Installation
Using uv (recommended)
uv tool install antigravity-swap
Using pipx
pipx install antigravity-swap
Standalone binary
No Python needed. Linux (x86_64, aarch64) and macOS (Apple silicon):
curl -fsSL https://raw.githubusercontent.com/hrmasss/antigravity-swap/main/install.sh | sh
Windows (PowerShell):
$d = "$env:LOCALAPPDATA\Programs\aswap"; New-Item -ItemType Directory -Force $d | Out-Null
irm https://github.com/hrmasss/antigravity-swap/releases/latest/download/aswap-windows-x86_64.exe -OutFile "$d\aswap.exe"
# then add $d to your PATH
Every binary is listed with its checksum on the releases page.
From source
git clone https://github.com/hrmasss/antigravity-swap.git
cd antigravity-swap
uv sync
uv run aswap --help
Updating
aswap upgrade # detects uv, pipx or the binary installer
# or run your installer directly:
uv tool install --force --refresh antigravity-swap
pipx upgrade antigravity-swap
On Windows, aswap upgrade prints the command instead of running it, because Windows keeps aswap.exe locked while it runs.
Usage
Add your accounts
Sign in to agy with your first account, then:
aswap add
Sign in to agy with the next account (just run agy and pick it; no need to sign out first) and run aswap add again. Repeat for each account. Re-running aswap add on an account that is already stored updates its login instead of duplicating it.
Already keep accounts in separate agy directories (agy --gemini_dir=...)? Import them directly:
aswap add --from-dir ~/agy-accounts/work --alias work
See every account
aswap list
Accounts:
1: you@example.com [home] (active)
├ Gemini 5h: 23% resets 13:42 in 4h 34m
├ Gemini 7d: 5% resets Oct 8 18:23 in 6d 9h
├ Claude/GPT 5h: 0% full
└ Claude/GPT 7d: 33% resets Oct 6 14:06 in 4d 4h
2: work@example.com [work] (limited until 17:13, in 1h 2m)
├ Gemini 5h: 0% full
├ Gemini 7d: 44% resets Oct 5 13:02 in 3d 3h
├ Claude/GPT 5h: 0% full
└ Claude/GPT 7d: 0% full
Running instances:
● TUI ~/code/app 1: you@example.com [home] (2 sessions)
● print ~/code/infra 2: work@example.com [work] (1 session)
Numbers are used percent per window, with when each window resets. agy meters two pools separately: the Gemini models, and Claude and GPT models through agy. full means nothing is used yet. (active) marks the account plain agy uses now. aswap list --refresh forces fresh readings, --cached skips the network, and --table prints one compact row per account.
Running instances lists every agy session on the machine, grouped by working directory, with the account it runs on: a session in an aswap directory by its slot, one started with --gemini_dir by the login in that directory, and one on the default login by whichever account was active when it started. A session that started before the first switch aswap recorded shows as "account unknown".
Readings come from the same endpoint the agy CLI uses. When an account's access token has expired, aswap renews it by running agy models, which lists models and spends no quota.
Switch accounts
Rotate to the next account:
aswap switch
Or pick one:
aswap switch 2
aswap switch work@example.com
aswap switch work # by alias, once set with `aswap alias 2 work`
Or let aswap choose: aswap switch --strategy most-quota, round-robin, next-available or soonest-reset (see Strategies).
After writing the new login, aswap runs agy models to prove it works. If the login was revoked, aswap switches back, quarantines that account, and tells you to re-add it.
New agy launches use the new account. A session that is already running keeps the account it started with, because agy holds its token in memory. Sessions started with aswap run move to the next account on their own when they run out.
Automatic switching
aswap auto # foreground loop, checks every 60s
aswap auto --threshold 80 # switch earlier
aswap auto --pool 3p # decide on the Claude/GPT quota instead of Gemini
aswap auto --once # one check, for cron or scripts
aswap auto --dry-run # say what it would do, never switch
aswap auto --strategy consume-first
How it decides
- The binding window is the most-used window in the chosen pool (
geminiby default,3p, orall). At or abovethreshold(default 90%), aswap looks for a better account. - A proactive switch only lands on an account below the threshold and at least
hysteresis_pct(default 10) points less used, so two accounts near the line never ping-pong. - A cooldown (default 5 min) separates proactive switches. An account that is fully spent, or held out after a quota error, skips the cooldown.
consume-firstkeeps you on the account whose weekly window resets soonest, switching even below the threshold, so quota that is about to reset is not wasted.- Polling is bounded: the active account every tick, at most two others per tick, each about every ten minutes.
- When every account is over the threshold, it reports
all-exhaustedand checks less often until the first hold ends. - Accounts that are disabled, quarantined or held out are never picked.
For cron, --once reports in its exit code: 0 switched, 1 error, 2 nothing to do, 3 blocked (no viable target). --json prints one JSON event per line:
*/5 * * * * aswap auto --once --json >> ~/.aswap-auto.log 2>&1
Keep a session going when an account runs out: aswap run
Start agy through aswap instead of directly:
aswap run # the active account, in this terminal
aswap run work # start on a specific account
aswap run -- --model claude-sonnet-4-6 --dangerously-skip-permissions # everything after -- goes to agy
When the account hits its quota mid-session, aswap:
- holds that account out until the reset time agy reports (
Resets in 4h42m2s), orfailover.limit_fallback_hoursif none is given, - picks the next account by
failover.strategy(defaultbest: the most quota left, read fresh at that moment), - stops agy, copies the conversation to the next account, and starts agy again with
--conversation <id> -i "Please continue where you left off.".
The conversation is a local file, so the next account has the whole history and picks up the task. You see agy restart in the same terminal and carry on. If no other account is free, aswap leaves the session alone and tells you.
Why a restart, and not a swap under the running session like claude-swap? Claude Code re-reads its credentials file, so claude-swap can change account in place. agy keeps its login in memory for the life of the process; replacing the file under a running agy does nothing (tested). Restarting on the same conversation is the closest equivalent.
aswap watches agy's log for the final quota error. agy retries a quota error a couple of times first ("attempt 1 failed ... retrying"), and those retries never trigger a switch. Only the error that ends the turn does.
To make it the default, alias it:
alias agy='aswap run --' # bash/zsh; agy -c becomes aswap run -- -c
aswap run --no-failover (or aswap config set run.failover false) runs a plain session on one account.
Where each account runs. On Linux each account other than the default login gets its own agy directory under aswap's data folder, with your settings, MCP config, skills and finished first-run setup mirrored from ~/.gemini on every launch, so several accounts run side by side. On Windows and keyring platforms agy has one login per machine, so a hop switches the default login, and says so.
Bind a directory to an account and a bare aswap run there starts on it:
aswap map 2 ~/work/client-app
cd ~/work/client-app && aswap run # account 2
aswap map # list bindings
aswap unmap ~/work/client-app
--share-history shares conversations between an account's directory and your default agy.
Print-mode tasks: aswap exec
The same failover for agy -p runs, for scripts and unattended work:
aswap exec -- -p "migrate the tests to pytest" --dangerously-skip-permissions
aswap exec work -- -p "..." # start on a specific account
aswap exec --strategy round-robin --max-hops 3 -- -p "..."
Before each hop aswap checks the login with agy models; an account whose login is dead is quarantined and skipped instead of stalling on a sign-in prompt. Output: with no --output-format, you get the final response as plain text; json gives agy's JSON; stream-json streams through. Exit code is agy's, or 3 when every account is spent. aswap run -- -p "..." does the same thing.
Strategies
One set of strategies everywhere a next account is picked (switch --strategy, run, exec, auto). Either name works:
| Strategy | Also called | Picks |
|---|---|---|
best |
most-quota |
the account with the most quota left in the pool you are using. Default for run, exec and auto. |
rotate |
round-robin |
the next account by number after the current one. Default for a bare aswap switch. |
next-available |
the next account by number that still has quota | |
consume-first |
soonest-reset |
among accounts with room, the one whose weekly window resets soonest, so quota that is about to reset gets used first |
Accounts that are disabled, quarantined or held out after a quota error are never picked. The pool follows the model: --model claude-* or gpt-* judges the Claude/GPT quota, anything else the Gemini quota.
Platform support
Where agy keeps its login decides what is possible:
| Linux (headless) | Windows | macOS, desktop Linux | |
|---|---|---|---|
| Login lives in | ~/.gemini/antigravity-cli/antigravity-oauth-token |
Credential Manager, gemini:antigravity |
system keyring |
add, list, switch, auto, exec |
yes | yes | experimental |
run with failover |
yes | yes (switches the default login) | yes |
| two accounts at once | yes | no | no |
| Quota for non-active accounts | always | yes, by a brief login swap (below) | yes, by a brief login swap |
On Windows and keyring platforms the login is global: agy --gemini_dir changes where conversations go, not which account signs in. So one account is active per machine.
Access tokens last about an hour. To keep every account's quota fresh there, aswap renews an expired non-active account by putting its login in place for the few seconds agy models takes (no quota is spent), saving the renewed token, and putting your login back, all under aswap's lock. Running agy sessions are not affected, since they hold their token in memory. An agy you launch during those seconds would start on the borrowed account. Turn it off with aswap config set usage.renew_inactive false; non-active accounts then show token expired an hour after their last use. Force a backend with ASWAP_BACKEND=file|wincred|keychain|secret-service.
Tested against agy 1.2.x on Linux and Windows. macOS and desktop-keyring support follow how agy's keyring library stores the login and have not been tested on real machines yet; reports welcome.
Other commands
aswap status # which account agy is using now
aswap refresh [N] # renew tokens and fetch quota now
aswap watch # live quota table
aswap disable 2 / aswap enable 2 # hold an account out of rotation (still a valid explicit target)
aswap limit 2 90m # hold it out for a while
aswap limit 2 --clear # clear a hold or a quarantine
aswap alias 2 work # short name, usable anywhere a number or email is
aswap alias 2 --unset
aswap remove 2 # forget an account (agy's own login is untouched)
aswap config # settings, see below
aswap export backup.json # all accounts to a file
aswap import backup.json # skips accounts that exist; --force replaces
aswap purge # delete all aswap data
Bare aswap is aswap list. rm is short for remove.
claude-swap flag spellings
The flag forms from claude-swap work too, so muscle memory carries over:
aswap --list # aswap list
aswap --status # aswap status
aswap --switch # aswap switch
aswap --switch-to 2 # aswap switch 2
aswap --add-account # aswap add
aswap --remove-account 2 # aswap remove 2
aswap --disable-account 2 / --enable-account 2
aswap --watch # aswap watch (also --tui)
aswap --export f / --import f / --upgrade / --auto / --refresh
Configuration
aswap config # every key, its value, and whether it is the default
aswap config set autoswitch.threshold 80 # validated
aswap config get autoswitch.threshold
aswap config unset autoswitch.threshold
aswap config path
| Key | Default | Meaning |
|---|---|---|
autoswitch.threshold |
90 |
used % that makes auto look for a better account |
autoswitch.interval_seconds |
60 |
seconds between checks |
autoswitch.cooldown_seconds |
300 |
minimum gap between proactive switches |
autoswitch.hysteresis_pct |
10 |
how much less used a target must be |
autoswitch.strategy |
best |
best or consume-first |
autoswitch.pool |
gemini |
gemini, 3p or all |
switch.verify |
true |
prove each new login with agy models |
run.failover |
true |
aswap run restarts on the next account when one runs out |
failover.strategy |
best |
how run and exec pick the next account (see Strategies) |
failover.max_hops |
0 |
most account changes per run; 0 is every account once |
failover.resume_prompt |
Please continue where you left off. |
sent when a conversation moves to the next account |
failover.limit_fallback_hours |
5 |
hold time when a quota error names no reset |
usage.max_age_seconds |
300 |
readings older than this are re-fetched |
usage.renew |
true |
renew expired tokens with agy models before reading quota |
usage.renew_inactive |
true |
on single-login platforms, renew other accounts by a brief login swap |
agy.path |
(PATH) | the agy binary to use; the ASWAP_AGY environment variable overrides it |
JSON output
list, status, switch and config take --json. Every payload has schemaVersion: 1; fields are only ever added.
{
"schemaVersion": 1,
"activeAccountNumber": 1,
"backend": "file",
"accounts": [
{ "number": 1, "email": "you@example.com", "active": true, "usageStatus": "ok", "usageAgeSeconds": 42,
"usage": { "gemini-5h": { "usedPct": 90.0, "resetsAt": "2026-10-01T17:23:31Z" },
"gemini-weekly": { "usedPct": 44.0, "resetsAt": "2026-10-05T07:02:44Z" } } }
]
}
Rows may also carry alias, disabled, limitedUntil / limitedReason, quarantined / quarantineReason, and usageError. list and status also carry runningInstances: pid, mode (TUI or print), cwd, accountNumber, startedAt.
How it works
aswap addcopies agy's login (its OAuth token blob) into aswap's data folder, labelled by the email in its ID token.aswap switchsaves the current login back to its slot first (agy may have renewed it), then writes the target's login where agy reads it, under a lock so two aswap processes never interleave.- Which account is active is never stored: aswap reads agy's current login and matches it, so it cannot drift.
- Token renewal is agy's own: aswap runs
agy models, which renews an expired access token and writes it back. aswap never holds an OAuth client secret. - Quota comes from
loadCodeAssistandretrieveUserQuotaSummaryon Google's Cloud Code endpoint, called with each account's own access token.
Data locations
| Platform | Folder |
|---|---|
| Linux | ${XDG_DATA_HOME:-~/.local/share}/antigravity-swap/ |
| macOS | ~/Library/Application Support/antigravity-swap/ |
| Windows | %LOCALAPPDATA%\antigravity-swap\ |
Override with ASWAP_HOME. Inside: accounts.json, credentials/<n>.json (0600, folder 0700), usage/, sessions/<n>/ (agy directories for run and exec), settings.json. ASWAP_GEMINI_DIR overrides agy's default ~/.gemini.
The stored logins and aswap export files are live credentials in plaintext. Treat them like SSH keys.
Uninstall
aswap purge
uv tool uninstall antigravity-swap # or: pipx uninstall antigravity-swap
Requirements
- The Antigravity CLI (
agy), signed in - Python 3.12+ (not needed for the standalone binary)
License
MIT
Metadata
Release files for antigravity-swap 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| antigravity_swap-0.3.0.tar.gz | 54.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| antigravity_swap-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 109.6 kB
Release files / antigravity_swap-0.3.0.tar.gz
| Download URL | antigravity_swap-0.3.0.tar.gz |
|---|---|
| Size | 54.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
151add32022d9f99b7200689a15dfebb5d8b75929703445b1acb28901837dc33
|
|
BLAKE2b-256 checksum How to use checksums |
a7867ff5a92abfbf78f9e3ecbaf8d0a7d093ee057be12c2d1946600ce630dc5a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / antigravity_swap-0.3.0-py3-none-any.whl
| Download URL | antigravity_swap-0.3.0-py3-none-any.whl |
|---|---|
| Size | 55.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a491cab3b28c131cf1db04206d04d026572e2f01afd4bff02a42a0eee20818e5
|
|
BLAKE2b-256 checksum How to use checksums |
4015c2975875613fb7458a5eea25b16c83608275cd5d8c4e2821b3fcfa14c8b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log