lean-ctx-engine 3.12.2

LeanCTX | OSS library: the open-source runtime for the Context Gateway for AI agents behind the lean-ctx CLI, MCP server and proxy integrations.
Documentation
// Auto-split from the former monolithic writers.rs. Grouped by operation
// (install/uninstall) + shared helpers; behavior is unchanged.

use super::types::{ConfigType, EditorTarget};

mod install;
mod shared;
mod uninstall;

pub use install::{COMMANDCODE_MCP_INSTRUCTIONS, validate_claude_binary};
pub use shared::auto_approve_tools;
pub use uninstall::remove_lean_ctx_mcp_server;
// Routers below dispatch to every install/uninstall writer; a glob keeps the
// dispatch table readable and lets the test module reach them via `super::*`.
#[allow(clippy::wildcard_imports)]
use install::*;
#[allow(clippy::wildcard_imports)]
use uninstall::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteAction {
    Created,
    Updated,
    Already,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct WriteOptions {
    pub overwrite_invalid: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WriteResult {
    pub action: WriteAction,
    pub note: Option<String>,
}

pub fn write_config(target: &EditorTarget, binary: &str) -> Result<WriteResult, String> {
    write_config_with_options(target, binary, WriteOptions::default())
}

pub fn write_config_with_options(
    target: &EditorTarget,
    binary: &str,
    opts: WriteOptions,
) -> Result<WriteResult, String> {
    let allow_rule_steering = crate::core::config::Config::load().rules_injection_effective()
        != crate::core::config::RulesInjection::Off;

    write_config_with_options_and_rule_steering(target, binary, opts, allow_rule_steering)
}

pub fn write_config_with_options_and_rule_steering(
    target: &EditorTarget,
    binary: &str,
    opts: WriteOptions,
    allow_rule_steering: bool,
) -> Result<WriteResult, String> {
    // A host on the governor setup has no LeanCTX MCP server; setup, doctor
    // and refresh paths all write through here.
    if crate::core::governor_hosts::suppresses_mcp(&target.agent_key, &target.config_path) {
        return Ok(WriteResult {
            action: WriteAction::Already,
            note: Some("governor setup: no MCP server".to_string()),
        });
    }
    if let Some(parent) = target.config_path.parent() {
        std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
    }

    match target.config_type {
        ConfigType::McpJson => write_mcp_json(target, binary, opts),
        ConfigType::Zed => write_zed_config(target, binary, opts),
        ConfigType::Codex => write_codex_config(target, binary),
        ConfigType::VsCodeMcp => write_vscode_mcp(target, binary, opts),
        ConfigType::CopilotCli => write_copilot_cli(target, binary, opts),
        ConfigType::OpenCode => write_opencode_config(target, binary, opts),
        ConfigType::Crush => write_crush_config(target, binary, opts),
        ConfigType::JetBrains => write_jetbrains_config(target, binary, opts),
        ConfigType::Amp => write_amp_config(target, binary, opts),
        ConfigType::HermesYaml => write_hermes_yaml(target, binary, opts),
        ConfigType::GeminiSettings => write_gemini_settings(target, binary, opts),
        ConfigType::QoderSettings => write_qoder_settings(target, binary, opts),
        ConfigType::AugmentVsCode => write_augment_vscode(target, binary, opts),
        ConfigType::OpenClaw => write_openclaw_config(target, binary, opts),
        ConfigType::VibeToml => write_vibe_toml(target, binary, opts),
        ConfigType::CommandCode => {
            write_commandcode_config_with_rule_steering(target, binary, opts, allow_rule_steering)
        }
        ConfigType::ClineCli => write_cline_cli_config(target, binary, opts),
        ConfigType::OmpMcp => write_omp_mcp(target, binary, opts),
        ConfigType::CodeWhale => write_codewhale_config(target, binary, opts),
    }
}

pub fn remove_lean_ctx_server(
    target: &EditorTarget,
    opts: WriteOptions,
) -> Result<WriteResult, String> {
    match target.config_type {
        ConfigType::McpJson
        | ConfigType::JetBrains
        | ConfigType::GeminiSettings
        | ConfigType::QoderSettings
        | ConfigType::CommandCode
        | ConfigType::ClineCli
        | ConfigType::OmpMcp
        // Copilot CLI's `mcp-config.json` uses `mcpServers` (`write_copilot_cli`),
        // not VS Code's `servers`; removing under `servers` left the entry behind.
        | ConfigType::CopilotCli => remove_lean_ctx_mcp_server(&target.config_path, opts),
        ConfigType::VsCodeMcp => remove_lean_ctx_vscode_server(&target.config_path, opts),
        ConfigType::Codex => remove_lean_ctx_codex_server(&target.config_path),
        ConfigType::OpenCode | ConfigType::Crush => {
            remove_lean_ctx_named_json_server(&target.config_path, "mcp", opts)
        }
        ConfigType::Zed => {
            remove_lean_ctx_named_json_server(&target.config_path, "context_servers", opts)
        }
        ConfigType::Amp => remove_lean_ctx_amp_server(&target.config_path, opts),
        ConfigType::HermesYaml => remove_lean_ctx_hermes_yaml_server(&target.config_path),
        ConfigType::AugmentVsCode => {
            remove_lean_ctx_augment_vscode_server(&target.config_path, opts)
        }
        ConfigType::OpenClaw => remove_lean_ctx_openclaw_server(&target.config_path, opts),
        ConfigType::VibeToml => remove_lean_ctx_vibe_toml_server(&target.config_path, opts),
        // #1402: CodeWhale accepts either root key, and we merge into whichever
        // one the user already had — so removal has to visit both, or an entry
        // written under `servers` would survive `uninstall`.
        ConfigType::CodeWhale => {
            let servers = remove_lean_ctx_named_json_server(&target.config_path, "servers", opts)?;
            let mcp_servers =
                remove_lean_ctx_named_json_server(&target.config_path, "mcpServers", opts)?;
            Ok(if servers.action == WriteAction::Updated {
                servers
            } else {
                mcp_servers
            })
        }
    }
}

#[cfg(test)]
mod tests;