Skip to content

Add Antigravity CLI provider#222

Open
gregce wants to merge 3 commits into
devfrom
feat/antigravity-cli-provider
Open

Add Antigravity CLI provider#222
gregce wants to merge 3 commits into
devfrom
feat/antigravity-cli-provider

Conversation

@gregce

@gregce gregce commented May 26, 2026

Copy link
Copy Markdown
Contributor

cc @belucid 👋

What this is (the human version)

Google just shipped Antigravity CLI — the terminal coding agent that replaces Gemini CLI, invoked as the agy binary (docs). This PR teaches the SpecStory CLI to capture those sessions, so anything you do with agy shows up as clean markdown history (and syncs to SpecStory Cloud) exactly like Claude Code, Cursor CLI, Codex CLI, Gemini CLI, Droid CLI, and DeepSeek TUI already do.

In other words: specstory sync antigravity now works. Run Antigravity in your project, then sync, and you get a readable transcript of your prompts, the model's reasoning, every tool call, and the results.

How it works

Antigravity stores each conversation as an append-only JSONL transcript buried at:

~/.gemini/antigravity-cli/brain/<id>/.system_generated/logs/transcript_full.jsonl

The new antigravity provider parses those step-by-step transcripts, groups them into conversation turns, stitches each tool result back onto the tool call that produced it, and renders Antigravity's tools (run_command, view_file, write_to_file, replace_file_content, grep_search, list_dir, …) into the unified SpecStory markdown format.

The interesting/tricky bits

A few things made Antigravity different from every provider we've added so far — worth a look in review:

  • Binary is agy (not antigravity); version via agy --version, resume via --conversation <id>.
  • Tool results are separate transcript steps with no call→result id, so results are matched to their originating tool call positionally (FIFO). Async/long-running command output (which lands in a separate tasks/task-N.log) is folded back in too.
  • Transcripts carry no workspace field, and history.jsonl only logs interactive sessions (not agy -p print mode). So the workspace is taken from history when available and otherwise inferred from the absolute paths the tools touched. Text-only sessions with no recoverable workspace are returned only on explicit -s <id> requests, never in project-filtered bulk listings (so they can't pollute unrelated projects).
  • The watcher establishes fsnotify watches level-by-level as that deeply-nested transcript directory gets created, since fsnotify isn't recursive.

The full reverse-engineered on-disk format is documented in docs/antigravity-format-spec.md for future maintainers.

What's included

  • New pkg/providers/antigravitycli/ package (modeled on the deepseektui provider), with a test file per source file (~70 cases).
  • Registered in the provider factory; antigravity_cmd config support.
  • docs/antigravity-format-spec.md, plus README + changelog updates.

Testing

  • Built and verified end-to-end against real captured agy sessions (interactive run_command, file edits/reads/search, multi-turn continuations, failed commands, and text-only sessions) via specstory sync antigravity -s <id> --print.
  • specstory check antigravity detects the real binary (v1.0.2).
  • go build ./..., go vet ./..., golangci-lint run ./... (0 issues), and go test ./... (all pass) are green.

🤖 Generated with Claude Code

Add a new `antigravity` provider that converts Antigravity CLI (`agy`)
sessions into SpecStory's unified session format, following the same flow
as the existing providers (Claude Code, Cursor CLI, Codex CLI, Gemini CLI,
Droid CLI, DeepSeek TUI).

Antigravity stores each conversation as an append-only JSONL transcript at
~/.gemini/antigravity-cli/brain/<id>/.system_generated/logs/transcript_full.jsonl.
The package parses those step-based transcripts, groups them into exchanges,
attaches positionally-correlated tool results (including async task output),
and renders Antigravity's tools (run_command, view_file, write_to_file,
replace_file_content, grep_search, list_dir, ...) to markdown.

Notable provider-specific handling:
- Binary is `agy`; version via `agy --version`; resume via `--conversation`.
- Tool results are separate transcript steps with no call->result id, so
  results are matched to their tool call by order (FIFO).
