codei-config 0.0.13

终端优先的 AI 编程 Agent,用自然语言在本地仓库中读代码、改代码、跑命令、调试问题。
Documentation
use std::fs;
use std::path::Path;

use serde::{Deserialize, Serialize};

use crate::error::ConfigError;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PluginsConfig {
    #[serde(default)]
    pub hooks: Vec<HookConfig>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HookConfig {
    pub event: HookEvent,
    pub command: String,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum HookEvent {
    BeforeTurn,
    AfterTurn,
}

pub fn load_plugins(project_root: &Path) -> PluginsConfig {
    let path = project_root.join(".codei").join("hooks.toml");
    if !path.is_file() {
        return PluginsConfig::default();
    }
    match fs::read_to_string(&path) {
        Ok(text) => toml::from_str(&text).unwrap_or_default(),
        Err(_) => PluginsConfig::default(),
    }
}

pub fn run_hook(hook: &HookConfig, cwd: &Path, env: &[(&str, String)]) -> Result<(), ConfigError> {
    let mut cmd = std::process::Command::new("sh");
    cmd.arg("-lc").arg(&hook.command).current_dir(cwd);
    for (key, value) in env {
        cmd.env(key, value);
    }
    let status = cmd.status().map_err(|source| ConfigError::Read {
        path: cwd.to_path_buf(),
        source,
    })?;
    if !status.success() {
        return Err(ConfigError::HookFailed {
            command: hook.command.clone(),
            code: status.code(),
        });
    }
    Ok(())
}

pub fn run_hooks(
    config: &PluginsConfig,
    event: HookEvent,
    cwd: &Path,
    env: &[(&str, String)],
) -> Result<(), ConfigError> {
    for hook in config.hooks.iter().filter(|hook| hook.event == event) {
        run_hook(hook, cwd, env)?;
    }
    Ok(())
}