telekinesis 0.2.19

AI coding agent TUI — rx4 harness + crepuscularity-tui, pi-first UX
//! Host MCP server config: ~/.telekinesis/mcp.json
//!
//! Engine owns stdio + remote HTTP/SSE transports. Host loads config and connects best-effort.

use serde::Deserialize;
use std::collections::HashMap;
use std::path::PathBuf;

#[derive(Debug, Clone, Deserialize)]
pub struct McpServerConfig {
    pub name: String,
    /// `stdio` | `http` | `sse`
    #[serde(default = "default_transport")]
    pub transport: String,
    pub command: Option<String>,
    #[serde(default)]
    pub args: Vec<String>,
    pub url: Option<String>,
    #[serde(default)]
    pub headers: HashMap<String, String>,
}

fn default_transport() -> String {
    "stdio".to_string()
}

#[derive(Debug, Deserialize, Default)]
struct McpConfigFile {
    #[serde(default)]
    servers: Vec<McpServerConfig>,
}

pub fn config_path() -> PathBuf {
    dirs::home_dir()
        .map(|h| h.join(".telekinesis/mcp.json"))
        .unwrap_or_else(|| PathBuf::from(".telekinesis/mcp.json"))
}

/// Load MCP server entries. Missing/invalid file → empty list (TUI still starts).
pub fn load() -> Vec<McpServerConfig> {
    let path = config_path();
    let Ok(raw) = std::fs::read_to_string(&path) else {
        return Vec::new();
    };
    match serde_json::from_str::<McpConfigFile>(&raw) {
        Ok(file) => file.servers,
        Err(e) => {
            eprintln!("telekinesis: ignore invalid {}: {e}", path.display());
            Vec::new()
        }
    }
}