use console::style;
use std::fs;
use std::path::Path;
use crate::templates;
pub fn run(editor: Option<String>) {
if !Path::new("Cargo.toml").exists() {
eprintln!("{} Cargo.toml not found", style("Error:").red().bold());
eprintln!(
"{}",
style("Make sure you're in a Ferro project root directory.").dim()
);
std::process::exit(1);
}
let target_editor = editor.unwrap_or_else(detect_editor);
println!(
"{} Installing AI development boost for {}...",
style("⚡").cyan(),
style(&target_editor).yellow()
);
println!();
generate_mcp_config(&target_editor);
generate_ai_guidelines(&target_editor);
println!();
println!(
"{}",
style("AI development boost installed successfully!")
.green()
.bold()
);
println!();
match target_editor.as_str() {
"cursor" => {
println!("To activate MCP in Cursor:");
println!(" 1. Open Command Palette (Cmd+Shift+P / Ctrl+Shift+P)");
println!(" 2. Search for 'Reload Window'");
println!(" 3. The Ferro MCP tools will now be available");
}
"claude" => {
println!("MCP configuration written to {}", style(".mcp.json").cyan());
println!("CLAUDE.md updated with Ferro framework guidelines.");
println!();
println!("Claude Code will automatically use these configurations.");
}
"vscode" => {
println!(
"AI guidelines written to {}",
style(".ai/guidelines/").cyan()
);
println!("GitHub Copilot will use these guidelines for context.");
}
_ => {
println!("Configuration files have been generated.");
}
}
println!();
}
fn detect_editor() -> String {
if Path::new(".cursor").exists() {
return "cursor".to_string();
}
if Path::new("CLAUDE.md").exists() || std::env::var("CLAUDE_CODE").is_ok() {
return "claude".to_string();
}
if Path::new(".vscode").exists() {
return "vscode".to_string();
}
"claude".to_string()
}
fn generate_mcp_config(editor: &str) {
let config_path = match editor {
"cursor" => {
fs::create_dir_all(".cursor").ok();
".cursor/mcp.json"
}
_ => {
".mcp.json"
}
};
let ferro_command = find_ferro_binary();
let config_content = format!(
r#"{{
"mcpServers": {{
"ferro": {{
"command": "{}",
"args": ["mcp"],
"env": {{}}
}}
}}
}}
"#,
ferro_command.replace('\\', "\\\\").replace('"', "\\\"")
);
if Path::new(config_path).exists() {
println!(
"{} {} already exists, skipping",
style("→").dim(),
config_path
);
} else {
if let Err(e) = fs::write(config_path, &config_content) {
eprintln!(
"{} Failed to write {}: {}",
style("Error:").red().bold(),
config_path,
e
);
return;
}
println!("{} Created {}", style("✓").green(), config_path);
}
}
fn find_ferro_binary() -> String {
if let Ok(output) = std::process::Command::new("which").arg("ferro").output() {
if output.status.success() {
if let Ok(path) = String::from_utf8(output.stdout) {
let path = path.trim();
if !path.is_empty() {
return path.to_string();
}
}
}
}
#[cfg(windows)]
if let Ok(output) = std::process::Command::new("where").arg("ferro").output() {
if output.status.success() {
if let Ok(path) = String::from_utf8(output.stdout) {
if let Some(first_line) = path.lines().next() {
return first_line.to_string();
}
}
}
}
if let Ok(current_exe) = std::env::current_exe() {
if let Some(exe_dir) = current_exe.parent() {
let ferro_in_same_dir = exe_dir.join("ferro");
if ferro_in_same_dir.exists() {
return ferro_in_same_dir.to_string_lossy().to_string();
}
}
if current_exe
.file_name()
.map(|n| n == "ferro")
.unwrap_or(false)
{
return current_exe.to_string_lossy().to_string();
}
}
"ferro".to_string()
}
fn generate_ai_guidelines(editor: &str) {
let guidelines_dir = Path::new(".ai/guidelines");
if let Err(e) = fs::create_dir_all(guidelines_dir) {
eprintln!(
"{} Failed to create .ai/guidelines: {}",
style("Error:").red().bold(),
e
);
return;
}
let ferro_md_path = guidelines_dir.join("ferro.md");
if !ferro_md_path.exists() {
let content = templates::ferro_guidelines_template();
if let Err(e) = fs::write(&ferro_md_path, content) {
eprintln!(
"{} Failed to write ferro.md: {}",
style("Error:").red().bold(),
e
);
} else {
println!("{} Created .ai/guidelines/ferro.md", style("✓").green());
}
} else {
println!(
"{} .ai/guidelines/ferro.md already exists, skipping",
style("→").dim()
);
}
match editor {
"cursor" => {
let cursor_rules_path = Path::new(".cursorrules");
if !cursor_rules_path.exists() {
let content = templates::cursor_rules_template();
if let Err(e) = fs::write(cursor_rules_path, content) {
eprintln!(
"{} Failed to write .cursorrules: {}",
style("Error:").red().bold(),
e
);
} else {
println!("{} Created .cursorrules", style("✓").green());
}
} else {
println!("{} .cursorrules already exists, skipping", style("→").dim());
}
}
"claude" => {
let claude_md_path = Path::new("CLAUDE.md");
if !claude_md_path.exists() {
let content = templates::claude_md_template();
if let Err(e) = fs::write(claude_md_path, content) {
eprintln!(
"{} Failed to write CLAUDE.md: {}",
style("Error:").red().bold(),
e
);
} else {
println!("{} Created CLAUDE.md", style("✓").green());
}
} else {
let existing = fs::read_to_string(claude_md_path).unwrap_or_default();
if !existing.contains("Ferro Framework") {
let ferro_section = templates::claude_md_ferro_section();
if let Err(e) = fs::write(
claude_md_path,
format!("{}\n\n{}", existing.trim(), ferro_section),
) {
eprintln!(
"{} Failed to update CLAUDE.md: {}",
style("Error:").red().bold(),
e
);
} else {
println!(
"{} Updated CLAUDE.md with Ferro guidelines",
style("✓").green()
);
}
} else {
println!(
"{} CLAUDE.md already contains Ferro guidelines, skipping",
style("→").dim()
);
}
}
}
"vscode" => {
let copilot_path = guidelines_dir.join("copilot.md");
if !copilot_path.exists() {
let content = templates::copilot_instructions_template();
if let Err(e) = fs::write(&copilot_path, content) {
eprintln!(
"{} Failed to write copilot.md: {}",
style("Error:").red().bold(),
e
);
} else {
println!("{} Created .ai/guidelines/copilot.md", style("✓").green());
}
} else {
println!(
"{} .ai/guidelines/copilot.md already exists, skipping",
style("→").dim()
);
}
}
_ => {}
}
}