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    #[serde(default)]
14    pub shell: ShellConfig,
15}
16
17#[derive(Debug, Default, Deserialize)]
18pub struct RepoConfig {
19    #[serde(default)]
20    pub register: RepoRegisterConfig,
21}
22
23#[derive(Debug, Default, Deserialize)]
24pub struct RepoRegisterConfig {
25    #[serde(default)]
26    pub hooks: Vec<Hook>,
27}
28
29/// A shell command run in the worktree directory after `git worktree add`.
30#[derive(Debug, Clone, Deserialize)]
31pub struct Hook {
32    pub run: String,
33}
34
35#[derive(Debug, Default, Deserialize)]
36pub struct AgentConfig {
37    /// Full command string used to spawn an agent. The prompt is appended
38    /// as the final argument. Defaults to `claude --dangerously-skip-permissions`.
39    pub command: Option<String>,
40}
41
42#[derive(Debug, Default, Deserialize)]
43pub struct ShellConfig {
44    /// Shell binary to run inside workstream PTY sessions.
45    /// Defaults to `$SHELL` or `/bin/bash` if not set.
46    pub binary: Option<String>,
47}
48
49impl UserConfig {
50    pub fn load(vex_home: &Path) -> Self {
51        let path = vex_home.join("config.yaml");
52        if !path.exists() {
53            return Self::default();
54        }
55        match std::fs::read_to_string(&path) {
56            Ok(content) => serde_yaml::from_str(&content).unwrap_or_default(),
57            Err(_) => Self::default(),
58        }
59    }
60
61    pub fn agent_command(&self) -> &str {
62        self.agent
63            .command
64            .as_deref()
65            .unwrap_or("claude --dangerously-skip-permissions")
66    }
67
68    pub fn register_hooks(&self) -> &[Hook] {
69        &self.repo.register.hooks
70    }
71
72    /// Shell binary to use for PTY shell sessions.
73    /// Priority: config → $SHELL env var → /bin/bash.
74    pub fn shell_binary(&self) -> String {
75        if let Some(ref bin) = self.shell.binary {
76            return bin.clone();
77        }
78        std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".to_string())
79    }
80}