reliary-agent 0.4.2

Grammar-free code intelligence daemon, CLI, MCP server, and API proxy.
use std::path::PathBuf;
use std::fs;
use std::net::TcpStream;
use std::time::Duration;
use serde_json::Value;

fn home_dir() -> Option<PathBuf> {
    dirs::home_dir()
}

pub fn doctor() {
    println!("\n{} Reliary Doctor — System Health & Diagnosis {}\n", "\x1b[1m\x1b[34m", "\x1b[0m");
    let mut all_good = true;

    // 1. Daemon Status
    print!("{}  Daemon Status: ", "\x1b[34m•\x1b[0m");
    if TcpStream::connect_timeout(&"127.0.0.1:9090".parse().expect("invalid port"), Duration::from_millis(500)).is_ok() {
        println!("{} Active on port 9090", "\x1b[32m✓\x1b[0m");
    } else {
        println!("{} Inactive or unreachable", "\x1b[31m✗\x1b[0m");
        println!("     {} Run 'reliary-agent serve' to start it", "\x1b[2m→\x1b[0m");
        all_good = false;
    }

    // 2. Proxy Status
    print!("{}  Proxy Status: ", "\x1b[34m•\x1b[0m");
    if TcpStream::connect_timeout(&"127.0.0.1:9090".parse().expect("invalid port"), Duration::from_millis(500)).is_ok() {
        println!("{} Active (auth-based routing)", "\x1b[32m✓\x1b[0m");
    } else {
        println!("{} Inactive", "\x1b[31m✗\x1b[0m");
    }

    // 3. Pi Agent
    print!("{}  Pi Agent: ", "\x1b[34m•\x1b[0m");
    let pi_gate = home_dir().map(|h| h.join(".local/share/reliary/gate.js")).unwrap_or_default();
    if pi_gate.exists() {
        println!("{} gate.js installed", "\x1b[32m✓\x1b[0m");
    } else {
        println!("{} gate.js not found (optional — only needed for Pi)", "\x1b[33m-\x1b[0m");
    }

    // 4. MCP Clients
    print!("{}  Claude Code MCP: ", "\x1b[34m•\x1b[0m");
    let claude_cfg = home_dir().map(|h| h.join(".claude.json")).unwrap_or_default();
    if has_mcp_server(&claude_cfg, "reliary") {
        println!("{} Wired", "\x1b[32m✓\x1b[0m");
    } else {
        println!("{} Not wired (run 'rel init')", "\x1b[33m-\x1b[0m");
    }

    print!("{}  OpenCode MCP: ", "\x1b[34m•\x1b[0m");
    let opencode_cfg = if cfg!(target_os = "windows") {
        dirs::config_dir().map(|d| d.join("opencode").join("opencode.json"))
    } else if cfg!(target_os = "macos") {
        home_dir().map(|h| h.join("Library/Application Support/opencode/opencode.json"))
    } else {
        home_dir().map(|h| h.join(".config/opencode/opencode.json"))
    }.unwrap_or_default();
    if has_mcp_server(&opencode_cfg, "reliary") {
        println!("{} Wired", "\x1b[32m✓\x1b[0m");
    } else {
        println!("{} Not wired", "\x1b[33m-\x1b[0m");
    }

    // 5. Project Health
    print!("\n{}  Project Health: ", "\x1b[34m•\x1b[0m");
    let index_path = PathBuf::from(".reliary/index.sqlite");
    if index_path.exists() {
        println!("{} Index exists", "\x1b[32m✓\x1b[0m");
    } else {
        println!("{} No index found", "\x1b[33m-\x1b[0m");
        println!("     {} Run 'reliary-agent index .' to build it", "\x1b[2m→\x1b[0m");
    }

    // 6. Config State
    print!("{}  Config State: ", "\x1b[34m•\x1b[0m");
    let mode = crate::config::resolve_mode(Some("."));
    println!("{} mode", mode.as_str());

    if all_good {
        println!("\n  {} System ready.", "\x1b[32m✓\x1b[0m");
    } else {
        println!("\n  {} Some checks failed. See tips above.", "\x1b[33m⚠\x1b[0m");
    }
}

pub fn status() {
    println!("\n{} Project Intelligence Overview {}\n", "\x1b[1m\x1b[34m", "\x1b[0m");

    let index_path = PathBuf::from(".reliary/index.sqlite");
    if !index_path.exists() {
        println!("{} No index found in current directory.", "\x1b[33m-\x1b[0m");
        println!("     {} Run 'reliary-agent index .' to build it", "\x1b[2m→\x1b[0m");
        return;
    }

    if let Ok(db) = rusqlite::Connection::open(&index_path) {
        let _ = db.execute_batch("PRAGMA synchronous=NORMAL;");
        let mut file_count = 0;
        if let Ok(mut stmt) = db.prepare("SELECT COUNT(DISTINCT file_id) FROM file_phrases") {
            if let Ok(mut rows) = stmt.query([]) {
                if let Ok(Some(row)) = rows.next() {
                    file_count = row.get::<_, i64>(0).unwrap_or(0);
                }
            }
        }
        println!("{} Index: {} files indexed", "\x1b[34m•\x1b[0m", file_count);

        let mut event_count = 0;
        if let Ok(mut stmt) = db.prepare("SELECT COUNT(*) FROM chronicle") {
            if let Ok(mut rows) = stmt.query([]) {
                if let Ok(Some(row)) = rows.next() {
                    event_count = row.get::<_, i64>(0).unwrap_or(0);
                }
            }
        }
        println!("{} Chronicle: {} events recorded", "\x1b[34m•\x1b[0m", event_count);
    } else {
        println!("{} Failed to open index.", "\x1b[31m✗\x1b[0m");
    }
}

pub fn clean(global: bool, all: bool) {
    let do_global = global || all;
    let do_local = !global || all;

    if do_local {
        let local_dir = PathBuf::from(".reliary");
        if local_dir.exists() {
            if fs::remove_dir_all(&local_dir).is_ok() {
                println!("✓ Cleaned project state (.reliary)");
            } else {
                println!("✗ Failed to clean project state");
            }
        } else {
            println!("- No project state found");
        }
    }

    if do_global {
        if let Some(home) = home_dir() {
            let global_dir = home.join(".reliary");
            if global_dir.exists() {
                if fs::remove_dir_all(&global_dir).is_ok() {
                    println!("✓ Cleaned global state (~/.reliary)");
                } else {
                    println!("✗ Failed to clean global state");
                }
            } else {
                println!("- No global state found");
            }
        }
    }
}

pub fn logs() {
    println!("Daemon logs are managed by your OS service manager.");
    #[cfg(target_os = "linux")]
    {
        println!("Run: journalctl --user -u reliary-daemon.service -f");
    }
    #[cfg(target_os = "macos")]
    {
        println!("Check standard output/error files configured for com.reliary.daemon, or use Console.app.");
    }
    #[cfg(target_os = "windows")]
    {
        println!("Daemon runs silently via VBScript on Windows. Custom logging is not currently implemented.");
    }
}

fn has_mcp_server(cfg_path: &PathBuf, server_name: &str) -> bool {
    if let Ok(content) = fs::read_to_string(cfg_path) {
        if let Ok(v) = serde_json::from_str::<Value>(&content) {
            if let Some(servers) = v.get("mcpServers").and_then(|m| m.as_object()) {
                return servers.contains_key(server_name);
            }
        }
    }
    false
}