mi6_cli/commands/
enable.rs

1//! Enable command - install mi6 hooks for AI coding frameworks.
2
3use std::path::PathBuf;
4
5use anyhow::{Context, Result, bail};
6
7use mi6_core::{Config, ConfigFormat, InitOptions, json_to_toml_string, preview_enable};
8use mi6_otel_server::{find_available_port, is_mi6_server, is_server_running};
9
10use crate::display::StderrColors;
11
12/// Result of running enable - used by the binary to determine what storage to create.
13pub struct EnableResult {
14    pub should_init_db: bool,
15    pub db_path: Option<PathBuf>,
16}
17
18/// CLI options for the enable command.
19pub struct EnableCliOptions {
20    pub frameworks: Vec<String>,
21    pub local: bool,
22    pub settings_local: bool,
23    pub print: bool,
24    pub db_only: bool,
25    pub hooks_only: bool,
26    pub otel: bool,
27    pub otel_port: u16,
28    pub no_otel: bool,
29}
30
31/// Run the enable command.
32pub fn run_enable(cli: EnableCliOptions) -> Result<EnableResult> {
33    let colors = StderrColors::new();
34    let config = Config::load().context("failed to load config")?;
35    let auto_detecting = cli.frameworks.is_empty();
36
37    let opts = InitOptions::for_frameworks(cli.frameworks)
38        .local(cli.local)
39        .settings_local(cli.settings_local)
40        .otel(cli.otel)
41        .otel_port(cli.otel_port)
42        .remove_otel(cli.no_otel)
43        .db_only(cli.db_only)
44        .hooks_only(cli.hooks_only);
45
46    let mut result = EnableResult {
47        should_init_db: !cli.hooks_only,
48        db_path: None,
49    };
50
51    if !cli.hooks_only {
52        result.db_path = Some(
53            config
54                .db_path()
55                .context("failed to determine database path")?,
56        );
57    }
58
59    // Check for port collision if otel is enabled
60    if cli.otel && is_server_running(cli.otel_port) && !is_mi6_server(cli.otel_port) {
61        let available_port = find_available_port(cli.otel_port.saturating_add(1));
62        bail!(
63            "port {} is in use by another service\n\n\
64             Try using a different port:\n  \
65             mi6 enable --otel --otel-port {}",
66            cli.otel_port,
67            available_port
68        );
69    }
70
71    // Handle print mode
72    if cli.print {
73        let previews = preview_enable(&opts).map_err(map_enable_error)?;
74        for preview in previews {
75            match preview.config_format {
76                ConfigFormat::Json => {
77                    println!(
78                        "{}",
79                        serde_json::to_string_pretty(&preview.hooks_config)
80                            .context("failed to serialize hooks")?
81                    );
82                }
83                ConfigFormat::Toml => {
84                    println!(
85                        "{}",
86                        json_to_toml_string(&preview.hooks_config)
87                            .context("failed to serialize hooks")?
88                    );
89                }
90            }
91        }
92        return Ok(result);
93    }
94
95    // Install hooks unless db_only
96    if !cli.db_only {
97        let enable_result = mi6_core::enable(opts).map_err(map_enable_error)?;
98
99        if auto_detecting {
100            let names: Vec<&str> = enable_result
101                .frameworks
102                .iter()
103                .map(|f| f.name.as_str())
104                .collect();
105            eprintln!(
106                "{}Detected frameworks:{} {}",
107                colors.cyan,
108                colors.reset,
109                names.join(", ")
110            );
111        }
112
113        for framework in &enable_result.frameworks {
114            eprintln!(
115                "{}Enabling{} mi6 for {}{}{}... {}done{}",
116                colors.green,
117                colors.reset,
118                colors.bold,
119                framework.name,
120                colors.reset,
121                colors.green,
122                colors.reset
123            );
124            eprintln!(
125                "  {}Installed hooks to:{} {}",
126                colors.cyan,
127                colors.reset,
128                framework.settings_path.display()
129            );
130        }
131
132        if cli.settings_local {
133            for framework in &enable_result.frameworks {
134                eprintln!(
135                    "  {}Note:{} Add '{}' to your project's .gitignore",
136                    colors.yellow,
137                    colors.reset,
138                    framework.settings_path.display()
139                );
140            }
141        }
142
143        if cli.otel {
144            eprintln!(
145                "  {}OpenTelemetry configured{} on port {} for automatic token tracking.",
146                colors.cyan, colors.reset, cli.otel_port
147            );
148        }
149    }
150
151    Ok(result)
152}
153
154fn map_enable_error(e: mi6_core::EnableError) -> anyhow::Error {
155    use mi6_core::EnableError;
156    match e {
157        EnableError::NoFrameworks { supported } => {
158            anyhow::anyhow!(
159                "No supported AI coding frameworks detected.\n\
160                 Supported frameworks: {}\n\
161                 Install one first, or specify explicitly: mi6 enable claude",
162                supported.join(", ")
163            )
164        }
165        EnableError::UnknownFramework(name) => {
166            anyhow::anyhow!("unknown framework: {}", name)
167        }
168        other => anyhow::anyhow!("{}", other),
169    }
170}