Skip to content

Commit 3512db0

Browse files
committed
Remove Gemini CLI, make antigravity fixes
1 parent 2c8f740 commit 3512db0

7 files changed

Lines changed: 38 additions & 238 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ release/
1717

1818
*.md
1919
!README.md
20-
.DS_Store
20+
.DS_Store
21+
tmp.bat

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@ Native desktop GUI for managing AI coding-agent configuration across providers.
99
- **Scope Switching** — project-level vs global configuration, with workspace browser.
1010
- **Diff Workbench** — compare project and global configs with stable, secret-safe fingerprints. Detects duplicates, missing targets, and scope conflicts.
1111
- **Hook Cockpit** — static hook inventory showing event, matcher, handler, blocking risk, timeout, duplicates, and project/global overlaps.
12-
- **Chat Manager** — unified chat history browser across Claude Code, Codex CLI, Gemini CLI, Antigravity CLI, and Kiro. Search, export (single JSON or multi-chat ZIP), soft-delete with Trash, and import archived sessions.
12+
- **Chat Manager** — unified chat history browser across Claude Code, Codex CLI, and Kiro. Search, export (single JSON or multi-chat ZIP), soft-delete with Trash, and import archived sessions.
1313
- **Inline Editor** — edit instruction files, rules, and steering docs without leaving the app.
1414
- **JSON Backups** — automatic `.bak` creation before any config mutation.
1515
- **Cross-platform** — Windows, Linux, and macOS builds.
1616

17+
**NOTE:** Antigravity CLI chats are encrypted, so chat management is not possible (atleast 'easily'). However the CLI itself helps to manage chats and MCPs well.
18+
1719
## Supported Providers
1820

1921
| Provider | Instruction File | Skills | Hooks | MCP | Other |
2022
|---|---|---|---|---|---|
2123
| Claude Code | `CLAUDE.md` | `.claude/skills/` | `settings.json` | `settings.json` | Rules |
2224
| Codex CLI | `AGENTS.md` | `.codex/skills/`, `.agents/skills/` | `config.toml`, `hooks.json` | `config.toml`, `.mcp.json` ||
23-
| Gemini CLI | `GEMINI.md`, `AGENTS.md` | `.gemini/skills/` | `settings.json` | `settings.json` | Rules |
24-
| Antigravity CLI | `GEMINI.md`, `AGENTS.md` | `.agents/skills/` | `settings.json` | `mcp_config.json` ||
25+
| Antigravity CLI | `GEMINI.md`, `AGENTS.md` | `~/.gemini/skills/` ||||
2526
| Kiro ||| Agent JSON | `settings/mcp.json` | Steering, Specs, Agents |
2627
| OpenCode | `AGENTS.md` | `.opencode/skills/` | Plugins | `opencode.json` | Agents |
2728

src/app.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -616,14 +616,15 @@ fn instruction_files(provider: ProviderId, root: &Path, dir: &Path, scope: Scope
616616
(ProviderId::Claude, Scope::Global) => vec![dir.join("CLAUDE.md")],
617617
(ProviderId::Codex, Scope::Project) => vec![root.join("AGENTS.md")],
618618
(ProviderId::Codex, Scope::Global) => vec![dir.join("AGENTS.md")],
619-
(ProviderId::Gemini, Scope::Project) => {
620-
vec![root.join("GEMINI.md"), root.join("AGENTS.md")]
621-
}
622-
(ProviderId::Gemini, Scope::Global) => vec![dir.join("GEMINI.md")],
619+
623620
(ProviderId::Antigravity, Scope::Project) => {
624621
vec![root.join("GEMINI.md"), root.join("AGENTS.md")]
625622
}
626-
(ProviderId::Antigravity, Scope::Global) => vec![dir.join("GEMINI.md")],
623+
(ProviderId::Antigravity, Scope::Global) => {
624+
let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("."));
625+
let gemini_dir = home.join(".gemini");
626+
vec![gemini_dir.join("GEMINI.md"), gemini_dir.join("AGENTS.md")]
627+
}
627628
(ProviderId::Kiro, Scope::Project) => {
628629
vec![root.join(".kiro").join("steering").join("instructions.md")]
629630
}

src/chat.rs

