Skip to main content

lean_ctx/core/config/
shell_activation.rs

1//! Shell activation mode — controls when lean-ctx aliases auto-activate.
2
3use serde::{Deserialize, Serialize};
4
5use super::Config;
6
7/// Controls when the shell hook auto-activates command aliases.
8///
9/// - `Always`: (Default) Aliases are active in every interactive shell.
10/// - `AgentsOnly`: Aliases only activate when an AI agent env var is detected
11///   (e.g. `LEAN_CTX_AGENT`, `CLAUDECODE`, `CODEX_CLI_SESSION`, `GEMINI_SESSION`).
12///   Perfect for users who only want lean-ctx when AI agents run shell commands.
13/// - `Off`: Aliases never auto-activate. The user must call `lean-ctx-on` manually.
14#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
15#[serde(rename_all = "kebab-case")]
16pub enum ShellActivation {
17    #[default]
18    Always,
19    AgentsOnly,
20    Off,
21}
22
23impl ShellActivation {
24    pub fn from_env() -> Option<Self> {
25        std::env::var("LEAN_CTX_SHELL_ACTIVATION")
26            .ok()
27            .and_then(|v| match v.trim().to_lowercase().as_str() {
28                "always" => Some(Self::Always),
29                "agents-only" | "agents_only" | "agentsonly" => Some(Self::AgentsOnly),
30                "off" | "none" | "manual" => Some(Self::Off),
31                _ => None,
32            })
33    }
34
35    pub fn effective(config: &Config) -> Self {
36        if let Some(env_val) = Self::from_env() {
37            return env_val;
38        }
39        config.shell_activation.clone()
40    }
41
42    /// Returns the shell condition snippet that guards auto-activation.
43    /// Used in generated shell hooks (posix, fish, powershell).
44    pub fn posix_guard(&self) -> &'static str {
45        match self {
46            Self::Always => {
47                r#"if [ -z "${LEAN_CTX_ACTIVE:-}" ] && [ -z "${LEAN_CTX_DISABLED:-}" ] && [ "${LEAN_CTX_ENABLED:-1}" != "0" ]; then"#
48            }
49            Self::AgentsOnly => {
50                r#"if [ -z "${LEAN_CTX_ACTIVE:-}" ] && [ -z "${LEAN_CTX_DISABLED:-}" ] && [ "${LEAN_CTX_ENABLED:-1}" != "0" ] && { [ -n "${LEAN_CTX_AGENT:-}" ] || [ -n "${CLAUDECODE:-}" ] || [ -n "${CODEX_CLI_SESSION:-}" ] || [ -n "${GEMINI_SESSION:-}" ]; }; then"#
51            }
52            Self::Off => "",
53        }
54    }
55
56    pub fn fish_guard(&self) -> &'static str {
57        match self {
58            Self::Always => {
59                "if not set -q LEAN_CTX_ACTIVE; and not set -q LEAN_CTX_DISABLED; and test (set -q LEAN_CTX_ENABLED; and echo $LEAN_CTX_ENABLED; or echo 1) != '0'"
60            }
61            Self::AgentsOnly => {
62                "if not set -q LEAN_CTX_ACTIVE; and not set -q LEAN_CTX_DISABLED; and test (set -q LEAN_CTX_ENABLED; and echo $LEAN_CTX_ENABLED; or echo 1) != '0'; and begin; set -q LEAN_CTX_AGENT; or set -q CLAUDECODE; or set -q CODEX_CLI_SESSION; or set -q GEMINI_SESSION; end"
63            }
64            Self::Off => "",
65        }
66    }
67
68    pub fn powershell_guard(&self) -> &'static str {
69        match self {
70            Self::Always => {
71                "if (-not $env:LEAN_CTX_ACTIVE -and -not $env:LEAN_CTX_DISABLED -and -not $env:LEAN_CTX_NO_HOOK)"
72            }
73            Self::AgentsOnly => {
74                "if (-not $env:LEAN_CTX_ACTIVE -and -not $env:LEAN_CTX_DISABLED -and -not $env:LEAN_CTX_NO_HOOK -and ($env:LEAN_CTX_AGENT -or $env:CLAUDECODE -or $env:CODEX_CLI_SESSION -or $env:GEMINI_SESSION))"
75            }
76            Self::Off => "",
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn default_is_always() {
87        assert_eq!(ShellActivation::default(), ShellActivation::Always);
88    }
89
90    #[test]
91    fn serde_roundtrip() {
92        let toml_str = r#"shell_activation = "agents-only""#;
93        #[derive(Deserialize)]
94        struct Wrapper {
95            shell_activation: ShellActivation,
96        }
97        let w: Wrapper = toml::from_str(toml_str).unwrap();
98        assert_eq!(w.shell_activation, ShellActivation::AgentsOnly);
99    }
100
101    #[test]
102    fn posix_guard_always_has_content() {
103        assert!(!ShellActivation::Always.posix_guard().is_empty());
104    }
105
106    #[test]
107    fn posix_guard_agents_checks_env_vars() {
108        let guard = ShellActivation::AgentsOnly.posix_guard();
109        assert!(guard.contains("LEAN_CTX_AGENT"));
110        assert!(guard.contains("CLAUDECODE"));
111        assert!(guard.contains("CODEX_CLI_SESSION"));
112        assert!(guard.contains("GEMINI_SESSION"));
113    }
114
115    #[test]
116    fn posix_guard_off_is_empty() {
117        assert!(ShellActivation::Off.posix_guard().is_empty());
118    }
119}