use clap::{Parser, Subcommand}; use colored::*; use anyhow::Result; use anvil::version::ANVIL_VERSION; mod commands { pub use anvil::commands::*; } #[derive(Parser)] #[command(name = "anvil")] #[command(author = "Eric Ratliff ")] #[command(version = ANVIL_VERSION)] #[command( about = "Arduino project generator and build tool - forges clean embedded projects", long_about = None )] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Create a new Arduino project New { /// Project name name: Option, /// Template to use (basic) #[arg(long, short = 't', value_name = "TEMPLATE")] template: Option, /// Board preset (uno, mega, nano, leonardo, micro) #[arg(long, short = 'b', value_name = "BOARD")] board: Option, /// List available templates #[arg(long, conflicts_with = "name")] list_templates: bool, /// List available board presets #[arg(long, conflicts_with = "name")] list_boards: bool, }, /// Check system health and diagnose issues Doctor { /// Automatically install missing tools #[arg(long)] fix: bool, }, /// Install arduino-cli and required cores Setup, /// List connected boards and serial ports Devices { /// Save a port to .anvil.local for this project #[arg(long, conflicts_with_all = ["get", "clear"])] set: bool, /// Show the saved port for this project #[arg(long, conflicts_with_all = ["set", "clear"])] get: bool, /// Remove .anvil.local (revert to auto-detect) #[arg(long, conflicts_with_all = ["set", "get"])] clear: bool, /// Port name (e.g. COM3, /dev/ttyUSB0). Auto-detects if omitted with --set. port_or_dir: Option, /// Path to project directory (defaults to current directory) #[arg(long, short = 'd', value_name = "DIR")] dir: Option, }, /// Update project scripts to the latest version Refresh { /// Path to project directory (defaults to current directory) dir: Option, /// Overwrite scripts even if they have been modified #[arg(long)] force: bool, }, /// Manage board profiles in .anvil.toml Board { /// Board name (e.g. mega, nano) name: Option, /// Add a board to the project #[arg(long, conflicts_with_all = ["remove", "listall", "default"])] add: bool, /// Remove a board from the project #[arg(long, conflicts_with_all = ["add", "listall", "default"])] remove: bool, /// Set the default board #[arg(long, conflicts_with_all = ["add", "remove", "listall"])] default: bool, /// Browse all available boards #[arg(long, conflicts_with_all = ["add", "remove", "default"])] listall: bool, /// Board identifier (from anvil board --listall) #[arg(long, value_name = "ID")] id: Option, /// Baud rate override #[arg(long, value_name = "RATE")] baud: Option, /// Path to project directory (defaults to current directory) #[arg(long, short = 'd', value_name = "DIR")] dir: Option, }, } fn main() -> Result<()> { #[cfg(windows)] colored::control::set_virtual_terminal(true).ok(); let cli = Cli::parse(); print_banner(); match cli.command { Commands::New { name, template, board, list_templates, list_boards } => { if list_boards { commands::new::list_boards() } else if list_templates { commands::new::list_templates() } else if let Some(project_name) = name { commands::new::create_project( &project_name, template.as_deref(), board.as_deref(), ) } else { anyhow::bail!( "Project name required.\n\ Usage: anvil new \n\ Usage: anvil new --board mega\n\ List boards: anvil new --list-boards\n\ List templates: anvil new --list-templates" ); } } Commands::Doctor { fix } => { commands::doctor::run_diagnostics(fix) } Commands::Setup => { commands::setup::run_setup() } Commands::Devices { set, get, clear, port_or_dir, dir } => { if set { commands::devices::set_port( port_or_dir.as_deref(), dir.as_deref(), ) } else if get { commands::devices::get_port( dir.as_deref().or(port_or_dir.as_deref()), ) } else if clear { commands::devices::clear_port( dir.as_deref().or(port_or_dir.as_deref()), ) } else { commands::devices::scan_devices() } } Commands::Refresh { dir, force } => { commands::refresh::run_refresh( dir.as_deref(), force, ) } Commands::Board { name, add, remove, default, listall, id, baud, dir } => { if listall { commands::board::listall_boards(name.as_deref()) } else if add { let board_name = name.as_deref().ok_or_else(|| { anyhow::anyhow!( "Board name required.\n\ Usage: anvil board --add mega\n\ Browse available boards: anvil board --listall" ) })?; commands::board::add_board( board_name, id.as_deref(), baud, dir.as_deref(), ) } else if remove { let board_name = name.as_deref().ok_or_else(|| { anyhow::anyhow!( "Board name required.\n\ Usage: anvil board --remove mega" ) })?; commands::board::remove_board( board_name, dir.as_deref(), ) } else if default { let board_name = name.as_deref().ok_or_else(|| { anyhow::anyhow!( "Board name required.\n\ Usage: anvil board --default uno\n\ List boards: anvil board" ) })?; commands::board::set_default_board( board_name, dir.as_deref(), ) } else { commands::board::list_boards(dir.as_deref()) } } } } fn print_banner() { println!( "{}", "================================================================" .bright_cyan() ); println!( "{}", format!(" Anvil - Arduino Build Tool v{}", ANVIL_VERSION) .bright_cyan() .bold() ); println!("{}", " Nexus Workshops LLC".bright_cyan()); println!( "{}", "================================================================" .bright_cyan() ); println!(); }