antigravity-cli¶
Modules: pkgs/antigravity-cli.nix,
pkgs/antigravity-bump.nix,
home/shell/antigravity.nix
Google's terminal agent, agy. Three decisions: why it is here in place of the Gemini CLI, where
the binary comes from, and why the only thing declared under ~/.gemini is merged in, never
linked.
Why not gemini-cli, which is what I went looking for¶
The third agent CLI here was supposed to be gemini-cli, on the same terms as Claude Code and codex: a subscription login, no API key. That door is closed. Google announced the transition to Antigravity CLI on 19/05/2026, and on 18/06/2026 the Gemini CLI stopped serving requests for free tier, AI Pro and Ultra. Only paid enterprise licenses kept it.
The upstream README still advertises the free tier with a personal Google account, which is why this note exists: that is drift on their side, not a second opinion. What settles it is the CLI's own answer, in issue #28846:
reasonCode: UNSUPPORTED_CLIENT
reasonMessage: This client is no longer supported for Gemini Code Assist for individuals.
To continue using Gemini, please migrate to the Antigravity suite of products
nixpkgs already carries the verdict too. gemini-cli evaluates with a marker, not just an
outdated version:
Package 'gemini-cli-0.47.0' has the following problem: removal: Unpaid tier and
Google AI Pro/Ultra users: Gemini CLI was replaced by Antigravity CLI.
So the tool that keeps the "log in, never hold a key" contract is agy, and gemini-cli would only
have been an inert binary waiting for a GEMINI_API_KEY that rule 12 does not want here.
The login is the account, not a key¶
Measured on 24/08/2026 against an isolated HOME: agy prints a Google OAuth URL, waits 60
seconds for the browser round trip, and ALSO accepts an authorization code pasted back into the
terminal. The redirect goes through antigravity.google/oauth-callback, so the same flow works
over SSH, where there is no browser to open.
The token then lives in the system keyring, reached over D-Bus (org.freedesktop.secrets), which
is the gnome-keyring this machine already unlocks at login. No credential of mine is in the repo
and none is in an env var, which is rule 12 read from the auth side.
The headless path exists and is deliberately NOT used: setting modelProvider to gemini plus a
GEMINI_API_KEY. Their docs are explicit that the variable alone does nothing, so there is no way
to end up on the API-key path by accident.
Why the official binary and not nixpkgs¶
antigravity-cli IS in nixpkgs-unstable, and it is a fetchurl of the same published tarball,
so this is the codex question again with the same answer: the third layer of the version strategy.
Counted on 24/08/2026, the publisher's latest endpoint said 1.1.20 while nixpkgs-unstable
was on 1.1.13. The nixpkgs bumps are not neglected, they are just slower than a preview
product: 1.1.11 on 05/08, 1.1.13 on 13/08, 1.1.19 on 23/08, and that last one is on master, not
yet in the channel. Upstream ships almost daily.
The artifact is the easy kind. One file in the tarball, antigravity, 197 MiB, Go, stripped, and
dynamically linked against glibc, so autoPatchelfHook is the whole build. It is installed as
agy, which is the name its docs, its errors and its own install subcommand use.
No PATH dependency, unlike codex: ripgrep is EMBEDDED in the binary, which its own error
strings give away (no embedded ripgrep binary, and a third_party/rust/ripgrep/rg build path).
That is why there is no wrapper here.
versionCheckHook runs agy --version against the store path at build time, which is what proves
the patchelf took: an unpatched binary cannot start at all.
The in-app updater is not the path here, same as codex: agy update wants to overwrite a
binary that lives in the read-only store, and agy install, which edits shell profiles for PATH
and aliases, is a job Nix already did.
Nothing under ~/.gemini is declared, and that is a measurement¶
Two facts, in this order: there is nothing to declare YET, and the codex contract would not hold if there were.
Sparse persistence is why the repo looks empty next to codex's. Their docs are explicit that
the CLI writes only the values that DIFFER from the defaults, so with the settings untouched there
is no settings.json at all. Measured after my own login, on 1.1.20: ~/.gemini/config/config.json
holds ONE generated key, mcp_config.json is 0 bytes, and no settings file exists. Codex is the
opposite: config.toml is there from the first run and the app persists into it, which is what
makes a mirror worth having THERE.
And the symlink does not survive a write, tested on 1.1.20 against the path their docs name,
~/.gemini/antigravity-cli/settings.json:
| Step | Result |
|---|---|
| Hand-written keys reached through a symlink | READ and honored: colorScheme and showTips both survived |
| The same run, afterwards | the path is a REGULAR file, re-serialized pretty-printed by the app |
| The mirror at the other end | untouched, still holding what I wrote |
~/.gemini/config/config.json behaves the same way: pointed at a mirror holding {}, the first
write detaches the link and the mirror stays at {}. The key it rewrites there is
userSettings.remoteControlHostname, a generated name (nixos-kingston-fiery-ion, then
-orbital-mars, then -deep-drift), which also says what that file is: the app's scratch space,
not my config.
So editing by hand WORKS and a symlink does not, which is the opposite pair from codex. The day a
setting here is worth owning (toolPermission, enableTerminalSandbox, enableTelemetry, or an
MCP server), the mechanism is rule 14's other half: an idempotent activation that merges my keys
into whatever the app left, never a link.
The login is not under HOME at all, which the same test proved by accident: a run with HOME
pointed at an empty scratch directory answered the prompt anyway. The token is in the keyring,
reached over D-Bus, and the keyring is per USER, so isolating HOME does not isolate auth and no
file under ~/.gemini carries the credential.
So the declaration is the PACKAGE and nothing else. Everything the tool keeps is state and belongs
to restic (rule 6), which paths = [ "/home/v1cferr" ] already covers:
| Path | What it holds |
|---|---|
~/.gemini/config/ |
config.json, mcp_config.json, per-project files, a .migrated marker |
~/.gemini/antigravity-cli/ |
settings.json and keybindings.json once touched, plus conversations, knowledge, brain, logs, crashes |
~/.cache/ms-playwright-go/ |
the browser runtime it downloads on its own for the browser tools |
A correction worth keeping (24/08/2026, same day): I first read the missing
settings.json plus the .migrated marker next to config.json as the settings file having
MOVED, and wrote here that their docs described a previous layout. Wrong. The documented path is
live, and what keeps it from existing is the sparse persistence above, proven by writing one by
hand and watching both keys take effect. config.json and settings.json are two different files
with two different jobs, and only the second one would ever be mine.
The MCP servers, which ARE declared, by merging¶
~/.gemini/config/mcp_config.json is where agy reads its servers from, and it is created empty
(0 bytes) on the first run. Since 24/08/2026 it points at
basic-memory, the memory the three CLIs share.
Given everything above, the mechanism is the merge and not a link:
home/shell/antigravity.nix runs a jq merge at
activation, so Nix owns the key it declares and whatever else lives in that file survives. * and
not + in the jq, because the recursive merge is what leaves a sibling server intact. It is
idempotent, so every rebuild reasserts the endpoint, and an empty file is treated as {} since jq
cannot parse zero bytes.
serverUrl and NOT url: their docs say the legacy key is no longer supported, and the
failure mode is silence. A server that does not parse is simply not there, with no message.
The endpoint itself is never a literal here: it comes from my.memory.url, the option declared in
home/services/basic-memory.nix (rule 11).