lean_ctx/proxy_setup/
mod.rs1mod claude;
7mod codex;
8mod commandcode;
9mod grok;
10mod pi;
11mod shell;
12mod util;
13
14#[cfg(test)]
15#[path = "tests.rs"]
16mod tests;
17
18use std::path::Path;
19
20use crate::marked_block;
21
22use claude::{install_claude_env, install_claude_env_inner, uninstall_claude_env};
23use codex::{codex_config_has_local_proxy_entry, strip_codex_proxy_entries, uninstall_codex_env};
24use commandcode::{install_commandcode_env, uninstall_commandcode_env};
25use grok::{
26 grok_config_has_local_proxy_entry, install_grok_env, strip_grok_proxy_entries,
27 uninstall_grok_env,
28};
29use pi::{install_pi_env, uninstall_pi_env};
30use shell::install_shell_exports;
31use util::{PROXY_ENV_END, PROXY_ENV_START};
32
33pub use claude::anthropic_api_key_available;
34pub(crate) use codex::install_codex_env;
35pub(crate) use commandcode::COMMANDCODE_MCP_INSTRUCTIONS;
36pub use grok::{grok_session_auth_available, xai_api_key_available};
37pub(crate) use util::is_proxy_reachable;
38pub use util::{default_port, is_local_lean_ctx_url, proxy_timeout};
39
40pub fn install_proxy_env(home: &Path, port: u16, quiet: bool) {
41 let cfg = crate::core::config::Config::load();
42 if cfg.proxy_enabled != Some(true) {
43 if !quiet {
44 println!(" Proxy env skipped (not enabled in config)");
45 }
46 return;
47 }
48 install_shell_exports(home, port, quiet, false);
49 install_claude_env(home, port, quiet);
50 install_codex_env(home, port, quiet);
51 install_pi_env(home, port, quiet, false);
52 install_grok_env(home, port, quiet, false);
53 install_commandcode_env(home, port, quiet, false);
54}
55
56pub fn install_proxy_env_unchecked(home: &Path, port: u16, quiet: bool, force_endpoint: bool) {
59 install_shell_exports(home, port, quiet, force_endpoint);
62 if force_endpoint {
63 install_claude_env_inner(home, port, quiet, true);
64 } else {
65 install_claude_env(home, port, quiet);
66 }
67 install_codex_env(home, port, quiet);
68 install_pi_env(home, port, quiet, force_endpoint);
69 install_grok_env(home, port, quiet, force_endpoint);
70 install_commandcode_env(home, port, quiet, force_endpoint);
71}
72
73pub fn preview_proxy_cleanup(home: &Path) {
74 let settings_dir = crate::core::editor_registry::claude_state_dir(home);
75 let settings_path = settings_dir.join("settings.json");
76 if let Ok(content) = std::fs::read_to_string(&settings_path)
77 && content.contains("ANTHROPIC_BASE_URL")
78 {
79 let cfg = crate::core::config::Config::load();
80 if let Some(ref upstream) = cfg.proxy.anthropic_upstream {
81 println!(" Would restore ANTHROPIC_BASE_URL → {upstream} in Claude Code settings");
82 } else {
83 println!(" Would remove ANTHROPIC_BASE_URL from Claude Code settings");
84 }
85 }
86
87 let codex_path = crate::core::home::resolve_codex_config_path()
88 .unwrap_or_else(|| home.join(".codex/config.toml"));
89 if let Ok(content) = std::fs::read_to_string(codex_path)
90 && codex_config_has_local_proxy_entry(&content)
91 {
92 println!(" Would remove Codex proxy URL from config.toml");
93 }
94
95 let grok_path = home.join(".grok/config.toml");
96 if let Ok(content) = std::fs::read_to_string(grok_path)
97 && grok_config_has_local_proxy_entry(&content)
98 {
99 println!(" Would remove Grok proxy models_base_url from config.toml");
100 }
101
102 let cc_mcp = home.join(".commandcode/mcp.json");
103 if let Ok(content) = std::fs::read_to_string(cc_mcp)
104 && content.contains("lean-ctx")
105 {
106 println!(" Would remove lean-ctx from Command Code MCP (~/.commandcode/mcp.json)");
107 }
108}
109
110pub fn cleanup_stale_proxy_env(home: &Path) -> usize {
113 let cfg = crate::core::config::Config::load();
114 if cfg.proxy_enabled == Some(true) {
115 return 0;
116 }
117
118 let mut cleaned = 0;
119
120 let settings_dir = crate::core::editor_registry::claude_state_dir(home);
121 let settings_path = settings_dir.join("settings.json");
122 if let Ok(content) = std::fs::read_to_string(&settings_path)
123 && let Ok(mut doc) = crate::core::jsonc::parse_jsonc(&content)
124 && let Some(base_url) = doc
125 .get("env")
126 .and_then(|e| e.get("ANTHROPIC_BASE_URL"))
127 .and_then(|v| v.as_str())
128 .map(String::from)
129 && is_local_lean_ctx_url(&base_url)
130 && let Some(env_obj) = doc.get_mut("env").and_then(|e| e.as_object_mut())
131 {
132 if let Some(ref upstream) = cfg.proxy.anthropic_upstream {
133 env_obj.insert(
134 "ANTHROPIC_BASE_URL".to_string(),
135 serde_json::Value::String(upstream.clone()),
136 );
137 println!(" ✓ Restored ANTHROPIC_BASE_URL → {upstream} in Claude Code settings");
138 } else {
139 env_obj.remove("ANTHROPIC_BASE_URL");
140 if env_obj.is_empty() {
141 doc.as_object_mut().map(|o| o.remove("env"));
142 }
143 println!(" ✓ Removed stale ANTHROPIC_BASE_URL from Claude Code settings");
144 }
145 let out = serde_json::to_string_pretty(&doc).unwrap_or_default();
146 let _ = std::fs::write(&settings_path, out + "\n");
147 cleaned += 1;
148 }
149
150 let codex_path = crate::core::home::resolve_codex_config_path()
151 .unwrap_or_else(|| home.join(".codex/config.toml"));
152 if let Ok(content) = std::fs::read_to_string(&codex_path)
153 && codex_config_has_local_proxy_entry(&content)
154 {
155 let filtered = strip_codex_proxy_entries(&content);
156 let _ = std::fs::write(&codex_path, &filtered);
157 println!(" ✓ Removed stale Codex proxy URL from config.toml");
158 cleaned += 1;
159 }
160
161 let grok_path = home.join(".grok/config.toml");
162 if let Ok(content) = std::fs::read_to_string(&grok_path)
163 && grok_config_has_local_proxy_entry(&content)
164 {
165 let cleaned_toml = strip_grok_proxy_entries(&content);
166 let _ = std::fs::write(&grok_path, &cleaned_toml);
167 println!(" ✓ Removed stale Grok proxy models_base_url from config.toml");
168 cleaned += 1;
169 }
170
171 cleaned
172}
173
174pub fn has_stale_proxy_url(home: &Path) -> bool {
175 let cfg = crate::core::config::Config::load();
176 if cfg.proxy_enabled == Some(true) {
177 return false;
178 }
179
180 let settings_dir = crate::core::editor_registry::claude_state_dir(home);
181 let settings_path = settings_dir.join("settings.json");
182 let Ok(content) = std::fs::read_to_string(&settings_path) else {
183 return false;
184 };
185 let Ok(doc) = crate::core::jsonc::parse_jsonc(&content) else {
186 return false;
187 };
188
189 let base_url = doc
190 .get("env")
191 .and_then(|e| e.get("ANTHROPIC_BASE_URL"))
192 .and_then(|v| v.as_str())
193 .unwrap_or("");
194
195 is_local_lean_ctx_url(base_url)
196}
197
198pub fn uninstall_proxy_env(home: &Path, quiet: bool) {
199 for rc in &[home.join(".zshrc"), home.join(".bashrc")] {
200 let label = format!(
201 "proxy env from ~/{}",
202 rc.file_name().unwrap_or_default().to_string_lossy()
203 );
204 marked_block::remove_from_file(rc, PROXY_ENV_START, PROXY_ENV_END, quiet, &label);
205 }
206
207 let fish_config = home.join(".config/fish/config.fish");
208 if fish_config.exists() {
209 marked_block::remove_from_file(
210 &fish_config,
211 PROXY_ENV_START,
212 PROXY_ENV_END,
213 quiet,
214 "proxy env from ~/.config/fish/config.fish",
215 );
216 }
217
218 let ps_profile =
219 dirs::home_dir().map(|h| crate::shell::platform::resolve_powershell_profile_path(&h));
220 if let Some(ref ps) = ps_profile
221 && ps.exists()
222 {
223 marked_block::remove_from_file(
224 ps,
225 PROXY_ENV_START,
226 PROXY_ENV_END,
227 quiet,
228 "proxy env from PowerShell profile",
229 );
230 }
231
232 uninstall_claude_env(home, quiet);
233 uninstall_codex_env(home, quiet);
234 uninstall_pi_env(home, quiet);
235 uninstall_grok_env(home, quiet);
236 uninstall_commandcode_env(home, quiet);
237}