Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
- platform: macos-latest
target: x86_64-apple-darwin
label: intel
- platform: ubuntu-latest
target: x86_64-unknown-linux-gnu
label: linux-amd64

runs-on: ${{ matrix.platform }}
env:
Expand All @@ -75,6 +78,18 @@ jobs:

- run: pnpm install --frozen-lockfile

- name: Install Linux system dependencies
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsecret-1-dev \
patchelf

- name: Build Tauri app (signed)
if: github.event_name != 'pull_request' && env.ENABLE_CODESIGN == 'true'
uses: tauri-apps/tauri-action@v0
Expand Down Expand Up @@ -106,7 +121,9 @@ jobs:
path: |
apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/dmg/*.dmg
apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/macos/*.app.tar.gz
if-no-files-found: error
apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/deb/*.deb
apps/desktop/src-tauri/target/${{ matrix.target }}/release/bundle/appimage/*.AppImage
if-no-files-found: warn

release:
name: Release
Expand All @@ -131,3 +148,5 @@ jobs:
files: |
artifacts/**/*.dmg
artifacts/**/*.app.tar.gz
artifacts/**/*.deb
artifacts/**/*.AppImage
61 changes: 61 additions & 0 deletions apps/desktop/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ urlencoding = "2"
url = "2"
sha2 = "0.10"
anyhow = "1"
json5 = "0.4"
open = "5"
base64 = "0.22"
6,770 changes: 6,770 additions & 0 deletions apps/desktop/src-tauri/gen/schemas/linux-schema.json

Large diffs are not rendered by default.

47 changes: 21 additions & 26 deletions apps/desktop/src-tauri/src/clients/antigravity.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::clients::ClientAdapter;
use crate::clients::{app_installed, ClientAdapter};
use crate::config::McpServerConfig;
use crate::config::{backup, normalizer, serializer};
use anyhow::Result;
Expand All @@ -7,36 +7,35 @@ use std::path::PathBuf;
pub struct AntigravityAdapter;

impl AntigravityAdapter {
fn get_config_path() -> Option<PathBuf> {
/// The canonical config path for writing — always mcp_config.json.
fn mcp_json_path() -> Option<PathBuf> {
let home = dirs::home_dir()?;
// Antigravity is a VS Code fork by Google, uses mcp.json like VS Code
let mcp_json = home
.join("Library")
.join("Application Support")
.join("Antigravity")
.join("User")
.join("mcp.json");
Some(home.join(".gemini").join("antigravity").join("mcp_config.json"))
}

/// Config path for reading — mcp.json if it exists, else settings.json for migration.
fn get_config_path() -> Option<PathBuf> {
let mcp_json = Self::mcp_json_path()?;
if mcp_json.exists() {
return Some(mcp_json);
}
// Fallback to settings.json
let settings = home
.join("Library")
.join("Application Support")
// Read from settings.json only as a one-time migration source
let config_dir = dirs::config_dir()?;
let settings = config_dir
.join("Antigravity")
.join("User")
.join("settings.json");
if settings.exists() {
return Some(settings);
}
// Default to mcp.json
// Default write target
Some(mcp_json)
}

fn is_mcp_json(path: &std::path::Path) -> bool {
path.file_name()
.and_then(|f| f.to_str())
.map(|f| f == "mcp.json")
.map(|f| f == "mcp_config.json" || f == "mcp.json")
.unwrap_or(false)
}
}
Expand All @@ -60,7 +59,7 @@ impl ClientAdapter for AntigravityAdapter {
return true;
}
}
std::path::Path::new("/Applications/Antigravity.app").exists()
app_installed("Antigravity.app", "antigravity")
}

