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 {
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 for framework in &enable_result.frameworks {
110 eprintln!(
111 "Enabling mi6 for {}... {}done{}",
112 framework.name, colors.green, colors.reset
113 );
114 let install_label = if framework.name == "claude" {
116 "Installed plugin to:"
117 } else {
118 "Installed hooks to:"
119 };
120 eprintln!(
121 " {} {}{}{}",
122 install_label,
123 colors.bold,
124 framework.settings_path.display(),
125 colors.reset
126 );
127 }
128
129 if cli.settings_local {
130 for framework in &enable_result.frameworks {
131 eprintln!(
132 " {}Note:{} Add '{}' to your project's .gitignore",
133 colors.yellow,
134 colors.reset,
135 framework.settings_path.display()
136 );
137 }
138 }
139
140 if cli.otel {
141 eprintln!(
142 " {}OpenTelemetry configured{} on port {} for automatic token tracking.",
143 colors.cyan, colors.reset, cli.otel_port
144 );
145 }
146 }
147
148 Ok(result)
149}
150
151fn map_enable_error(e: mi6_core::EnableError) -> anyhow::Error {
152 use mi6_core::EnableError;
153 match e {
154 EnableError::NoFrameworks { supported } => {
155 anyhow::anyhow!(
156 "No supported AI coding frameworks detected.\n\
157 Supported frameworks: {}\n\
158 Install one first, or specify explicitly: mi6 enable claude",
159 supported.join(", ")
160 )
161 }
162 EnableError::UnknownFramework(name) => {
163 anyhow::anyhow!("unknown framework: {}", name)
164 }
165 other => anyhow::anyhow!("{}", other),
166 }
167}