Call the Google Antigravity CLI (agy) headlessly from any non-TTY context (subprocess, MCP, CI). Works around upstream bug #76.
Project description
agy-headless-bridge
Call the Google Antigravity CLI (agy) headlessly โ and actually get output back.
Codename PtyGravity ยท pty + antiGravity
๐ Architecture & docs โ rhishi99.github.io/agy-headless-bridge
TL;DR โ the problem, before & after
agy -p "<prompt>" prints nothing when its stdout is not a real terminal.
So calling it from a subprocess, an MCP server, CI, or another coding agent
(Claude Code, Codex, โฆ) returns an empty string and exit 0 โ silently. This
package gives agy a fresh pseudo-terminal so it emits normally, then cleans
the output.
flowchart TB
subgraph B["โ BEFORE โ agy -p from any non-TTY caller"]
direction TB
a1["subprocess ยท MCP ยท CI ยท agent"] --> a2["agy -p "prompt""]
a2 --> a3["stdout gated by isatty()"]
a3 --> a4["(empty string)<br/>exit 0 ยท no error ยท no output"]
end
subgraph A["โ
AFTER โ through agy-headless-bridge"]
direction TB
b1["subprocess ยท MCP ยท CI ยท agent"] --> b2["run(prompt)"]
b2 --> b3["allocate fresh pseudo-terminal"]
b3 --> b4["agy -p "prompt"<br/>isatty() == True"]
b4 --> b5["clean() strips ANSI/TUI"]
b5 --> b6["clean text โ"]
end
classDef bad fill:#2a1313,stroke:#f87171,color:#ffd9d9;
classDef good fill:#0f2a1e,stroke:#34d399,color:#d7ffe9;
class a1,a2,a3,a4 bad;
class b1,b2,b3,b4,b5,b6 good;
# โ The problem โ plain subprocess
import subprocess
r = subprocess.run(["agy", "-p", "say hi"], capture_output=True, text=True)
print(r.stdout) # '' โ prints nothing, exit 0
# โ
The fix
from agy_headless_bridge import run
print(run("say hi")) # 'Hi! How can I help?'
Three entry points around one core:
| Entry point | Invoke | Use for |
|---|---|---|
| Library | from agy_headless_bridge import run |
embedding agy in Python |
| CLI | agy-bridge "prompt" |
shell scripts, quick calls |
| MCP server | python -m agy_headless_bridge.mcp_server |
letting an agent call agy as a tool |
The problem in detail โ upstream bug #76
agy gates its stdout on isatty(). The instant stdout isn't a terminal, it
goes silent โ no output, no error, exit 0:
$ agy -p "say hi" | cat
$ # empty. exit 0. nothing.
The common winpty agy -p "..." workaround needs a terminal that already
exists, so it still fails from any automated / non-TTY caller.
The fix โ give agy a tty it didn't ask for
Allocate a brand-new pseudo-terminal (one that needs no parent tty) and
attach agy to it. Same code path on every OS โ only the pty allocator differs.
flowchart TD
A["Caller โ non-TTY<br/>Claude Code ยท MCP ยท subprocess ยท CI"] -->|"prompt"| B{{"run(prompt)"}}
B --> C["find_agy()<br/>$AGY_PATH โ PATH โ OS defaults"]
C --> D{"sys.platform?"}
D -->|"win32"| E["pywinpty<br/>PtyProcess.spawn"]
D -->|"posix"| F["stdlib pty<br/>os.openpty + Popen"]
E --> G(["fresh pseudo-terminal"])
F --> G
G --> H["agy -p prompt<br/>isatty == True โ emits"]
H -->|"raw bytes + ANSI/TUI chrome"| I["clean()<br/>strip CSI/OSC ยท collapse \r repaints ยท drop spinner glyphs"]
I -->|"clean text"| A
| Platform | pty backend | Status |
|---|---|---|
| Windows | ConPTY via pywinpty (PtyProcess) |
โ verified (agy 1.0.6) |
| Linux / macOS | stdlib pty (os.openpty + subprocess.Popen) |
๐งช pty mechanics verified on Linux CI (stub-driven); real agy round-trip untested on hardware โ report results here |
[!TIP] POSIX (macOS & Linux) users wanted. The pty mechanics are verified on Linux CI, but the real
agyround-trip on POSIX hasn't been run on hardware. If you're on macOS/Linux:pip install agy-headless-bridge, try it, and tell us how it went โ pass or fail. PRs welcome.
Why not just the existing
agyClaude Code plugins? They wrapagyfor triggering (slash commands, model selection) but still callagy -pdirectly โ so in any headless context they hit this exact empty-output bug. This package fixes the I/O layer they're missing. Use both together.
Prerequisites
Before installing this bridge you need:
- Python 3.9+ โ
python --version. - The Antigravity CLI (
agy), installed and authenticated:- Install: https://antigravity.google/cli
- Authenticate once interactively (
agyopens a browser OAuth flow), or setANTIGRAVITY_API_KEYin your environment if you use an API key. - Verify it runs in a real terminal:
agy -p "say hi"should print a reply. (From a pipe it won't โ that's the very bug this package fixes.)
- Windows only:
pywinpty(installed automatically as a dependency). POSIX uses the stdlibptymodule โ nothing extra.
This package does not install or authenticate
agy, and does not bundle any credentials. It only spawns theagyalready on your machine.
Install
Requires Python 3.9+.
pip install agy-headless-bridge # pywinpty auto-installs on Windows only
From source:
git clone https://github.com/rhishi99/agy-headless-bridge
cd agy-headless-bridge
pip install -e .
The bridge locates the binary via, in order: $AGY_PATH โ agy on PATH โ
OS default install paths.
Usage
Library
from agy_headless_bridge import run, AgyNotFoundError
try:
print(run("reply with exactly: OK", timeout=60))
except AgyNotFoundError:
print("install agy first")
run(prompt, timeout=180, agy_path=None) -> str โ raises AgyNotFoundError if
the binary is missing, TimeoutError on timeout, ValueError on empty prompt.
Returns "" only if agy genuinely emitted nothing.
CLI
agy-bridge "reply with exactly: OK"
python -m agy_headless_bridge "reply with exactly: OK" # equivalent
MCP server
claude mcp add --transport stdio antigravity -- \
python -m agy_headless_bridge.mcp_server
The server speaks JSON-RPC stdio directly (no MCP SDK dependency) and routes every call through the pty bridge.
Tool schema (what an agent โ or you, integrating manually โ sees):
| Tool | Argument | Type | Required | Description |
|---|---|---|---|---|
agy_ask |
prompt |
string | โ | one-shot prompt sent to agy |
agy_research |
query |
string | โ | wrapped as a deep-research prompt for agy |
Response shape โ a standard MCP tools/call result; the answer is the text
content:
{
"jsonrpc": "2.0",
"id": 2,
"result": { "content": [ { "type": "text", "text": "<agy's cleaned answer>" } ] }
}
On failure the text is an [agy-mcp] ERROR: ... string (agy missing, timeout,
etc.) rather than a JSON-RPC error, so the agent always gets a readable reply.
Use cases & wiring it into your AI coding tools
The whole point: let one AI coding tool delegate work to Gemini via Antigravity, headlessly. Common setups:
| Use case | How |
|---|---|
| Claude Code asks Gemini for a second opinion / diff review | MCP server โ agy_ask tool |
A CI step runs an agy prompt and captures the answer |
agy-bridge "..." in the workflow |
| A Python pipeline fans work out to agy | from agy_headless_bridge import run |
| Codex / any MCP-capable agent delegates to agy | register the same MCP server |
| Cron / scheduled job summarizes logs via agy | agy-bridge in the script |
Wire into Claude Code
Register the MCP server, then prompt Claude to use it:
claude mcp add --transport stdio antigravity -- \
python -m agy_headless_bridge.mcp_server
Prompt to Claude Code: "Use the
agy_asktool to ask Antigravity to review this function for edge cases, then summarize its findings for me."
If you also want slash-command triggering and model selection, pair this bridge
with the community antigravity-cc Claude Code plugin โ that handles the
/agy:* commands and Gemini/Claude model swap; this handles the headless I/O.
Wire into Codex (or any MCP client)
Add the server to the client's MCP config:
{
"mcpServers": {
"antigravity": {
"command": "python",
"args": ["-m", "agy_headless_bridge.mcp_server"]
}
}
}
Prompt to the agent: "Call
agy_researchwith the query 'idiomatic error handling in Rust' and turn the result into a checklist."
Use from a shell / CI script
ANSWER="$(agy-bridge 'Summarize the key risk in this diff in one sentence.')"
echo "$ANSWER"
Configuration
| Env var | Default | Meaning |
|---|---|---|
AGY_PATH |
auto-detect | Absolute path to the agy binary |
AGY_BRIDGE_TIMEOUT |
180 |
Seconds before a call is killed |
How clean() works
agy's pty output is a TUI stream, not plain text. clean() removes ANSI
escapes (CSI/OSC โ colors, cursor moves), \r repaints (a spinner
overwrites one line; only the final paint is kept), and box-drawing / spinner
glyphs (โญโโฎ โ โ โ โ น) โ leaving just the model's answer.
What comes off the pty vs. what you get back:
RAW (off the pty) CLEANED (returned to you)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ
โ thinkingโฆ\rโ thinkingโฆ\r\x1b[2K A closure is a function that
\x1b[32mโญโโโโโโโโโโโโโโฎ\x1b[0m captures variables from the
\x1b[32mโ\x1b[0m A closure is a function scope where it was defined.
that captures variables from the
scope where it was defined.
\x1b[32mโฐโโโโโโโโโโโโโโฏ\x1b[0m
Troubleshooting / FAQ
pip install fails on Windows building pywinpty โ pywinpty is a native
extension. If pip tries to build from source and errors with a compiler/cl.exe
message, install the Microsoft C++ Build Tools (or use a Python where a
prebuilt pywinpty wheel exists โ recent CPython on Windows has them). Upgrade
pip first: python -m pip install -U pip.
AgyNotFoundError โ the bridge can't find agy. Set AGY_PATH to the
absolute path of the binary, or make sure agy is on your PATH
(agy --version should work in your shell).
Empty string returned โ agy produced no output. Confirm it works in a real
terminal first: agy -p "say hi". If that's also empty, the problem is agy/auth,
not the bridge. Re-authenticate (agy interactively) or check
ANTIGRAVITY_API_KEY.
TimeoutError โ the call exceeded AGY_BRIDGE_TIMEOUT (default 180s). Raise
it for long prompts: AGY_BRIDGE_TIMEOUT=600 agy-bridge "..." or
run(prompt, timeout=600).
Pseudo-terminal allocation fails โ rare. On Windows it means pywinpty
isn't importable (reinstall it). On POSIX it means the system is out of pty
slots or pty.openpty() is denied (containers with no /dev/pts); run with a
real pty available.
Garbled / partial output โ open an
issue with the OS,
Python + agy version, and the raw output; clean() may need another glyph rule.
Development & CI
pip install -e ".[dev]"
pytest
Unit tests (cleaning, arg validation, binary discovery) always run. The live
agy round-trip test auto-skips when agy isn't installed โ so CI runners
(which don't have agy) stay green and never need credentials. CI runs on
Windows + Linux across Python 3.9 and 3.12.
Scope, non-goals & disclaimer
- Model selection (Gemini Pro / Flash / Claude inside agy) is not handled
here โ it's an
agysettings.jsonconcern, covered by theantigravity-ccplugin. Pair the two. - Does not install or authenticate
agy, and ships no credentials. - Automating any vendor CLI may interact with that vendor's terms / rate limits.
You are responsible for using
agywithin Google's terms of service. This project only changes how stdout is captured โ it does not bypass auth, quotas, or any access control. - Not affiliated with Google. Antigravity and agy are Google products.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agy_headless_bridge-1.0.1.tar.gz.
File metadata
- Download URL: agy_headless_bridge-1.0.1.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ca32865412058146cc537454233501ceb3859ecf3029717691661f14f96d68cc
|
|
| MD5 |
9e52537286ea26c2faff22f0c96b99cd
|
|
| BLAKE2b-256 |
6874c5fe086f00fffd198c39a6a52447c608d793a3c589a2ef1bc8890faf37b0
|
Provenance
The following attestation bundles were made for agy_headless_bridge-1.0.1.tar.gz:
Publisher:
publish.yml on rhishi99/agy-headless-bridge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agy_headless_bridge-1.0.1.tar.gz -
Subject digest:
ca32865412058146cc537454233501ceb3859ecf3029717691661f14f96d68cc - Sigstore transparency entry: 1808909551
- Sigstore integration time:
-
Permalink:
rhishi99/agy-headless-bridge@aaddb6038fc5591a8cea8fc67f5e5f97d1bb045e -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/rhishi99
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aaddb6038fc5591a8cea8fc67f5e5f97d1bb045e -
Trigger Event:
release
-
Statement type:
File details
Details for the file agy_headless_bridge-1.0.1-py3-none-any.whl.
File metadata
- Download URL: agy_headless_bridge-1.0.1-py3-none-any.whl
- Upload date:
- Size: 14.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
989aff6ec617a21a1469cb6b1f3ba1010e7d494e9d8b8788bc9e93d550d18b23
|
|
| MD5 |
41924487be52e3dfa3d86df4a67888bc
|
|
| BLAKE2b-256 |
b26d17884dbc92db8aa47d2be5def70d376bea4d648788293e968767fa58354b
|
Provenance
The following attestation bundles were made for agy_headless_bridge-1.0.1-py3-none-any.whl:
Publisher:
publish.yml on rhishi99/agy-headless-bridge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agy_headless_bridge-1.0.1-py3-none-any.whl -
Subject digest:
989aff6ec617a21a1469cb6b1f3ba1010e7d494e9d8b8788bc9e93d550d18b23 - Sigstore transparency entry: 1808909639
- Sigstore integration time:
-
Permalink:
rhishi99/agy-headless-bridge@aaddb6038fc5591a8cea8fc67f5e5f97d1bb045e -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/rhishi99
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aaddb6038fc5591a8cea8fc67f5e5f97d1bb045e -
Trigger Event:
release
-
Statement type: