1use crate::hooks::to_bash_compatible_path;
2
3pub(crate) fn quiet_enabled() -> bool {
4 matches!(std::env::var("LEAN_CTX_QUIET"), Ok(v) if v.trim() == "1")
5}
6
7macro_rules! qprintln {
8 ($($t:tt)*) => {
9 if !quiet_enabled() {
10 println!($($t)*);
11 }
12 };
13}
14
15pub fn cmd_init(args: &[String]) {
16 let global = args.iter().any(|a| a == "--global" || a == "-g");
17 let project = args.iter().any(|a| a == "--project");
18 let dry_run = args.iter().any(|a| a == "--dry-run");
19 let no_hook = args.iter().any(|a| a == "--no-shell-hook")
20 || crate::core::config::Config::load().shell_hook_disabled_effective();
21
22 let explicit_mode = args
23 .windows(2)
24 .find(|w| w[0] == "--mode")
25 .and_then(|w| crate::hooks::HookMode::from_str_loose(&w[1]));
26
27 if args.windows(2).any(|w| w[0] == "--mode")
28 && !args
29 .windows(2)
30 .any(|w| w[0] == "--mode" && crate::hooks::HookMode::from_str_loose(&w[1]).is_some())
31 {
32 let bad = args
33 .windows(2)
34 .find(|w| w[0] == "--mode")
35 .map_or("?", |w| w[1].as_str());
36 eprintln!("Unknown hook mode: '{bad}'. Valid: mcp, hybrid, replace");
37 std::process::exit(1);
38 }
39
40 let agents: Vec<&str> = args
41 .windows(2)
42 .filter(|w| w[0] == "--agent")
43 .map(|w| w[1].as_str())
44 .collect();
45
46 if !agents.is_empty() {
47 let cwd = std::env::current_dir().unwrap_or_default();
48 for agent_name in &agents {
49 let mode =
50 explicit_mode.unwrap_or_else(|| crate::hooks::recommend_hook_mode(agent_name));
51 let result = crate::setup::setup_single_agent(agent_name, global, mode);
52 for name in &result.rules.injected {
53 qprintln!(" ✓ {name} rules injected");
54 }
55 for name in &result.rules.updated {
56 qprintln!(" ✓ {name} rules updated");
57 }
58 for name in &result.rules.already {
59 qprintln!(" ✓ {name} rules up-to-date");
60 }
61 if result.skill_installed {
62 qprintln!(" ✓ SKILL.md installed for {agent_name}");
63 }
64 if result.mcp_skipped {
65 qprintln!(" • MCP registration skipped for {agent_name} (auto_update_mcp=false)");
66 }
67 for e in &result.errors {
68 eprintln!(" ✗ {agent_name}: {e}");
69 }
70 if agent_name.eq_ignore_ascii_case("hermes") {
71 qprintln!("\n Beyond MCP, lean-ctx can be Hermes' active context engine");
72 qprintln!(" (replaces the built-in ContextCompressor). Install the plugin from");
73 qprintln!(" integrations/hermes-lean-ctx (scripts/install.sh), then set");
74 qprintln!(" context.engine: \"lean-ctx\" in ~/.hermes/config.yaml.");
75 }
76 if project {
77 crate::hooks::install_agent_project_hooks(agent_name, &cwd);
78 }
79 }
80 if !global {
81 crate::hooks::install_project_rules_for_agents(&agents);
82 }
83 qprintln!("\nRun 'lean-ctx gain' after using some commands to see your savings.");
84 return;
85 }
86
87 let eval_shell = args
88 .iter()
89 .find(|a| matches!(a.as_str(), "bash" | "zsh" | "fish" | "powershell" | "pwsh"));
90 if let Some(shell) = eval_shell
91 && !global
92 {
93 super::shell_init::print_hook_stdout(shell);
94 return;
95 }
96
97 let shell_name = std::env::var("SHELL").unwrap_or_default();
98 let is_zsh = shell_name.contains("zsh");
99 let is_fish = shell_name.contains("fish");
100 let is_powershell = cfg!(windows) && shell_name.is_empty();
101
102 let binary = crate::core::portable_binary::resolve_portable_binary();
103
104 if dry_run {
105 let rc = if is_powershell {
106 dirs::home_dir().map_or_else(
107 || "PowerShell profile".to_string(),
108 |h| {
109 crate::shell::platform::resolve_powershell_profile_path(&h)
110 .to_string_lossy()
111 .into_owned()
112 },
113 )
114 } else if is_fish {
115 "~/.config/fish/config.fish".to_string()
116 } else if is_zsh {
117 "~/.zshrc".to_string()
118 } else {
119 "~/.bashrc".to_string()
120 };
121 qprintln!("\nlean-ctx init --dry-run\n");
122 qprintln!(" Would modify: {rc}");
123 qprintln!(" Would backup: {rc}.lean-ctx.bak");
124 qprintln!(" Would alias: git npm pnpm yarn cargo docker docker-compose kubectl");
125 qprintln!(" gh pip pip3 ruff go golangci-lint eslint prettier tsc");
126 qprintln!(" curl wget php composer (24 commands + k)");
127 let data_dir = crate::core::data_dir::lean_ctx_data_dir().map_or_else(
128 |_| "~/.config/lean-ctx/".to_string(),
129 |p| p.to_string_lossy().to_string(),
130 );
131 qprintln!(" Would create: {data_dir}");
132 qprintln!(" Binary: {binary}");
133 qprintln!("\n Safety: aliases auto-fallback to original command if lean-ctx is removed.");
134 qprintln!("\n Run without --dry-run to apply.");
135 return;
136 }
137
138 if no_hook {
139 qprintln!("Shell hook disabled (--no-shell-hook or shell_hook_disabled config).");
140 qprintln!("MCP tools remain active. Set LEAN_CTX_NO_HOOK=1 to disable at runtime.");
141 } else if is_powershell {
142 super::shell_init::init_powershell(&binary);
143 } else {
144 let bash_binary = to_bash_compatible_path(&binary);
145 if is_fish {
146 super::shell_init::init_fish(&bash_binary);
147 } else {
148 super::shell_init::init_posix(is_zsh, &bash_binary);
149 }
150 }
151
152 if let Ok(lean_dir) = crate::core::data_dir::lean_ctx_data_dir()
153 && !lean_dir.exists()
154 {
155 let _ = std::fs::create_dir_all(&lean_dir);
156 qprintln!("Created {}", lean_dir.display());
157 }
158
159 let rc = if is_powershell {
160 "$PROFILE"
161 } else if is_fish {
162 "config.fish"
163 } else if is_zsh {
164 ".zshrc"
165 } else {
166 ".bashrc"
167 };
168
169 qprintln!("\nlean-ctx init complete (24 aliases installed)");
170 qprintln!();
171 qprintln!(" Disable temporarily: lean-ctx-off");
172 qprintln!(" Re-enable: lean-ctx-on");
173 qprintln!(" Check status: lean-ctx-status");
174 qprintln!(" Full uninstall: lean-ctx uninstall");
175 qprintln!(" Diagnose issues: lean-ctx doctor");
176 qprintln!(" Preview changes: lean-ctx init --global --dry-run");
177 qprintln!();
178 if is_powershell {
179 qprintln!(" Restart PowerShell or run: . {rc}");
180 } else {
181 qprintln!(" Restart your shell or run: source ~/{rc}");
182 }
183 qprintln!();
184 qprintln!("For AI tool integration: lean-ctx init --agent <tool> [--mode <mode>]");
185 qprintln!(" Supported: aider, amazonq, amp, antigravity, antigravity-cli, augment,");
186 qprintln!(" claude, cline, codex, continue, copilot, crush, cursor, emacs, gemini,");
187 qprintln!(" hermes, jetbrains, kiro, neovim, openclaw, opencode, pi, qoder,");
188 qprintln!(" qoderwork, qwen, roo, sublime, trae, verdent, vscode, windsurf, zed");
189 qprintln!(" Modes: mcp, hybrid, replace (auto-detected per agent, override with --mode)");
190}
191
192pub fn cmd_init_quiet(args: &[String]) {
193 unsafe { std::env::set_var("LEAN_CTX_QUIET", "1") };
196 cmd_init(args);
197 unsafe { std::env::remove_var("LEAN_CTX_QUIET") };
199}