lean_ctx/core/config/
shell_activation.rs1use serde::{Deserialize, Serialize};
4
5use super::Config;
6
7#[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 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}