Skip to main content

devboy_core/agents/
antigravity.rs

1//! Antigravity (Google IDE-agent) detector.
2//!
3//! Antigravity shares storage with Gemini CLI but is a separate product
4//! (`antigravity.google`). State lives at:
5//! ```text
6//! ~/.gemini/antigravity/
7//! ├── conversations/<uuid>.pb     ← Protobuf — one per conversation
8//! ├── code_tracker/{active,history}/
9//! ├── brain/, implicit/, context_state/
10//! ├── installation_id, mcp_config.json, user_settings.pb
11//! └── browserAllowlist.txt
12//! ```
13//!
14//! Sessions = count of `*.pb` files under `conversations/`.
15//! last_used = max mtime of those files.
16
17use std::path::Path;
18
19use super::fs_util::{dir_nonempty, max_mtime_in};
20use super::{AgentDetector, AgentSnapshot, InstallStatus};
21
22const ID: &str = "antigravity";
23const DISPLAY_NAME: &str = "Antigravity";
24
25pub struct AntigravityDetector;
26
27impl AgentDetector for AntigravityDetector {
28    fn id(&self) -> &'static str {
29        ID
30    }
31    fn display_name(&self) -> &'static str {
32        DISPLAY_NAME
33    }
34
35    fn detect(&self, home: &Path) -> AgentSnapshot {
36        let dir = home.join(".gemini/antigravity");
37        let conversations = dir.join("conversations");
38        let paths_checked = vec![dir.clone(), conversations.clone()];
39
40        let installed = dir.is_dir() && dir_nonempty(&dir);
41
42        if !installed {
43            return AgentSnapshot {
44                id: ID,
45                display_name: DISPLAY_NAME,
46                status: InstallStatus::No,
47                sessions: None,
48                last_used: None,
49                score: 0.0,
50                paths_checked,
51            };
52        }
53
54        let count = count_pb_files(&conversations);
55        let last_used = max_mtime_in(&conversations, |p| p.extension().is_some_and(|e| e == "pb"));
56
57        AgentSnapshot {
58            id: ID,
59            display_name: DISPLAY_NAME,
60            status: InstallStatus::Yes,
61            sessions: (count > 0).then_some(count),
62            last_used,
63            score: 0.0,
64            paths_checked,
65        }
66    }
67}
68
69fn count_pb_files(dir: &Path) -> u64 {
70    let Ok(entries) = std::fs::read_dir(dir) else {
71        return 0;
72    };
73    entries
74        .flatten()
75        .filter(|e| e.path().extension().is_some_and(|e| e == "pb"))
76        .count() as u64
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use std::fs;
83    use tempfile::tempdir;
84
85    #[test]
86    fn counts_pb_conversations() {
87        let home = tempdir().unwrap();
88        let conv = home.path().join(".gemini/antigravity/conversations");
89        fs::create_dir_all(&conv).unwrap();
90        fs::write(conv.join("aaaa.pb"), b"\x00").unwrap();
91        fs::write(conv.join("bbbb.pb"), b"\x00").unwrap();
92        // non-.pb file should be ignored
93        fs::write(conv.join("readme.txt"), b"x").unwrap();
94
95        let snap = AntigravityDetector.detect(home.path());
96        assert_eq!(snap.status, InstallStatus::Yes);
97        assert_eq!(snap.sessions, Some(2));
98    }
99
100    #[test]
101    fn empty_antigravity_dir_means_not_installed() {
102        let home = tempdir().unwrap();
103        // Directory doesn't exist at all.
104        let snap = AntigravityDetector.detect(home.path());
105        assert_eq!(snap.status, InstallStatus::No);
106    }
107
108    #[test]
109    fn antigravity_empty_present_dir_means_not_installed() {
110        let home = tempdir().unwrap();
111        fs::create_dir_all(home.path().join(".gemini/antigravity")).unwrap();
112        let snap = AntigravityDetector.detect(home.path());
113        assert_eq!(snap.status, InstallStatus::No);
114    }
115
116    #[test]
117    fn antigravity_dir_with_only_marker_file_still_installed_no_sessions() {
118        let home = tempdir().unwrap();
119        let dir = home.path().join(".gemini/antigravity");
120        fs::create_dir_all(&dir).unwrap();
121        fs::write(dir.join("installation_id"), b"abc").unwrap();
122        let snap = AntigravityDetector.detect(home.path());
123        assert_eq!(snap.status, InstallStatus::Yes);
124        assert!(snap.sessions.is_none());
125    }
126}