Skip to main content

ferro_cli/commands/
claude_install.rs

1//! claude:install command - Install Ferro Claude Code skills
2
3use console::style;
4use std::fs;
5use std::path::PathBuf;
6
7/// Embedded skill files - these are compiled into the binary
8const SKILLS: &[(&str, &str)] = &[
9    ("help.md", include_str!("skills/help.md")),
10    ("info.md", include_str!("skills/info.md")),
11    ("routes.md", include_str!("skills/routes.md")),
12    ("route-explain.md", include_str!("skills/route-explain.md")),
13    ("model.md", include_str!("skills/model.md")),
14    ("models.md", include_str!("skills/models.md")),
15    ("controller.md", include_str!("skills/controller.md")),
16    ("middleware.md", include_str!("skills/middleware.md")),
17    ("db.md", include_str!("skills/db.md")),
18    ("test.md", include_str!("skills/test.md")),
19    ("serve.md", include_str!("skills/serve.md")),
20    ("new.md", include_str!("skills/new.md")),
21    ("tinker.md", include_str!("skills/tinker.md")),
22    ("diagnose.md", include_str!("skills/diagnose.md")),
23];
24
25pub fn run(force: bool, list: bool) {
26    if list {
27        list_skills();
28        return;
29    }
30
31    let target_dir = get_target_directory();
32
33    println!(
34        "{} Installing Ferro Claude Code skills...",
35        style("🦀").cyan()
36    );
37    println!();
38
39    // Create target directory
40    if let Err(e) = fs::create_dir_all(&target_dir) {
41        eprintln!(
42            "{} Failed to create directory {}: {}",
43            style("Error:").red().bold(),
44            target_dir.display(),
45            e
46        );
47        std::process::exit(1);
48    }
49
50    let mut installed = 0;
51    let mut skipped = 0;
52    let mut errors = 0;
53
54    for (filename, content) in SKILLS {
55        let target_path = target_dir.join(filename);
56
57        if target_path.exists() && !force {
58            println!(
59                "{} {} already exists, skipping (use --force to overwrite)",
60                style("→").dim(),
61                filename
62            );
63            skipped += 1;
64            continue;
65        }
66
67        match fs::write(&target_path, content) {
68            Ok(_) => {
69                let action = if target_path.exists() && force {
70                    "Updated"
71                } else {
72                    "Created"
73                };
74                println!("{} {} {}", style("✓").green(), action, filename);
75                installed += 1;
76            }
77            Err(e) => {
78                eprintln!("{} Failed to write {}: {}", style("✗").red(), filename, e);
79                errors += 1;
80            }
81        }
82    }
83
84    println!();
85
86    if errors > 0 {
87        eprintln!(
88            "{} Completed with errors: {} installed, {} skipped, {} failed",
89            style("âš ").yellow(),
90            installed,
91            skipped,
92            errors
93        );
94        std::process::exit(1);
95    }
96
97    println!(
98        "{}",
99        style("Ferro Claude Code skills installed successfully!")
100            .green()
101            .bold()
102    );
103    println!();
104    println!("Location: {}", style(target_dir.display()).cyan());
105    println!();
106    println!("Available commands:");
107    println!(
108        "  {} - Show all available Ferro commands",
109        style("/ferro:help").yellow()
110    );
111    println!("  {} - Project information", style("/ferro:info").yellow());
112    println!("  {} - List all routes", style("/ferro:routes").yellow());
113    println!("  {} - Generate a model", style("/ferro:model").yellow());
114    println!("  {} - Database operations", style("/ferro:db").yellow());
115    println!();
116    println!(
117        "{}",
118        style("Tip: Run /ferro:help in Claude Code to see all commands").dim()
119    );
120}
121
122fn list_skills() {
123    println!("{} Ferro Claude Code Skills", style("🦀").cyan());
124    println!();
125
126    for (filename, _) in SKILLS {
127        let name = filename.trim_end_matches(".md");
128        println!("  {} /ferro:{}", style("•").dim(), style(name).yellow());
129    }
130
131    println!();
132    println!("Total: {} skills", SKILLS.len());
133}
134
135fn get_target_directory() -> PathBuf {
136    // Get home directory
137    let home = dirs::home_dir().unwrap_or_else(|| {
138        eprintln!(
139            "{} Could not determine home directory",
140            style("Error:").red().bold()
141        );
142        std::process::exit(1);
143    });
144
145    home.join(".claude").join("commands").join("ferro")
146}