fn config_path(&self) -> Option<PathBuf> {
Expand All @@ -74,9 +73,9 @@ impl ClientAdapter for AntigravityAdapter {
return Ok(Vec::new());
}
let content = std::fs::read_to_string(&path)?;
// Same format as VS Code
// Standard format (mcpServers) for mcp_config.json
if Self::is_mcp_json(&path) {
normalizer::parse_client_config("vscode-mcp", &content)
normalizer::parse_client_config("claude-desktop", &content)
} else {
normalizer::parse_client_config("vscode", &content)
}
Expand All @@ -88,15 +87,11 @@ impl ClientAdapter for AntigravityAdapter {
existing_content: Option<&str>,
previously_synced_names: &[String],
) -> Result<()> {
let path = Self::get_config_path()
// Always write to mcp.json — Antigravity warns against MCP in settings.json
let path = Self::mcp_json_path()
.ok_or_else(|| anyhow::anyhow!("Cannot determine config path for Antigravity"))?;

let format = if Self::is_mcp_json(&path) {
"vscode-mcp"
} else {
"vscode"
};

// Read existing mcp.json content (ignore settings.json for write merge)
let current_content = match existing_content {
Some(c) => Some(c.to_string()),
None => {
Expand All @@ -109,7 +104,7 @@ impl ClientAdapter for AntigravityAdapter {
};

let output =
serializer::serialize_to_client_format(format, servers, current_content.as_deref(), previously_synced_names)?;
serializer::serialize_to_client_format("antigravity", servers, current_content.as_deref(), previously_synced_names)?;

if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
Expand Down
9 changes: 1 addition & 8 deletions apps/desktop/src-tauri/src/clients/claude_code.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::clients::ClientAdapter;
use crate::clients::{which_exists, ClientAdapter};
use crate::config::McpServerConfig;
use crate::config::{backup, normalizer, serializer};
use anyhow::Result;
Expand Down Expand Up @@ -97,10 +97,3 @@ impl ClientAdapter for ClaudeCodeAdapter {
}
}

fn which_exists(cmd: &str) -> bool {
std::process::Command::new("which")
.arg(cmd)
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
11 changes: 4 additions & 7 deletions apps/desktop/src-tauri/src/clients/claude_desktop.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::clients::ClientAdapter;
use crate::clients::{app_installed, ClientAdapter};
use crate::config::McpServerConfig;
use crate::config::{backup, normalizer, serializer};
use anyhow::Result;
Expand All @@ -8,10 +8,8 @@ pub struct ClaudeDesktopAdapter;

impl ClaudeDesktopAdapter {
fn get_config_path() -> Option<PathBuf> {
let home = dirs::home_dir()?;
let path = home
.join("Library")
.join("Application Support")
let config_dir = dirs::config_dir()?;
let path = config_dir
.join("Claude")
.join("claude_desktop_config.json");
Some(path)
Expand All @@ -37,8 +35,7 @@ impl ClientAdapter for ClaudeDesktopAdapter {
return true;
}
}
// Also check if the app is installed
std::path::Path::new("/Applications/Claude.app").exists()
app_installed("Claude.app", "claude-desktop")
}

fn config_path(&self) -> Option<PathBuf> {
Expand Down
8 changes: 2 additions & 6 deletions apps/desktop/src-tauri/src/clients/codex.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::clients::ClientAdapter;
use crate::clients::{which_exists, ClientAdapter};
use crate::config::McpServerConfig;
use crate::config::{backup, normalizer, serializer};
use anyhow::Result;
Expand Down Expand Up @@ -44,11 +44,7 @@ impl ClientAdapter for CodexAdapter {
}
}
// Check if codex CLI is available
std::process::Command::new("which")
.arg("codex")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
which_exists("codex")
}

fn config_path(&self) -> Option<PathBuf> {
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/src/clients/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::clients::ClientAdapter;
use crate::clients::{app_installed, ClientAdapter};
use crate::config::McpServerConfig;
use crate::config::{backup, normalizer, serializer};
use anyhow::Result;
Expand Down Expand Up @@ -32,7 +32,7 @@ impl ClientAdapter for CursorAdapter {
return true;
}
}
std::path::Path::new("/Applications/Cursor.app").exists()
app_installed("Cursor.app", "cursor")
}

fn config_path(&self) -> Option<PathBuf> {
Expand Down
29 changes: 13 additions & 16 deletions apps/desktop/src-tauri/src/clients/jetbrains.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::clients::ClientAdapter;
use crate::clients::{app_installed, ClientAdapter};
use crate::config::McpServerConfig;
use crate::config::{backup, normalizer, serializer};
use anyhow::Result;
Expand All @@ -10,11 +10,8 @@ impl JetBrainsAdapter {
/// JetBrains stores MCP config in the most recent IDE's config directory.
/// We search for common JetBrains IDEs on macOS.
fn get_config_path() -> Option<PathBuf> {
let home = dirs::home_dir()?;
let app_support = home
.join("Library")
.join("Application Support")
.join("JetBrains");
let config_dir = dirs::config_dir()?;
let app_support = config_dir.join("JetBrains");

if !app_support.exists() {
return None;
Expand Down Expand Up @@ -74,17 +71,17 @@ impl ClientAdapter for JetBrainsAdapter {
return path.exists();
}
// Check if any JetBrains IDE is installed
let apps = [
"/Applications/IntelliJ IDEA.app",
"/Applications/WebStorm.app",
"/Applications/PyCharm.app",
"/Applications/GoLand.app",
"/Applications/RustRover.app",
"/Applications/CLion.app",
"/Applications/Rider.app",
"/Applications/PhpStorm.app",
let ides: &[(&str, &str)] = &[
("IntelliJ IDEA.app", "idea"),
("WebStorm.app", "webstorm"),
("PyCharm.app", "pycharm"),
("GoLand.app", "goland"),
("RustRover.app", "rustrover"),
("CLion.app", "clion"),
("Rider.app", "rider"),
("PhpStorm.app", "phpstorm"),
];
apps.iter().any(|app| std::path::Path::new(app).exists())
ides.iter().any(|(mac, bin)| app_installed(mac, bin))
}

fn config_path(&self) -> Option<PathBuf> {
Expand Down
Loading