- Transcripts carry no workspace field and history.jsonl only covers
  interactive sessions, so the workspace is resolved from history when
  available and otherwise inferred from tool-arg paths. Unscoped text-only
  sessions are returned only on explicit by-id retrieval, never in
  project-filtered bulk listings.
- The watcher establishes fsnotify watches level-by-level as the deeply
  nested transcript directory is created (fsnotify is non-recursive).

Also: register the provider in the factory, add antigravity_cmd config
support, document the reverse-engineered on-disk format in
docs/antigravity-format-spec.md, and update the README and changelog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 26, 2026 22:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new Antigravity CLI provider to SpecStory CLI so sessions created by the agy binary can be detected, parsed from Antigravity’s on-disk JSONL transcripts, rendered into SpecStory’s unified markdown/session schema, and watched live for updates. This extends the existing provider ecosystem (Claude Code, Cursor CLI, Codex CLI, Gemini CLI, Droid CLI, DeepSeek TUI) with a new JSONL-based source under ~/.gemini/antigravity-cli/.

Changes:

  • Register new antigravity provider in the SPI factory and add antigravity_cmd config support (README + config template + config accessors/tests).
  • Add pkg/providers/antigravitycli/ implementation: transcript parsing, workspace inference, tool rendering, agy execution/resume handling, and fsnotify-based watcher for nested transcript paths.
  • Add extensive unit tests for the new provider components, plus a reverse-engineered on-disk format spec document.

3 most important improvements:

  1. Watcher safety/robustness: processTranscriptFile should stop early when resolveTranscriptPath() returns ("", nil) to avoid parsing unrelated transcript*.jsonl files while temporarily watching an ancestor directory (this can lead to incorrect parsing/emission).
  2. Prompt-cleaning correctness: tighten metadataBlockRe so it only removes correctly paired tags (use a backreference or separate regexes) to avoid accidental deletion when mixed blocks appear.
  3. Operational/performance consideration (optional): consider streaming transcript parsing (scanner/decoder) instead of os.ReadFile + bytes.Split for very large transcripts/tool outputs to reduce peak memory usage during sync/watch scans.

Unit test opportunities (targeted):

  • Add a watcher test ensuring processTranscriptFile does not parse event.Name when resolveTranscriptPath(conversationID) indicates the conversation has no usable transcript in the expected Antigravity directory layout.
  • Add a prompt-cleaning test case demonstrating that mismatched/mixed metadata tags don’t cause unintended content removal once the regex is fixed.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
specstory-cli/README.md Document Antigravity support and add antigravity_cmd config option.
specstory-cli/pkg/spi/factory/registry.go Register the new antigravity provider in the provider registry.
specstory-cli/pkg/providers/antigravitycli/watcher.go fsnotify watcher for Antigravity’s nested transcript directories + dispatch.
specstory-cli/pkg/providers/antigravitycli/watcher_test.go Tests for watcher helpers and modtime dedupe behavior.
specstory-cli/pkg/providers/antigravitycli/transcript_parser.go Parse transcript JSONL + history index + async task logs; build metadata.
specstory-cli/pkg/providers/antigravitycli/transcript_parser_test.go Tests for transcript/history/task parsing and metadata extraction.
specstory-cli/pkg/providers/antigravitycli/provider.go Provider implementation: check/detect/list/get/watch/exec integration.
specstory-cli/pkg/providers/antigravitycli/provider_test.go Provider behavior tests (check helpers, detect/get/list/watch).
specstory-cli/pkg/providers/antigravitycli/path_utils.go Resolve Antigravity disk layout paths and list/locate transcripts.
specstory-cli/pkg/providers/antigravitycli/path_utils_test.go Tests for path resolution, transcript listing, and path containment.
specstory-cli/pkg/providers/antigravitycli/markdown_tools.go Tool classification + input/output formatting + path hint extraction.
specstory-cli/pkg/providers/antigravitycli/markdown_tools_test.go Tests for tool classification/formatting and path hint extraction.
specstory-cli/pkg/providers/antigravitycli/antigravity_exec.go Launch agy, parse custom command, and inject resume args safely.
specstory-cli/pkg/providers/antigravitycli/antigravity_exec_test.go Tests for command parsing, tilde expansion, and resume arg handling.
specstory-cli/pkg/providers/antigravitycli/agent_session.go Convert transcript steps into unified SessionData/Exchanges/Tools schema.
specstory-cli/pkg/providers/antigravitycli/agent_session_test.go Tests for exchange grouping, tool result attachment, workspace inference.
specstory-cli/pkg/config/config.go Add antigravity_cmd to providers config and expose via GetProviderCmd.
specstory-cli/pkg/config/config_test.go Extend config tests to cover deepseek + antigravity provider commands.
specstory-cli/docs/antigravity-format-spec.md Reverse-engineered Antigravity on-disk format spec for maintainers.
specstory-cli/changelog.md Changelog entry for Antigravity CLI support.

