lean_ctx/core/config/
shell_activation.rs1use serde::{Deserialize, Serialize};
4
5use super::Config;
6
7#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
19#[serde(rename_all = "kebab-case")]
20pub enum ShellActivation {
21 Always,
22 #[default]
23 AgentsOnly,
24 Off,
25}
26
27impl ShellActivation {
28 pub fn from_env() -> Option<Self> {
29 std::env::var("LEAN_CTX_SHELL_ACTIVATION")
30 .ok()
31 .and_then(|v| match v.trim().to_lowercase().as_str() {
32 "always" => Some(Self::Always),
33 "agents-only" | "agents_only" | "agentsonly" => Some(Self::AgentsOnly),
34 "off" | "none" | "manual" => Some(Self::Off),
35 _ => None,
36 })
37 }
38
39 pub fn effective(config: &Config) -> Self {
40 if let Some(env_val) = Self::from_env() {
41 return env_val;
42 }
43 config.shell_activation.clone()
44 }
45
46 pub fn posix_guard(&self) -> &'static str {
49 match self {
50 Self::Always => {
51 r#"if [ -z "${LEAN_CTX_ACTIVE:-}" ] && [ -z "${LEAN_CTX_DISABLED:-}" ] && [ "${LEAN_CTX_ENABLED:-1}" != "0" ]; then"#
52 }
53 Self::AgentsOnly => {
54 r#"if [ -z "${LEAN_CTX_ACTIVE:-}" ] && [ -z "${LEAN_CTX_DISABLED:-}" ] && [ "${LEAN_CTX_ENABLED:-1}" != "0" ] && { [ -n "${LEAN_CTX_AGENT:-}" ] || [ -n "${CURSOR_AGENT:-}" ] || [ -n "${CLAUDECODE:-}" ] || [ -n "${CODEBUDDY:-}" ] || [ -n "${CODEX_CLI_SESSION:-}" ] || [ -n "${GEMINI_SESSION:-}" ]; }; then"#
55 }
56 Self::Off => "",
57 }
58 }
59
60 pub fn fish_guard(&self) -> &'static str {
61 match self {
62 Self::Always => {
63 "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'"
64 }
65 Self::AgentsOnly => {
66 "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 CURSOR_AGENT; or set -q CLAUDECODE; or set -q CODEBUDDY; or set -q CODEX_CLI_SESSION; or set -q GEMINI_SESSION; end"
67 }
68 Self::Off => "",
69 }
70 }
71
72 pub fn powershell_guard(&self) -> &'static str {
73 match self {
74 Self::Always => {
75 "if (-not $env:LEAN_CTX_ACTIVE -and -not $env:LEAN_CTX_DISABLED -and -not $env:LEAN_CTX_NO_HOOK)"
76 }
77 Self::AgentsOnly => {
78 "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:CURSOR_AGENT -or $env:CLAUDECODE -or $env:CODEBUDDY -or $env:CODEX_CLI_SESSION -or $env:GEMINI_SESSION))"
79 }
80 Self::Off => "",
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
93 fn default_is_agents_only() {
94 assert_eq!(ShellActivation::default(), ShellActivation::AgentsOnly);
95 }
96
97 #[test]
98 fn serde_roundtrip() {
99 let toml_str = r#"shell_activation = "agents-only""#;
100 #[derive(Deserialize)]
101 struct Wrapper {
102 shell_activation: ShellActivation,
103 }
104 let w: Wrapper = toml::from_str(toml_str).unwrap();
105 assert_eq!(w.shell_activation, ShellActivation::AgentsOnly);
106 }
107
108 #[test]
109 fn posix_guard_always_has_content() {
110 assert!(!ShellActivation::Always.posix_guard().is_empty());
111 }
112
113 #[test]
114 fn posix_guard_agents_checks_env_vars() {
115 let guard = ShellActivation::AgentsOnly.posix_guard();
116 assert!(guard.contains("LEAN_CTX_AGENT"));
117 assert!(guard.contains("CURSOR_AGENT"));
118 assert!(guard.contains("CLAUDECODE"));
119 assert!(guard.contains("CODEBUDDY"));
120 assert!(guard.contains("CODEX_CLI_SESSION"));
121 assert!(guard.contains("GEMINI_SESSION"));
122 }
123
124 #[test]
128 fn all_guards_recognize_cursor_agent() {
129 assert!(
130 ShellActivation::AgentsOnly
131 .fish_guard()
132 .contains("CURSOR_AGENT")
133 );
134 assert!(
135 ShellActivation::AgentsOnly
136 .powershell_guard()
137 .contains("CURSOR_AGENT")
138 );
139 }
140
141 #[test]
142 fn posix_guard_off_is_empty() {
143 assert!(ShellActivation::Off.posix_guard().is_empty());
144 }
145}