mi6_cli/commands/
enable.rs1use 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!(
126 "Enabling mi6 for {}... {}done{}",
127 framework.name, colors.green, colors.reset
128 );
129 let install_label = if framework.name == "claude" {
131 "Installed plugin to:"
132 } else {
133 "Installed hooks to:"
134 };
135 eprintln!(
136 " {} {}{}{}",
137 install_label,
138 colors.bold,
139 framework.settings_path.display(),
140 colors.reset
141 );
142 }
143
144 if cli.settings_local {
145 for framework in &enable_result.frameworks {
146 eprintln!(
147 " {}Note:{} Add '{}' to your project's .gitignore",
148 colors.yellow,
149 colors.reset,
150 framework.settings_path.display()
151 );
152 }
153 }
154
155 if cli.otel {
156 eprintln!(
157 " {}OpenTelemetry configured{} on port {} for automatic token tracking.",
158 colors.cyan, colors.reset, cli.otel_port
159 );
160 }
161 }
162
163 if has_failures {
165 let failed_names: Vec<&str> = enable_result
166 .failures
167 .iter()
168 .map(|f| f.name.as_str())
169 .collect();
170 let count = enable_result.failures.len();
171 let framework_word = if count == 1 {
172 "framework"
173 } else {
174 "frameworks"
175 };
176 bail!(
177 "{} {} failed. Fix the errors and retry with: mi6 enable {}",
178 count,
179 framework_word,
180 failed_names.join(" ")
181 );
182 }
183 }
184
185 Ok(result)
186}
187
188fn display_partial_success_summary(
190 enable_result: &mi6_core::EnableResult,
191 colors: &StderrColors,
192 settings_local: bool,
193 otel: bool,
194 otel_port: u16,
195) {
196 eprintln!();
198 eprintln!("{}Enabled:{}", colors.green, colors.reset);
199 for framework in &enable_result.frameworks {
200 let install_label = if framework.name == "claude" {
201 "plugin"
202 } else {
203 "hooks"
204 };
205 eprintln!(
206 " {} -> {} ({})",
207 framework.name,
208 framework.settings_path.display(),
209 install_label
210 );
211 }
212
213 eprintln!();
215 eprintln!("{}Failed:{}", colors.red, colors.reset);
216 for failure in &enable_result.failures {
217 eprintln!(" {} -> {}", failure.name, failure.error);
218 }
219 eprintln!();
220
221 if settings_local {
223 for framework in &enable_result.frameworks {
224 eprintln!(
225 "{}Note:{} Add '{}' to your project's .gitignore",
226 colors.yellow,
227 colors.reset,
228 framework.settings_path.display()
229 );
230 }
231 }
232
233 if otel && !enable_result.frameworks.is_empty() {
234 eprintln!(
235 "{}OpenTelemetry configured{} on port {} for automatic token tracking.",
236 colors.cyan, colors.reset, otel_port
237 );
238 }
239}
240
241fn map_enable_error(e: mi6_core::EnableError) -> anyhow::Error {
242 use mi6_core::EnableError;
243 match e {
244 EnableError::NoFrameworks { supported } => {
245 anyhow::anyhow!(
246 "No supported AI coding frameworks detected.\n\
247 Supported frameworks: {}\n\
248 Install one first, or specify explicitly: mi6 enable claude",
249 supported.join(", ")
250 )
251 }
252 EnableError::UnknownFramework(name) => {
253 anyhow::anyhow!("unknown framework: {}", name)
254 }
255 other => anyhow::anyhow!("{}", other),
256 }
257}