Skip to main content

lean_ctx/cli/
init_cmd.rs

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");
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            for e in &result.errors {
65                eprintln!("  ✗ {agent_name}: {e}");
66            }
67            if project {
68                crate::hooks::install_agent_project_hooks(agent_name, &cwd);
69            }
70        }
71        if !global {
72            crate::hooks::install_project_rules_for_agents(&agents);
73        }
74        qprintln!("\nRun 'lean-ctx gain' after using some commands to see your savings.");
75        return;
76    }
77
78    let eval_shell = args
79        .iter()
80        .find(|a| matches!(a.as_str(), "bash" | "zsh" | "fish" | "powershell" | "pwsh"));
81    if let Some(shell) = eval_shell {
82        if !global {
83            super::shell_init::print_hook_stdout(shell);
84            return;
85        }
86    }
87
88    let shell_name = std::env::var("SHELL").unwrap_or_default();
89    let is_zsh = shell_name.contains("zsh");
90    let is_fish = shell_name.contains("fish");
91    let is_powershell = cfg!(windows) && shell_name.is_empty();
92
93    let binary = crate::core::portable_binary::resolve_portable_binary();
94
95    if dry_run {
96        let rc = if is_powershell {
97            "Documents/PowerShell/Microsoft.PowerShell_profile.ps1".to_string()
98        } else if is_fish {
99            "~/.config/fish/config.fish".to_string()
100        } else if is_zsh {
101            "~/.zshrc".to_string()
102        } else {
103            "~/.bashrc".to_string()
104        };
105        qprintln!("\nlean-ctx init --dry-run\n");
106        qprintln!("  Would modify:  {rc}");
107        qprintln!("  Would backup:  {rc}.lean-ctx.bak");
108        qprintln!("  Would alias:   git npm pnpm yarn cargo docker docker-compose kubectl");
109        qprintln!("                 gh pip pip3 ruff go golangci-lint eslint prettier tsc");
110        qprintln!("                 curl wget php composer (24 commands + k)");
111        let data_dir = crate::core::data_dir::lean_ctx_data_dir().map_or_else(
112            |_| "~/.config/lean-ctx/".to_string(),
113            |p| p.to_string_lossy().to_string(),
114        );
115        qprintln!("  Would create:  {data_dir}");
116        qprintln!("  Binary:        {binary}");
117        qprintln!("\n  Safety: aliases auto-fallback to original command if lean-ctx is removed.");
118        qprintln!("\n  Run without --dry-run to apply.");
119        return;
120    }
121
122    if no_hook {
123        qprintln!("Shell hook disabled (--no-shell-hook or shell_hook_disabled config).");
124        qprintln!("MCP tools remain active. Set LEAN_CTX_NO_HOOK=1 to disable at runtime.");
125    } else if is_powershell {
126        super::shell_init::init_powershell(&binary);
127    } else {
128        let bash_binary = to_bash_compatible_path(&binary);
129        if is_fish {
130            super::shell_init::init_fish(&bash_binary);
131        } else {
132            super::shell_init::init_posix(is_zsh, &bash_binary);
133        }
134    }
135
136    if let Ok(lean_dir) = crate::core::data_dir::lean_ctx_data_dir() {
137        if !lean_dir.exists() {
138            let _ = std::fs::create_dir_all(&lean_dir);
139            qprintln!("Created {}", lean_dir.display());
140        }
141    }
142
143    let rc = if is_powershell {
144        "$PROFILE"
145    } else if is_fish {
146        "config.fish"
147    } else if is_zsh {
148        ".zshrc"
149    } else {
150        ".bashrc"
151    };
152
153    qprintln!("\nlean-ctx init complete (24 aliases installed)");
154    qprintln!();
155    qprintln!("  Disable temporarily:  lean-ctx-off");
156    qprintln!("  Re-enable:            lean-ctx-on");
157    qprintln!("  Check status:         lean-ctx-status");
158    qprintln!("  Full uninstall:       lean-ctx uninstall");
159    qprintln!("  Diagnose issues:      lean-ctx doctor");
160    qprintln!("  Preview changes:      lean-ctx init --global --dry-run");
161    qprintln!();
162    if is_powershell {
163        qprintln!("  Restart PowerShell or run: . {rc}");
164    } else {
165        qprintln!("  Restart your shell or run: source ~/{rc}");
166    }
167    qprintln!();
168    qprintln!("For AI tool integration: lean-ctx init --agent <tool> [--mode <mode>]");
169    qprintln!("  Supported: aider, amazonq, amp, antigravity, claude, cline, codex,");
170    qprintln!("    continue, copilot, crush, cursor, emacs, gemini, hermes, jetbrains,");
171    qprintln!("    kiro, neovim, opencode, pi, qoder, qoderwork, qwen, roo, sublime,");
172    qprintln!("    trae, verdent, vscode, windsurf, zed");
173    qprintln!("  Modes: mcp, hybrid  (auto-detected per agent, override with --mode)");
174}
175
176pub fn cmd_init_quiet(args: &[String]) {
177    std::env::set_var("LEAN_CTX_QUIET", "1");
178    cmd_init(args);
179    std::env::remove_var("LEAN_CTX_QUIET");
180}