Comment on lines +191 to +196
if preferredPath, err := resolveTranscriptPath(conversationID); err == nil && preferredPath != "" {
transcriptPath = preferredPath
} else if err != nil {
slog.Debug("antigravity: unable to resolve preferred transcript", "conversationId", conversationID, "error", err)
return
}
// <ADDITIONAL_METADATA> / <USER_SETTINGS_CHANGE> blocks that must not be shown.
var (
userRequestRe = regexp.MustCompile(`(?s)<USER_REQUEST>(.*?)</USER_REQUEST>`)
metadataBlockRe = regexp.MustCompile(`(?s)<(ADDITIONAL_METADATA|USER_SETTINGS_CHANGE|SYSTEM_MESSAGE)>.*?</(ADDITIONAL_METADATA|USER_SETTINGS_CHANGE|SYSTEM_MESSAGE)>`)
Copilot AI review requested due to automatic review settings May 26, 2026 23:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comment on lines +492 to +505
func stripFileURI(value string) string {
trimmed := strings.TrimSpace(value)
if !strings.HasPrefix(trimmed, "file:") {
return trimmed
}
parsed, err := url.Parse(trimmed)
if err == nil && parsed.Scheme == "file" {
if decoded, err := url.PathUnescape(parsed.Path); err == nil {
return decoded
}
return parsed.Path
}
return strings.TrimPrefix(trimmed, "file://")
}
Comment on lines +16 to +55
// Unknown tools fall back to "generic" (not "unknown") so that tools we know the
// provider can emit still render sensibly.
func classifyToolType(name string) string {
writeTools := map[string]bool{
"write_to_file": true, "replace_file_content": true,
"create_file": true, "edit_file": true, "apply_patch": true,
}
readTools := map[string]bool{
"view_file": true, "read_file": true,
"list_dir": true, "read_directory": true,
}
searchTools := map[string]bool{
"grep_search": true, "codebase_search": true, "file_search": true,
"find_files": true, "web_search": true, "read_url": true,
"fetch_url": true, "execute_url": true,
}
shellTools := map[string]bool{
"run_command": true, "run_terminal_command": true,
"exec": true, "shell": true,
}
taskTools := map[string]bool{
"update_plan": true, "todo_write": true, "create_plan": true,
"task_list": true,
}

switch {
case writeTools[name]:
return schema.ToolTypeWrite
case readTools[name]:
return schema.ToolTypeRead
case searchTools[name]:
return schema.ToolTypeSearch
case shellTools[name]:
return schema.ToolTypeShell
case taskTools[name]:
return schema.ToolTypeTask
default:
return schema.ToolTypeGeneric
}
}
Comment on lines +40 to +41
// maxTranscriptLineSize bounds JSONL sidecar scanning well above bufio's 64KB
// default while still placing a hard cap on malformed lines.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants