Skip to main content

vex_cli/
user_config.rs

1use serde::Deserialize;
2use std::path::Path;
3
4/// Top-level user configuration loaded from `$VEX_HOME/config.yaml`.
5/// All fields are optional; missing fields use defaults. If the file does
6/// not exist no error is shown and defaults are used throughout.
7#[derive(Debug, Default, Deserialize)]
8pub struct UserConfig {
9    #[serde(default)]
10    pub repo: RepoConfig,
11    #[serde(default)]
12    pub agent: AgentConfig,
13}
14
15#[derive(Debug, Default, Deserialize)]
16pub struct RepoConfig {
17    #[serde(default)]
18    pub register: RepoRegisterConfig,
19}
20
21#[derive(Debug, Default, Deserialize)]
22pub struct RepoRegisterConfig {
23    #[serde(default)]
24    pub hooks: Vec<Hook>,
25}
26
27/// A shell command run in the worktree directory after `git worktree add`.
28#[derive(Debug, Clone, Deserialize)]
29pub struct Hook {
30    pub run: String,
31}
32
33#[derive(Debug, Default, Deserialize)]
34pub struct AgentConfig {
35    /// Full command string used to spawn an agent. The prompt is appended
36    /// as the final argument. Defaults to `claude --dangerously-skip-permissions`.
37    pub command: Option<String>,
38}
39
40impl UserConfig {
41    pub fn load(vex_home: &Path) -> Self {
42        let path = vex_home.join("config.yaml");
43        if !path.exists() {
44            return Self::default();
45        }
46        match std::fs::read_to_string(&path) {
47            Ok(content) => serde_yaml::from_str(&content).unwrap_or_default(),
48            Err(_) => Self::default(),
49        }
50    }
51
52    pub fn agent_command(&self) -> &str {
53        self.agent
54            .command
55            .as_deref()
56            .unwrap_or("claude --dangerously-skip-permissions")
57    }
58
59    pub fn register_hooks(&self) -> &[Hook] {
60        &self.repo.register.hooks
61    }
62}