mi6_cli/commands/
enable.rs1use 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
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 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 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 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 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}