Lines changed: 4 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const ARCHIVE_EXT: &str = "agentswitch-chat.json";
2020
pub enum ChatProvider {
2121
Claude,
2222
Codex,
23-
Gemini,
2423
Antigravity,
2524
Kiro,
2625
}
@@ -30,7 +29,6 @@ impl ChatProvider {
3029
match self {
3130
Self::Claude => "Claude Code",
3231
Self::Codex => "Codex CLI",
33-
Self::Gemini => "Gemini CLI",
3432
Self::Antigravity => "Antigravity CLI",
3533
Self::Kiro => "Kiro",
3634
}
@@ -40,7 +38,6 @@ impl ChatProvider {
4038
match self {
4139
Self::Claude => "claude",
4240
Self::Codex => "codex",
43-
Self::Gemini => "gemini",
4441
Self::Antigravity => "antigravity",
4542
Self::Kiro => "kiro",
4643
}
@@ -50,7 +47,6 @@ impl ChatProvider {
5047
match self {
5148
Self::Claude => ProviderId::Claude.color(),
5249
Self::Codex => ProviderId::Codex.color(),
53-
Self::Gemini => ProviderId::Gemini.color(),
5450
Self::Antigravity => ProviderId::Antigravity.color(),
5551
Self::Kiro => ProviderId::Kiro.color(),
5652
}
@@ -163,8 +159,6 @@ pub fn scan_all(workspace: &Path) -> Vec<ChatSession> {
163159
let mut sessions = Vec::new();
164160
sessions.extend(scan_claude());
165161
sessions.extend(scan_codex());
166-
sessions.extend(scan_gemini());
167-
sessions.extend(scan_antigravity());
168162
sessions.extend(scan_kiro(workspace));
169163
sessions.extend(scan_imported());
170164
sessions.sort_by(|a, b| {
@@ -594,115 +588,6 @@ fn scan_codex() -> Vec<ChatSession> {
594588
out
595589
}
596590

597-
fn scan_gemini() -> Vec<ChatSession> {
598-
let Some(home) = dirs::home_dir() else {
599-
return vec![];
600-
};
601-
let tmp = env::var("GEMINI_DIR")
602-
.map(PathBuf::from)
603-
.unwrap_or_else(|_| home.join(".gemini"))
604-
.join("tmp");
605-
if !tmp.is_dir() {
606-
return vec![];
607-
}
608-
let mut out = Vec::new();
609-
let Ok(projects) = fs::read_dir(&tmp) else {
610-
return out;
611-
};
612-
for project in projects.flatten().filter(|e| e.path().is_dir()) {
613-
let chats = project.path().join("chats");
614-
if let Ok(entries) = fs::read_dir(&chats) {
615-
for entry in entries.flatten() {
616-
let path = entry.path();
617-
if path.is_file() && is_gemini_session_file(&path) {
618-
if let Ok(session) = jsonl_session(ChatProvider::Gemini, &tmp, &path) {
619-
if session.turn_count > 0 {
620-
out.push(session);
621-
}
622-
}
623-
} else if path.is_dir() {
624-
let files = jsonl_files_in(&path, 1);
625-
if !files.is_empty() {
626-
if let Ok(session) =
627-
jsonl_dir_session(ChatProvider::Gemini, &tmp, &path, files)
628-
{
629-
if session.turn_count > 0 {
630-
out.push(session);
631-
}
632-
}
633-
}
634-
}
635-
}
636-
}
637-
if let Ok(entries) = fs::read_dir(project.path()) {
638-
for entry in entries.flatten() {
639-
let path = entry.path();
640-
if path.is_file() && is_gemini_checkpoint_file(&path) {
641-
if let Ok(session) = jsonl_session(ChatProvider::Gemini, &tmp, &path) {
642-
if session.turn_count > 0 {
643-
out.push(session);
644-
}
645-
}
646-
}
647-
}
648-
}
649-
}
650-
out
651-
}
652-
653-
fn scan_antigravity() -> Vec<ChatSession> {
654-
let Some(home) = dirs::home_dir() else {
655-
return vec![];
656-
};
657-
let tmp = home.join(".gemini").join("antigravity-cli").join("tmp");
658-
if !tmp.is_dir() {
659-
return vec![];
660-
}
661-
let mut out = Vec::new();
662-
let Ok(projects) = fs::read_dir(&tmp) else {
663-
return out;
664-
};
665-
for project in projects.flatten().filter(|e| e.path().is_dir()) {
666-
let chats = project.path().join("chats");
667-
if let Ok(entries) = fs::read_dir(&chats) {
668-
for entry in entries.flatten() {
669-
let path = entry.path();
670-
if path.is_file() && is_gemini_session_file(&path) {
671-
if let Ok(session) = jsonl_session(ChatProvider::Antigravity, &tmp, &path) {
672-
if session.turn_count > 0 {
673-
out.push(session);
674-
}
675-
}
676-
} else if path.is_dir() {
677-
let files = jsonl_files_in(&path, 1);
678-
if !files.is_empty() {
679-
if let Ok(session) =
680-
jsonl_dir_session(ChatProvider::Antigravity, &tmp, &path, files)
681-
{
682-
if session.turn_count > 0 {
683-
out.push(session);
684-
}
685-
}
686-
}
687-
}
688-
}
689-
}
690-
if let Ok(entries) = fs::read_dir(project.path()) {
691-
for entry in entries.flatten() {
692-
let path = entry.path();
693-
if path.is_file() && is_gemini_checkpoint_file(&path) {
694-
if let Ok(session) = jsonl_session(ChatProvider::Antigravity, &tmp, &path) {
695-
if session.turn_count > 0 {
696-
out.push(session);
697-
}
698-
}
699-
}
700-
}
701-
}
702-
}
703-
out
704-
}
705-
706591
fn scan_kiro(workspace: &Path) -> Vec<ChatSession> {
707592
let _ = workspace;
708593
let Some(home) = dirs::home_dir() else {
@@ -935,6 +820,7 @@ fn parse_jsonl_meta(provider: ChatProvider, path: &Path, root: &Path) -> Result<
935820
Ok(meta)
936821
}
937822

823+
#[allow(dead_code)]
938824
fn jsonl_dir_session(
939825
provider: ChatProvider,
940826
root: &Path,
@@ -1055,7 +941,7 @@ fn load_kiro_archive(session: &ChatSession) -> Result<ChatArchive> {
1055941
})
1056942
}
1057943

1058-
fn update_meta_from_event(provider: ChatProvider, value: &Value, meta: &mut SessionMeta) {
944+
fn update_meta_from_event(_provider: ChatProvider, value: &Value, meta: &mut SessionMeta) {
1059945
let event = value.get("payload").unwrap_or(value);
1060946
if meta.id.is_none() {
1061947
meta.id = str_field(event, &["id", "sessionId", "session_id"])
@@ -1089,14 +975,6 @@ fn update_meta_from_event(provider: ChatProvider, value: &Value, meta: &mut Sess
1089975
if is_message_event(value) {
1090976
meta.turn_count += 1;
1091977
}
1092-
if meta.project_path.is_none() && provider == ChatProvider::Gemini {
1093-
meta.project_path =
1094-
str_field(value, &["projectHash"]).map(|s| format!("Gemini project {s}"));
1095-
}
1096-
if meta.project_path.is_none() && provider == ChatProvider::Antigravity {
1097-
meta.project_path =
1098-
str_field(value, &["projectHash"]).map(|s| format!("Antigravity project {s}"));
1099-
}
1100978
}
1101979

1102980
fn tool_title(value: &Value) -> Option<&str> {
@@ -1284,26 +1162,14 @@ fn available_restore_path(path: &Path) -> PathBuf {
12841162
path.to_path_buf()
12851163
}
12861164

1287-
fn project_label_from_path(provider: ChatProvider, root: &Path, path: &Path) -> String {
1165+
fn project_label_from_path(provider: ChatProvider, _root: &Path, path: &Path) -> String {
12881166
match provider {
12891167
ChatProvider::Claude => path
12901168
.parent()
12911169
.and_then(|p| p.file_name())
12921170
.and_then(|s| s.to_str())
12931171
.map(|s| s.replace('-', std::path::MAIN_SEPARATOR_STR))
12941172
.unwrap_or_else(|| "Claude project".into()),
1295-
ChatProvider::Gemini => path
1296-
.strip_prefix(root)
1297-
.ok()
1298-
.and_then(|p| p.components().next())
1299-
.map(|c| c.as_os_str().to_string_lossy().to_string())
1300-
.unwrap_or_else(|| "Gemini project".into()),
1301-
ChatProvider::Antigravity => path
1302-
.strip_prefix(root)
1303-
.ok()
1304-
.and_then(|p| p.components().next())
1305-
.map(|c| c.as_os_str().to_string_lossy().to_string())
1306-
.unwrap_or_else(|| "Antigravity project".into()),
13071173
_ => path
13081174
.parent()
13091175
.map(|p| p.to_string_lossy().to_string())
@@ -1422,6 +1288,7 @@ fn codex_titles() -> HashMap<String, String> {
14221288
.collect()
14231289
}
14241290

1291+
#[allow(dead_code)]
14251292
fn merge_meta(to: &mut SessionMeta, from: SessionMeta) {
14261293
to.id = to.id.take().or(from.id);
14271294
to.title = to.title.take().or(from.title);
@@ -1452,20 +1319,6 @@ fn jsonl_files_in(dir: &Path, depth: usize) -> Vec<PathBuf> {
14521319
files
14531320
}
14541321

1455-
fn is_gemini_session_file(path: &Path) -> bool {
1456-
let name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
1457-
name.starts_with("session-")
1458-
&& matches!(
1459-
path.extension().and_then(|e| e.to_str()),
1460-
Some("json" | "jsonl")
1461-
)
1462-
}
1463-
1464-
fn is_gemini_checkpoint_file(path: &Path) -> bool {
1465-
let name = path.file_name().and_then(|s| s.to_str()).unwrap_or("");
1466-
name.starts_with("checkpoint-") && path.extension().and_then(|e| e.to_str()) == Some("json")
1467-
}
1468-
14691322
fn normalize_role(role: &str) -> &'static str {
14701323
match role {
14711324
"user_message" | "user" => "user",
@@ -1596,14 +1449,6 @@ mod tests {
15961449
validate_archive(&archive).unwrap();
15971450
}
15981451

1599-
#[test]
1600-
fn gemini_session_filter_skips_internal_chunks() {
1601-
assert!(is_gemini_session_file(Path::new("session-abc.jsonl")));
1602-
assert!(is_gemini_session_file(Path::new("session-abc.json")));
1603-
assert!(!is_gemini_session_file(Path::new("0mhqht.jsonl")));
1604-
assert!(is_gemini_checkpoint_file(Path::new("checkpoint-save.json")));
1605-
}
1606-
16071452
#[test]
16081453
fn metadata_search_matches_known_fields() {
16091454
let dir = temp_test_dir("metadata-search");

0 commit comments

Comments
 (0)