ferro_cli/commands/
claude_install.rs1use console::style;
4use std::fs;
5use std::path::PathBuf;
6
7const 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 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 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}