Skip to main content

magick_mcp/
cli.rs

1use clap::{Parser, Subcommand, ValueEnum};
2use std::path::PathBuf;
3
4/// Magick MCP - A Model Context Protocol server
5#[derive(Parser, Debug)]
6#[command(name = "magick-mcp")]
7#[command(version = env!("CARGO_PKG_VERSION"))]
8pub struct Args {
9    #[command(subcommand)]
10    pub command: Commands,
11}
12
13#[derive(Subcommand, Debug)]
14pub enum Commands {
15    /// Check if ImageMagick is installed
16    Check,
17    /// Start the MCP server
18    Mcp,
19    /// Install magick-mcp to MCP client configuration
20    Install {
21        /// Client type to install for
22        #[arg(long, value_enum, default_value = "both")]
23        r#type: ClientTypeArg,
24    },
25    /// Execute an ImageMagick command
26    Magick {
27        /// ImageMagick command arguments (e.g., "test.png -negate out.png")
28        command: String,
29    },
30    /// Manage magick functions
31    Func {
32        #[command(subcommand)]
33        func_command: FuncCommands,
34    },
35}
36
37#[derive(Subcommand, Debug)]
38pub enum FuncCommands {
39    /// List all available functions
40    List,
41    /// Print a function in human-readable format
42    Print {
43        /// Name of the function to print
44        name: String,
45    },
46    /// Execute a function by name
47    Execute {
48        /// Name of the function to execute
49        name: String,
50        /// Input value to replace $input placeholders in commands
51        #[arg(long)]
52        input: Option<String>,
53    },
54    /// Save a function from a JSON file
55    Save {
56        /// Path to the JSON file containing the function
57        #[arg(long)]
58        file: PathBuf,
59    },
60}
61
62#[derive(Debug, Clone, Copy, ValueEnum)]
63pub enum ClientTypeArg {
64    Cursor,
65    Claude,
66    Both,
67}
68
69impl From<ClientTypeArg> for crate::ClientType {
70    fn from(arg: ClientTypeArg) -> Self {
71        match arg {
72            ClientTypeArg::Cursor => crate::ClientType::Cursor,
73            ClientTypeArg::Claude => crate::ClientType::Claude,
74            ClientTypeArg::Both => crate::ClientType::Both,
75        }
76    }
77}
78
79/// Handle command execution
80pub fn handle_command(command: Commands) {
81    match command {
82        Commands::Check => match crate::check() {
83            Ok(output) => {
84                println!("{output}");
85                std::process::exit(0);
86            }
87            Err(e) => {
88                eprintln!("Error: {e}");
89                std::process::exit(1);
90            }
91        },
92        Commands::Mcp => {
93            let rt = tokio::runtime::Runtime::new().expect("Failed to create tokio runtime");
94            if let Err(e) = rt.block_on(crate::mcp::run_server()) {
95                eprintln!("Error running MCP server: {e}");
96                std::process::exit(1);
97            }
98        }
99        Commands::Install { r#type } => {
100            let client_type: crate::ClientType = r#type.into();
101            let config_paths = match crate::ConfigPaths::from_home_dir() {
102                Ok(paths) => paths,
103                Err(e) => {
104                    eprintln!("Error getting config paths: {e}");
105                    std::process::exit(1);
106                }
107            };
108            match crate::install(client_type, config_paths) {
109                Ok(_) => {
110                    println!("Successfully installed magick-mcp to MCP configuration");
111                    std::process::exit(0);
112                }
113                Err(e) => {
114                    eprintln!("Error installing magick-mcp: {e}");
115                    std::process::exit(1);
116                }
117            }
118        }
119        Commands::Magick { command } => match crate::magick(&command, None) {
120            Ok(output) => {
121                println!("{output}");
122                std::process::exit(0);
123            }
124            Err(e) => {
125                eprintln!("Error executing magick command: {e}");
126                std::process::exit(1);
127            }
128        },
129        Commands::Func { func_command } => handle_func_command(func_command),
130    }
131}
132
133/// Handle function subcommand execution
134fn handle_func_command(func_command: FuncCommands) {
135    match func_command {
136        FuncCommands::List => match crate::list_functions() {
137            Ok(functions) => {
138                if functions.is_empty() {
139                    println!("No functions found");
140                } else {
141                    for name in functions {
142                        println!("{name}");
143                    }
144                }
145                std::process::exit(0);
146            }
147            Err(e) => {
148                eprintln!("Error listing functions: {e}");
149                std::process::exit(1);
150            }
151        },
152        FuncCommands::Print { name } => match crate::load_function(&name) {
153            Ok(function) => {
154                println!("Name: {}", function.name);
155                println!("Commands:");
156                for command in &function.commands {
157                    println!("  - {command}");
158                }
159                std::process::exit(0);
160            }
161            Err(e) => {
162                eprintln!("Error loading function '{name}': {e}");
163                std::process::exit(1);
164            }
165        },
166        FuncCommands::Execute { name, input } => {
167            let function = match crate::load_function(&name) {
168                Ok(f) => f,
169                Err(e) => {
170                    eprintln!("Error loading function '{name}': {e}");
171                    std::process::exit(1);
172                }
173            };
174            let input_ref = input.as_deref();
175            match crate::run_function(&function, None, input_ref) {
176                Ok(outputs) => {
177                    for output in outputs {
178                        println!("{output}");
179                    }
180                    std::process::exit(0);
181                }
182                Err(e) => {
183                    eprintln!("Error executing function '{name}': {e}");
184                    std::process::exit(1);
185                }
186            }
187        }
188        FuncCommands::Save { file } => {
189            let contents = match std::fs::read_to_string(&file) {
190                Ok(c) => c,
191                Err(e) => {
192                    eprintln!("Error reading file '{}': {e}", file.display());
193                    std::process::exit(1);
194                }
195            };
196            let function: crate::Function = match serde_json::from_str(&contents) {
197                Ok(f) => f,
198                Err(e) => {
199                    eprintln!("Error parsing JSON from '{}': {e}", file.display());
200                    std::process::exit(1);
201                }
202            };
203            match crate::save_function(function) {
204                Ok(_) => {
205                    println!("Function saved successfully");
206                    std::process::exit(0);
207                }
208                Err(e) => {
209                    eprintln!("Error saving function: {e}");
210                    std::process::exit(1);
211                }
212            }
213        }
214    }
215}