Skip to content
Merged
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
6 changes: 1 addition & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ jobs:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
Expand All @@ -43,7 +41,7 @@ jobs:
- platform: macos-latest
target: aarch64-apple-darwin
label: apple-silicon
- platform: macos-13
- platform: macos-latest
target: x86_64-apple-darwin
label: intel

Expand All @@ -52,8 +50,6 @@ jobs:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 10

- uses: actions/setup-node@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ target/
.env
.env.local
*.log
.turbo/
.nx/
6 changes: 5 additions & 1 deletion apps/desktop/src-tauri/src/clients/antigravity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ impl ClientAdapter for AntigravityAdapter {
let path = Self::get_config_path()
.ok_or_else(|| anyhow::anyhow!("Cannot determine config path for Antigravity"))?;

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

let current_content = match existing_content {
Some(c) => Some(c.to_string()),
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src-tauri/src/clients/claude_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ impl ClientAdapter for ClaudeCodeAdapter {
}
};

let output =
serializer::serialize_to_client_format("claude-code", servers, current_content.as_deref())?;
let output = serializer::serialize_to_client_format(
"claude-code",
servers,
current_content.as_deref(),
)?;

if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src-tauri/src/clients/claude_desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,11 @@ impl ClientAdapter for ClaudeDesktopAdapter {
}
};

let output =
serializer::serialize_to_client_format("claude-desktop", servers, current_content.as_deref())?;
let output = serializer::serialize_to_client_format(
"claude-desktop",
servers,
current_content.as_deref(),
)?;

if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src-tauri/src/clients/jetbrains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ impl JetBrainsAdapter {
/// 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 app_support = home
.join("Library")
.join("Application Support")
.join("JetBrains");

if !app_support.exists() {
return None;
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src-tauri/src/clients/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ pub trait ClientAdapter: Send + Sync {

/// Write MCP server configurations to this client's config,
/// optionally merging with existing content.
fn write_servers(&self, servers: &[McpServerConfig], existing_content: Option<&str>)
-> Result<()>;
fn write_servers(
&self,
servers: &[McpServerConfig],
existing_content: Option<&str>,
) -> Result<()>;
}

/// Information about a detected client.
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src-tauri/src/clients/vscode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ impl ClientAdapter for VSCodeAdapter {
let path = Self::get_config_path()
.ok_or_else(|| anyhow::anyhow!("Cannot determine config path for VS Code"))?;

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

let current_content = match existing_content {
Some(c) => Some(c.to_string()),
Expand Down
7 changes: 5 additions & 2 deletions apps/desktop/src-tauri/src/clients/windsurf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,11 @@ impl ClientAdapter for WindsurfAdapter {
}
};

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

if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
Expand Down
7 changes: 6 additions & 1 deletion apps/desktop/src-tauri/src/commands/activity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ use crate::config::{self, ActivityEntry};

#[tauri::command]
pub async fn get_activity() -> Result<Vec<ActivityEntry>, String> {
let cfg = config::read_config().map_err(|e| e.to_string())?;
let mut cfg = config::read_config().map_err(|e| e.to_string())?;
let pruned = config::prune_activity_entries(&mut cfg.activity);
if pruned > 0 {
config::write_config(&cfg).map_err(|e| e.to_string())?;
}

let mut entries = cfg.activity;
entries.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
Ok(entries)
Expand Down
9 changes: 7 additions & 2 deletions apps/desktop/src-tauri/src/commands/detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ pub async fn detect_clients() -> Result<Vec<ClientDetection>, String> {
let detected = adapter.detect();
let (server_count, server_names) = if detected {
match adapter.read_servers() {
Ok(servers) => (servers.len(), servers.iter().map(|s| s.name.clone()).collect()),
Ok(servers) => (
servers.len(),
servers.iter().map(|s| s.name.clone()).collect(),
),
Err(_) => (0, Vec::new()),
}
} else {
Expand All @@ -38,7 +41,9 @@ pub async fn detect_clients() -> Result<Vec<ClientDetection>, String> {
display_name: adapter.display_name().to_string(),
icon: adapter.icon().to_string(),
detected,
config_path: adapter.config_path().map(|p| p.to_string_lossy().to_string()),
config_path: adapter
.config_path()
.map(|p| p.to_string_lossy().to_string()),
server_count,
server_names,
last_synced_at,
Expand Down
16 changes: 10 additions & 6 deletions apps/desktop/src-tauri/src/commands/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ use crate::config::{self, ImportResult};

#[tauri::command]
pub async fn import_from_client(client_id: String) -> Result<ImportResult, String> {
let adapter = clients::get_adapter(&client_id)
.ok_or_else(|| format!("Unknown client: {}", client_id))?;
let adapter =
clients::get_adapter(&client_id).ok_or_else(|| format!("Unknown client: {}", client_id))?;

if !adapter.detect() {
return Err(format!("Client '{}' is not installed or not detected", client_id));
return Err(format!(
"Client '{}' is not installed or not detected",
client_id
));
}

let client_servers = adapter.read_servers().map_err(|e| e.to_string())?;
Expand All @@ -18,9 +21,10 @@ pub async fn import_from_client(client_id: String) -> Result<ImportResult, Strin
let mut skipped_count = 0usize;

for server in client_servers {
let is_duplicate = cfg.servers.iter().any(|existing| {
existing.name == server.name && existing.command == server.command
});
let is_duplicate = cfg
.servers
.iter()
.any(|existing| existing.name == server.name && existing.command == server.command);

if is_duplicate {
skipped_count += 1;
Expand Down
10 changes: 5 additions & 5 deletions apps/desktop/src-tauri/src/commands/logo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,7 @@ fn find_app_icon(bundle: &PathBuf, resources: &PathBuf) -> Option<PathBuf> {
}

// Strategy 2: Try well-known icon file names derived from the app name
let app_name = bundle
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
let app_name = bundle.file_stem().and_then(|s| s.to_str()).unwrap_or("");
if !app_name.is_empty() {
let candidate = resources.join(format!("{}.icns", app_name));
if candidate.exists() {
Expand Down Expand Up @@ -260,7 +257,10 @@ fn try_icon_from_url_domain(url: &str) -> Option<String> {
// Use Google's favicon service for the domain
// This returns a real favicon for any public domain
let base_domain = extract_base_domain(host);
Some(format!("https://www.google.com/s2/favicons?domain={}&sz=64", base_domain))
Some(format!(
"https://www.google.com/s2/favicons?domain={}&sz=64",
base_domain
))
}

/// Extract the registrable domain from a hostname
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src-tauri/src/commands/oauth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ pub async fn start_oauth_flow(
/// Check the authentication status for a server.
#[tauri::command]
pub async fn check_auth_status(server_id: String) -> Result<OAuthStatus, String> {
let username = format!("{}:oauth_token", server_id);
let entry = keyring::Entry::new("conductor", &username).map_err(|e| e.to_string())?;

let authenticated = entry.get_password().is_ok();
let authenticated = crate::oauth::get_valid_oauth_token(&server_id)
.await
.map(|token| token.is_some())
.unwrap_or(false);

let provider = if authenticated {
let provider_key = format!("{}:oauth_provider", server_id);
Expand Down
Loading
Loading