#[cfg(feature = "mcp")]
use clap::Args;
use clap::{Parser, Subcommand};
use colored::Colorize;
use stuckbar::{ExplorerManager, SystemProcessRunner, check_platform};
#[derive(Parser)]
#[command(
name = "stuckbar",
about = "A CLI tool for restarting Windows Explorer when the taskbar gets stuck",
long_about = "A CLI tool for restarting Windows Explorer when the taskbar gets stuck.\n\n\
This tool is Windows-only and provides commands to kill, start, or restart \
explorer.exe. It also supports running as an MCP (Model Context Protocol) \
server for AI agent integration.",
version,
author
)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand, Debug, Clone, PartialEq)]
pub enum Commands {
Kill,
Start,
Restart,
#[cfg(feature = "mcp")]
Serve(ServeArgs),
}
#[cfg(feature = "mcp")]
#[derive(Args, Debug, Clone, PartialEq)]
pub struct ServeArgs {
#[arg(long, group = "transport")]
pub stdio: bool,
#[cfg(feature = "mcp-http")]
#[arg(long, group = "transport")]
pub http: bool,
#[cfg(feature = "mcp-http")]
#[arg(long, default_value = "127.0.0.1", requires = "http")]
pub host: String,
#[cfg(feature = "mcp-http")]
#[arg(long, default_value = "8080", requires = "http")]
pub port: u16,
}
fn run_command(command: Option<Commands>) -> bool {
let manager = ExplorerManager::new(SystemProcessRunner);
match command {
Some(Commands::Kill) => manager.kill(),
Some(Commands::Start) => manager.start(),
Some(Commands::Restart) => manager.restart(),
#[cfg(feature = "mcp")]
Some(Commands::Serve(args)) => {
run_mcp_server(args);
true
}
None => manager.restart(),
}
}
#[cfg(feature = "mcp")]
#[allow(unused_variables)]
fn run_mcp_server(args: ServeArgs) {
use tokio::runtime::Runtime;
let rt = Runtime::new().expect("Failed to create Tokio runtime");
rt.block_on(async {
#[cfg(feature = "mcp-http")]
if args.http {
if let Err(e) = stuckbar::mcp::run_http_server(&args.host, args.port).await {
eprintln!("{} {}", "MCP HTTP server error:".red(), e);
std::process::exit(1);
}
return;
}
if let Err(e) = stuckbar::mcp::run_stdio_server().await {
eprintln!("{} {}", "MCP STDIO server error:".red(), e);
std::process::exit(1);
}
});
}
fn main() {
if let Err(e) = check_platform() {
eprintln!("{}", e.red().bold());
std::process::exit(1);
}
let cli = Cli::parse();
let success = run_command(cli.command);
if !success {
std::process::exit(1);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_parse_no_args() {
let cli = Cli::parse_from(["stuckbar"]);
assert!(cli.command.is_none());
}
#[test]
fn test_cli_parse_kill() {
let cli = Cli::parse_from(["stuckbar", "kill"]);
assert_eq!(cli.command, Some(Commands::Kill));
}
#[test]
fn test_cli_parse_start() {
let cli = Cli::parse_from(["stuckbar", "start"]);
assert_eq!(cli.command, Some(Commands::Start));
}
#[test]
fn test_cli_parse_restart() {
let cli = Cli::parse_from(["stuckbar", "restart"]);
assert_eq!(cli.command, Some(Commands::Restart));
}
#[test]
fn test_cli_version_flag() {
let result = Cli::try_parse_from(["stuckbar", "--version"]);
assert!(result.is_err()); }
#[test]
fn test_cli_help_flag() {
let result = Cli::try_parse_from(["stuckbar", "--help"]);
assert!(result.is_err()); }
#[test]
fn test_cli_invalid_command() {
let result = Cli::try_parse_from(["stuckbar", "invalid"]);
assert!(result.is_err());
}
#[test]
fn test_commands_equality() {
assert_eq!(Commands::Kill, Commands::Kill);
assert_eq!(Commands::Start, Commands::Start);
assert_eq!(Commands::Restart, Commands::Restart);
assert_ne!(Commands::Kill, Commands::Start);
}
#[test]
fn test_commands_clone() {
let cmd = Commands::Restart;
let cloned = cmd.clone();
assert_eq!(cmd, cloned);
}
#[cfg(feature = "mcp")]
#[test]
fn test_cli_parse_serve_stdio() {
let cli = Cli::parse_from(["stuckbar", "serve", "--stdio"]);
match cli.command {
Some(Commands::Serve(args)) => {
assert!(args.stdio);
}
_ => panic!("Expected Serve command"),
}
}
#[cfg(all(feature = "mcp", feature = "mcp-http"))]
#[test]
fn test_cli_parse_serve_http() {
let cli = Cli::parse_from(["stuckbar", "serve", "--http"]);
match cli.command {
Some(Commands::Serve(args)) => {
assert!(args.http);
assert_eq!(args.host, "127.0.0.1");
assert_eq!(args.port, 8080);
}
_ => panic!("Expected Serve command"),
}
}
#[cfg(all(feature = "mcp", feature = "mcp-http"))]
#[test]
fn test_cli_parse_serve_http_custom_port() {
let cli = Cli::parse_from(["stuckbar", "serve", "--http", "--port", "3000"]);
match cli.command {
Some(Commands::Serve(args)) => {
assert!(args.http);
assert_eq!(args.port, 3000);
}
_ => panic!("Expected Serve command"),
}
}
#[cfg(all(feature = "mcp", feature = "mcp-http"))]
#[test]
fn test_cli_parse_serve_http_custom_host_port() {
let cli = Cli::parse_from([
"stuckbar", "serve", "--http", "--host", "0.0.0.0", "--port", "9000",
]);
match cli.command {
Some(Commands::Serve(args)) => {
assert!(args.http);
assert_eq!(args.host, "0.0.0.0");
assert_eq!(args.port, 9000);
}
_ => panic!("Expected Serve command"),
}
}
}