A Unix-style Go CLI that extracts decrypted transcripts from
Google Antigravity CLI sessions by talking to the
local language-server daemon Antigravity runs while agy is active. Encrypted
conversation .pb session files live under
~/.gemini/antigravity-cli/conversations/; agy-reader fetches the
decrypted JSON from the daemon, renders Markdown for humans, and writes a
<uuid>.trajectory.json sidecar next to each .pb file for downstream tools.
By default, the daemon binds a different ephemeral port each agy session.
agy-reader automatically discovers this port on the fly (via the session log
files), meaning no manual configuration is required on the happy path.
The sister project agentsview is a
local web viewer for AI agent sessions. It can list Antigravity CLI sessions but
cannot render assistant turns because they're AES-GCM encrypted at rest.
agy-reader fills that gap by producing a plain JSON sidecar that agentsview will
detect and parse.
Integration contract is the file format. No imports, no protocol, no
coupling — just <uuid>.trajectory.json sitting next to <uuid>.pb.
- Port auto-discovery: parses
cli.logto discover and verify the daemon's ephemeral HTTP port; no manual configuration on the happy path. - Rich transcript formatting: renders
CodeActionsteps asgit-style diffs and converts file URI paths into clickable local links so you can jump into your IDE. - Sidecar contract with agentsview:
every render also writes
<uuid>.trajectory.jsonnext to the source.pbfor downstream tools to pick up.
go install github.com/mjacobs/agy-reader/cmd/agy-reader@latestThis drops agy-reader into $(go env GOBIN) (or $(go env GOPATH)/bin); make
sure that directory is on your PATH.
To build from a local checkout instead:
go build ./cmd/agy-readerList syncable conversation sessions (does not contact the daemon):
agy-reader --listAntigravity also writes background implicit/ trajectory files. They are not
ordinary chat transcripts and are not syncable through the current daemon API,
so they are hidden by default. To inspect them while debugging:
agy-reader --list --include-implicitPick a cascade id from that list and render it to stdout as Markdown:
agy-reader <cascade-id>Render and save:
agy-reader --format md --out transcript.md <cascade-id>
agy-reader --format json --out trajectory.json <cascade-id>Sync the sidecar without printing anything (for agentsview consumption):
agy-reader --sync <cascade-id>Even in the default (Markdown) mode, agy-reader writes the sidecar
<uuid>.trajectory.json next to the source .pb whenever it can — that's the
point of the contract.
For every syncable ~/.gemini/antigravity-cli/conversations/<uuid>.pb,
agy-reader writes <uuid>.trajectory.json in the same directory. The contents
are the raw GetCascadeTrajectory response from the Antigravity daemon — no
schema invented on top. agentsview is expected to ignore unknown step types and
use metadata.createdAt for timestamps.
agy-reader --watch # 30s interval (default)
agy-reader --watch --watch-interval=10s # custom intervalPolls the session root, fetches a trajectory for any conversations/*.pb whose
sidecar is missing or older than the .pb file, and writes the sidecar
atomically. Daemon errors are non-fatal — connection-refused logs once per
failure streak and the loop retries on the next tick. SIGINT or SIGTERM drains
in-flight work and exits cleanly.
Auto-discovery failed and ANTIGRAVITY_DAEMON_URL is not set
By default, agy-reader scans cli.log inside the session root directory
(~/.gemini/antigravity-cli/ or $ANTIGRAVITY_CLI_ROOT) to locate the active
HTTP port.
If the log file is missing, empty, or the server is unresponsive, auto-discovery will fail. You can troubleshoot by:
- Ensuring
agyis running, as the daemon is only active during an active session. - Manually overriding the port if necessary. Find the port using:
The HTTP JSON-RPC endpoint is typically the lower-numbered port. Export it manually:
ss -tlnp 2>/dev/null | grep agy # Linux lsof -iTCP -sTCP:LISTEN -anP | grep agy # macOS
export ANTIGRAVITY_DAEMON_URL=http://127.0.0.1:<port>
connection refused
The daemon only listens while agy (Antigravity CLI) is running, and the port
changes each time agy restarts. Ensure agy is running. If you are manually
specifying ANTIGRAVITY_DAEMON_URL, remember to update the port to match the
new session.
daemon error 5xx or unknown cascade id
The daemon only knows about sessions it has loaded. Calling LoadTrajectory
first (which agy-reader does automatically) usually solves this, but the
daemon will refuse if the session truly doesn't exist or its key is unavailable.
The current daemon LoadTrajectory request accepts only a cascade ID and resolves
it under conversations/; implicit/ files are hidden by default and are not
synced by watch mode. Use --list --include-implicit only when debugging those
unsupported background traces.
no sessions found
Set ANTIGRAVITY_CLI_ROOT if your sessions are not at the default
~/.gemini/antigravity-cli.
| Env var | Purpose | Default |
|---|---|---|
ANTIGRAVITY_DAEMON_URL |
Daemon base URL override (optional, auto-detected by default) | unset (optional fallback) |
ANTIGRAVITY_CLI_ROOT |
Override session root dir | ~/.gemini/antigravity-cli |
AGY_READER_LIVE |
Enable live daemon smoke test | unset (test skips) |
AGY_READER_TEST_UUID |
Cascade id to use in the live test | unset |
agy-reader does not ship a daemon installer. --watch is a long-running
loop, so use whatever process manager you already use to keep it alive — the
examples below are starting points, not installation instructions.
~/.config/systemd/user/agy-reader.service:
[Unit]
Description=agy-reader sync loop
After=default.target
[Service]
Type=simple
ExecStart=%h/.local/bin/agy-reader --watch
Restart=on-failure
RestartSec=30
[Install]
WantedBy=default.targetEnable with systemctl --user enable --now agy-reader.
~/Library/LaunchAgents/dev.mjacobs.agy-reader.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.mjacobs.agy-reader</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/agy-reader</string>
<string>--watch</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/agy-reader.out</string>
<key>StandardErrorPath</key>
<string>/tmp/agy-reader.err</string>
</dict>
</plist>Load with
launchctl load -w ~/Library/LaunchAgents/dev.mjacobs.agy-reader.plist.
go test ./...The daemon smoke test is gated. To exercise it against a live agy:
AGY_READER_LIVE=1 AGY_READER_TEST_UUID=<some-cascade-id> go test ./internal/daemonActive development. Currently supports:
- Automatic daemon port discovery via
cli.log. - Single-shot fetch and render.
- Continuous polling sync via
--watch. - Interactive formatting (clickable file links, code diffs, clean text layouts).
Offline decryption (direct binary RE) is planned as a future goal.
MIT