1use std::path::PathBuf;
4
5use anyhow::{Context, Result, bail};
6
7use mi6_core::{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
12pub struct EnableResult {
14 pub should_init_db: bool,
15 pub db_path: Option<PathBuf>,
16}
17
18pub 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
31pub fn run_enable(cli: EnableCliOptions) -> Result<EnableResult> {
33 let colors = StderrColors::new();
34 let auto_detecting = cli.frameworks.is_empty();
35
36 let opts = InitOptions::for_frameworks(cli.frameworks)
37 .local(cli.local)
38 .settings_local(cli.settings_local)
39 .otel(cli.otel)
40 .otel_port(cli.otel_port)
41 .remove_otel(cli.no_otel)
42 .db_only(cli.db_only)
43 .hooks_only(cli.hooks_only);
44
45 let mut result = EnableResult {
46 should_init_db: !cli.hooks_only,
47 db_path: None,
48 };
49
50 if !cli.hooks_only {
51 result.db_path =
52 Some(mi6_core::Config::db_path().context("failed to determine database path")?);
53 }
54
55 if cli.otel && is_server_running(cli.otel_port) && !is_mi6_server(cli.otel_port) {
57 let available_port = find_available_port(cli.otel_port.saturating_add(1));
58 bail!(
59 "port {} is in use by another service\n\n\
60 Try using a different port:\n \
61 mi6 enable --otel --otel-port {}",
62 cli.otel_port,
63 available_port
64 );
65 }
66
67 if cli.print {
69 let previews = preview_enable(&opts).map_err(map_enable_error)?;
70 for preview in previews {
71 match preview.config_format {
72 ConfigFormat::Json => {
73 println!(
74 "{}",
75 serde_json::to_string_pretty(&preview.hooks_config)
76 .context("failed to serialize hooks")?
77 );
78 }
79 ConfigFormat::Toml => {
80 println!(
81 "{}",
82 json_to_toml_string(&preview.hooks_config)
83 .context("failed to serialize hooks")?
84 );
85 }
86 }
87 }
88 return Ok(result);
89 }
90
91 if !cli.db_only {
93 let enable_result = mi6_core::enable(opts).map_err(map_enable_error)?;
94
95 if auto_detecting && !enable_result.frameworks.is_empty() {
96 let names: Vec<&str> = enable_result
97 .frameworks
98 .iter()
99 .map(|f| f.name.as_str())
100 .collect();
101 eprintln!(
102 "{}Detected frameworks:{} {}",
103 colors.cyan,
104 colors.reset,
105 names.join(", ")
106 );
107 }
108
109 let has_failures = !enable_result.failures.is_empty();
111 let has_successes = !enable_result.frameworks.is_empty();
112
113 if has_failures && has_successes {
114 display_partial_success_summary(
116 &enable_result,
117 &colors,
118 cli.settings_local,
119 cli.otel,
120 cli.otel_port,
121 );
122 } else if has_successes {
123 for framework in &enable_result.frameworks {
125 eprintln!("Enabling mi6 for {}...", framework.name);
126
127 for cmd in &framework.commands_run {
129 eprintln!(
130 " Running: {}{}{}... {}done{}",
131 colors.bold_white, cmd, colors.reset, colors.bold_green, colors.reset
132 );
133 }
134
135 let install_label = if framework.name == "claude" {
137 "Installing plugin to"
138 } else {
139 "Installing hooks to"
140 };
141 eprintln!(
142 " {}: {}{}{}... {}done{}",
143 install_label,
144 colors.bold_white,
145 framework.settings_path.display(),
146 colors.reset,
147 colors.bold_green,
148 colors.reset
149 );
150 }
151
152 if cli.settings_local {
153 for framework in &enable_result.frameworks {
154 eprintln!(
155 " {}Note:{} Add '{}' to your project's .gitignore",
156 colors.yellow,
157 colors.reset,
158 framework.settings_path.display()
159 );
160 }
161 }
162
163 if cli.otel {
164 eprintln!(
165 " {}OpenTelemetry configured{} on port {} for automatic token tracking.",
166 colors.cyan, colors.reset, cli.otel_port
167 );
168 }
169 }
170
171 if has_failures {
173 let failed_names: Vec<&str> = enable_result
174 .failures
175 .iter()
176 .map(|f| f.name.as_str())
177 .collect();
178 let count = enable_result.failures.len();
179 let framework_word = if count == 1 {
180 "framework"
181 } else {
182 "frameworks"
183 };
184 bail!(
185 "{} {} failed. Fix the errors and retry with: mi6 enable {}",
186 count,
187 framework_word,
188 failed_names.join(" ")
189 );
190 }
191 }
192
193 Ok(result)
194}
195
196fn display_partial_success_summary(
198 enable_result: &mi6_core::EnableResult,
199 colors: &StderrColors,
200 settings_local: bool,
201 otel: bool,
202 otel_port: u16,
203) {
204 eprintln!();
206 eprintln!("{}Enabled:{}", colors.green, colors.reset);
207 for framework in &enable_result.frameworks {
208 let install_label = if framework.name == "claude" {
209 "plugin"
210 } else {
211 "hooks"
212 };
213 eprintln!(" {}", framework.name);
214 for cmd in &framework.commands_run {
216 eprintln!(
217 " Running: {}{}{}... {}done{}",
218 colors.bold_white, cmd, colors.reset, colors.bold_green, colors.reset
219 );
220 }
221 eprintln!(
222 " Installing {} to: {}{}{}... {}done{}",
223 install_label,
224 colors.bold_white,
225 framework.settings_path.display(),
226 colors.reset,
227 colors.bold_green,
228 colors.reset
229 );
230 }
231
232 eprintln!();
234 eprintln!("{}Failed:{}", colors.red, colors.reset);
235 for failure in &enable_result.failures {
236 eprintln!(" {} -> {}", failure.name, failure.error);
237 }
238 eprintln!();
239
240 if settings_local {
242 for framework in &enable_result.frameworks {
243 eprintln!(
244 "{}Note:{} Add '{}' to your project's .gitignore",
245 colors.yellow,
246 colors.reset,
247 framework.settings_path.display()
248 );
249 }
250 }
251
252 if otel && !enable_result.frameworks.is_empty() {
253 eprintln!(
254 "{}OpenTelemetry configured{} on port {} for automatic token tracking.",
255 colors.cyan, colors.reset, otel_port
256 );
257 }
258}
259
260fn map_enable_error(e: mi6_core::EnableError) -> anyhow::Error {
261 use mi6_core::EnableError;
262 match e {
263 EnableError::NoFrameworks { supported } => {
264 anyhow::anyhow!(
265 "No supported AI coding frameworks detected.\n\
266 Supported frameworks: {}\n\
267 Install one first, or specify explicitly: mi6 enable claude",
268 supported.join(", ")
269 )
270 }
271 EnableError::UnknownFramework(name) => {
272 anyhow::anyhow!("unknown framework: {}", name)
273 }
274 other => anyhow::anyhow!("{}", other),
275 }
276}