1use crate::marked_block;
2
3const MARKER_START: &str = "# >>> lean-ctx shell hook >>>";
4const MARKER_END: &str = "# <<< lean-ctx shell hook <<<";
5const ALIAS_START: &str = "# >>> lean-ctx agent aliases >>>";
6const ALIAS_END: &str = "# <<< lean-ctx agent aliases <<<";
7
8const KNOWN_AGENT_ENV_VARS: &[&str] = &[
9 "LEAN_CTX_AGENT",
10 "CLAUDECODE",
11 "CODEX_CLI_SESSION",
12 "GEMINI_SESSION",
13];
14
15const AGENT_ALIASES: &[(&str, &str)] = &[
16 ("claude", "claude"),
17 ("codex", "codex"),
18 ("gemini", "gemini"),
19];
20
21pub fn install_all(quiet: bool) {
22 let home = match dirs::home_dir() {
23 Some(h) => h,
24 None => {
25 eprintln!("Cannot resolve home directory");
26 return;
27 }
28 };
29
30 install_zshenv(&home, quiet);
31 install_bashenv(&home, quiet);
32 install_aliases(&home, quiet);
33}
34
35pub fn uninstall_all(quiet: bool) {
36 let home = match dirs::home_dir() {
37 Some(h) => h,
38 None => return,
39 };
40
41 marked_block::remove_from_file(
42 &home.join(".zshenv"),
43 MARKER_START,
44 MARKER_END,
45 quiet,
46 "shell hook from ~/.zshenv",
47 );
48 marked_block::remove_from_file(
49 &home.join(".bashenv"),
50 MARKER_START,
51 MARKER_END,
52 quiet,
53 "shell hook from ~/.bashenv",
54 );
55 marked_block::remove_from_file(
56 &home.join(".zshrc"),
57 ALIAS_START,
58 ALIAS_END,
59 quiet,
60 "agent aliases from ~/.zshrc",
61 );
62 marked_block::remove_from_file(
63 &home.join(".bashrc"),
64 ALIAS_START,
65 ALIAS_END,
66 quiet,
67 "agent aliases from ~/.bashrc",
68 );
69}
70
71fn install_zshenv(home: &std::path::Path, quiet: bool) {
72 let path = home.join(".zshenv");
73 let env_check = build_env_check();
74
75 let hook = format!(
76 r#"{MARKER_START}
77if [[ -n "$ZSH_EXECUTION_STRING" ]] && command -v lean-ctx &>/dev/null; then
78 if {env_check}; then
79 exec lean-ctx -c "$ZSH_EXECUTION_STRING"
80 fi
81fi
82{MARKER_END}"#
83 );
84
85 marked_block::upsert(
86 &path,
87 MARKER_START,
88 MARKER_END,
89 &hook,
90 quiet,
91 "shell hook in ~/.zshenv",
92 );
93}
94
95fn install_bashenv(home: &std::path::Path, quiet: bool) {
96 let path = home.join(".bashenv");
97 let env_check = build_env_check();
98
99 let hook = format!(
100 r#"{MARKER_START}
101if [[ -n "$BASH_EXECUTION_STRING" ]] && command -v lean-ctx &>/dev/null; then
102 if {env_check}; then
103 exec lean-ctx -c "$BASH_EXECUTION_STRING"
104 fi
105fi
106{MARKER_END}"#
107 );
108
109 marked_block::upsert(
110 &path,
111 MARKER_START,
112 MARKER_END,
113 &hook,
114 quiet,
115 "shell hook in ~/.bashenv",
116 );
117}
118
119fn install_aliases(home: &std::path::Path, quiet: bool) {
120 let mut lines = Vec::new();
121 lines.push(ALIAS_START.to_string());
122 for (alias_name, bin_name) in AGENT_ALIASES {
123 lines.push(format!(
124 "alias {alias_name}='LEAN_CTX_AGENT=1 BASH_ENV=\"$HOME/.bashenv\" {bin_name}'"
125 ));
126 }
127 lines.push(ALIAS_END.to_string());
128 let block = lines.join("\n");
129
130 for rc in &[home.join(".zshrc"), home.join(".bashrc")] {
131 if rc.exists() {
132 let label = format!(
133 "agent aliases in ~/{}",
134 rc.file_name().unwrap_or_default().to_string_lossy()
135 );
136 marked_block::upsert(rc, ALIAS_START, ALIAS_END, &block, quiet, &label);
137 }
138 }
139}
140
141fn build_env_check() -> String {
142 let checks: Vec<String> = KNOWN_AGENT_ENV_VARS
143 .iter()
144 .map(|v| format!("-n \"${v}\""))
145 .collect();
146 format!("[[ {} ]]", checks.join(" || "))
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn env_check_format() {
155 let check = build_env_check();
156 assert!(check.contains("LEAN_CTX_AGENT"));
157 assert!(check.contains("CLAUDECODE"));
158 assert!(check.contains("||"));
159 }
160}