use clap::builder::{Styles, styling::AnsiColor};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(
name = "augent",
author,
version,
color = clap::ColorChoice::Always,
styles = Styles::styled()
.header(AnsiColor::Green.on_default().bold())
.usage(AnsiColor::Green.on_default().bold())
.literal(AnsiColor::Cyan.on_default().bold())
.placeholder(AnsiColor::Cyan.on_default()),
about = "Lean package manager for various AI coding platforms",
long_about = "Augent manages AI coding platform resources (commands, rules, skills, MCP servers) \
across multiple platforms (Claude, Cursor, OpenCode, ...) in a platform-independent, \
reproducible manner.",
after_help = "\x1b[1m\x1b[32mExamples:\x1b[0m\n \
augent install @author/bundle \x1b[90m# Install from GitHub shorthand\x1b[0m\n \
augent install ./bundle --to claude \x1b[90m# Install only for Claude Code\x1b[0m\n \
augent uninstall @author/bundle \x1b[90m# Uninstall bundle\x1b[0m\n \
augent uninstall @author --all-bundles \x1b[90m# Uninstall all bundles under scope\x1b[0m\n \
augent list \x1b[90m# List all installed bundles\x1b[0m\n \
augent show @author/bundle \x1b[90m# Show bundle information\x1b[0m\n\n\
"
)]
pub struct Cli {
#[arg(long, short = 'w', global = true, env = "AUGENT_WORKSPACE")]
pub workspace: Option<PathBuf>,
#[arg(long, short = 'v', global = true)]
pub verbose: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Install(InstallArgs),
Uninstall(UninstallArgs),
List(ListArgs),
Show(ShowArgs),
#[command(name = "cache")]
Cache(CacheArgs),
#[command(hide = true)]
Version,
Completions(CompletionsArgs),
}
#[derive(Parser, Debug)]
#[command(after_help = "EXAMPLES:\n \
Install from GitHub:\n augent install @author/bundle\n \
augent install github:author/bundle\n\n\
Install from local directory:\n augent install ./my-bundle\n\n\
Install for specific platforms:\n augent install ./bundle --to cursor\n\n\
Install with frozen lockfile:\n augent install @author/bundle --frozen")]
pub struct InstallArgs {
pub source: Option<String>,
#[arg(long = "to", short = 't', value_name = "PLATFORM", num_args = 1..)]
pub platforms: Vec<String>,
#[arg(long)]
pub frozen: bool,
#[arg(long = "all-bundles")]
pub all_bundles: bool,
#[arg(long)]
pub update: bool,
#[arg(long)]
pub dry_run: bool,
#[arg(long, short = 'y')]
pub yes: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "EXAMPLES:\n \
Uninstall a bundle:\n augent uninstall my-bundle\n\n\
Uninstall without confirmation:\n augent uninstall my-bundle -y\n\n\
Uninstall a specific bundle name:\n augent uninstall author/bundle\n\n\
Uninstall all bundles matching a scope:\n augent uninstall @wshobson/agents\n\n\
Uninstall scope without prompt:\n augent uninstall @wshobson/agents --all-bundles\n\n\
Select bundle interactively:\n augent uninstall")]
pub struct UninstallArgs {
pub name: Option<String>,
#[arg(long, short = 'y')]
pub yes: bool,
#[arg(long = "all-bundles")]
pub all_bundles: bool,
#[arg(long)]
pub dry_run: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "EXAMPLES:\n \
List all installed bundles:\n augent list\n\n\
Show detailed information:\n augent list --detailed\n\n\
Use verbose output:\n augent list -v")]
pub struct ListArgs {
#[arg(long)]
pub detailed: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "EXAMPLES:\n \
Show bundle information:\n augent show my-bundle\n\n\
Show a specific bundle:\n augent show author/debug-tools\n\n\
Show all bundles under a scope:\n augent show @wshobson/agents\n\n\
Select bundle interactively:\n augent show\n\n\
Show including dependencies:\n augent show my-bundle --detailed")]
pub struct ShowArgs {
pub name: Option<String>,
#[arg(long)]
pub detailed: bool,
}
#[derive(Parser, Debug)]
#[command(after_help = "EXAMPLES:\n \
Generate bash completions:\n augent completions bash > ~/.bash_completion.d/augent\n\n\
Generate zsh completions:\n augent completions zsh > ~/.zfunc/_augent\n\n\
Generate fish completions:\n augent completions fish > ~/.config/fish/completions/augent.fish\n\n\
Generate PowerShell completions:\n augent completions powershell")]
pub struct CompletionsArgs {
pub shell: String,
}
#[derive(Parser, Debug)]
#[command(after_help = "EXAMPLES:\n \
Show cache statistics:\n augent cache\n\n\
List cached bundles:\n augent cache list\n\n\
Clear all cached bundles:\n augent cache clear\n\n\
Remove specific bundle:\n augent cache clear --only @author/repo")]
pub struct CacheArgs {
#[command(subcommand)]
pub command: Option<CacheSubcommand>,
}
#[derive(Subcommand, Debug)]
pub enum CacheSubcommand {
List,
Clear(ClearCacheArgs),
}
#[derive(Parser, Debug)]
pub struct ClearCacheArgs {
#[arg(long)]
pub only: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cli_parsing_install() {
let cli = Cli::try_parse_from(["augent", "install", "github:author/bundle"]).unwrap();
match cli.command {
Commands::Install(args) => {
assert_eq!(args.source, Some("github:author/bundle".to_string()));
assert!(args.platforms.is_empty());
assert!(!args.frozen);
}
_ => panic!("Expected Install command"),
}
}
#[test]
fn test_cli_parsing_install_no_source() {
let cli = Cli::try_parse_from(["augent", "install"]).unwrap();
match cli.command {
Commands::Install(args) => {
assert_eq!(args.source, None);
assert!(args.platforms.is_empty());
assert!(!args.frozen);
}
_ => panic!("Expected Install command"),
}
}
#[test]
fn test_cli_parsing_install_with_options() {
let cli = Cli::try_parse_from([
"augent",
"install",
"./local-bundle",
"--to",
"cursor",
"--to",
"opencode",
"--frozen",
])
.unwrap();
match cli.command {
Commands::Install(args) => {
assert_eq!(args.source, Some("./local-bundle".to_string()));
assert_eq!(args.platforms, vec!["cursor", "opencode"]);
assert!(args.frozen);
assert!(!args.dry_run);
}
_ => panic!("Expected Install command"),
}
}
#[test]
fn test_cli_parsing_install_with_dry_run() {
let cli =
Cli::try_parse_from(["augent", "install", "./local-bundle", "--dry-run"]).unwrap();
match cli.command {
Commands::Install(args) => {
assert_eq!(args.source, Some("./local-bundle".to_string()));
assert!(args.dry_run);
}
_ => panic!("Expected Install command"),
}
}
#[test]
fn test_cli_parsing_uninstall() {
let cli = Cli::try_parse_from(["augent", "uninstall", "my-bundle"]).unwrap();
match cli.command {
Commands::Uninstall(args) => {
assert_eq!(args.name, Some("my-bundle".to_string()));
assert!(!args.yes);
assert!(!args.all_bundles);
assert!(!args.dry_run);
}
_ => panic!("Expected Uninstall command"),
}
}
#[test]
fn test_cli_parsing_uninstall_with_dry_run() {
let cli = Cli::try_parse_from(["augent", "uninstall", "my-bundle", "--dry-run"]).unwrap();
match cli.command {
Commands::Uninstall(args) => {
assert_eq!(args.name, Some("my-bundle".to_string()));
assert!(args.dry_run);
}
_ => panic!("Expected Uninstall command"),
}
}
#[test]
fn test_cli_parsing_uninstall_no_name() {
let cli = Cli::try_parse_from(["augent", "uninstall"]).unwrap();
match cli.command {
Commands::Uninstall(args) => {
assert_eq!(args.name, None);
assert!(!args.yes);
assert!(!args.all_bundles);
}
_ => panic!("Expected Uninstall command"),
}
}
#[test]
fn test_cli_parsing_list() {
let cli = Cli::try_parse_from(["augent", "list"]).unwrap();
assert!(matches!(cli.command, Commands::List(_)));
}
#[test]
fn test_cli_parsing_show() {
let cli = Cli::try_parse_from(["augent", "show", "my-bundle"]).unwrap();
match cli.command {
Commands::Show(args) => {
assert_eq!(args.name, Some("my-bundle".to_string()));
}
_ => panic!("Expected Show command"),
}
}
#[test]
fn test_cli_parsing_show_no_name() {
let cli = Cli::try_parse_from(["augent", "show"]).unwrap();
match cli.command {
Commands::Show(args) => {
assert_eq!(args.name, None);
}
_ => panic!("Expected Show command"),
}
}
#[test]
fn test_cli_parsing_version() {
let cli = Cli::try_parse_from(["augent", "version"]).unwrap();
assert!(matches!(cli.command, Commands::Version));
}
#[test]
fn test_cli_global_options() {
let cli = Cli::try_parse_from(["augent", "-v", "-w", "/tmp/workspace", "list"]).unwrap();
assert!(cli.verbose);
assert_eq!(cli.workspace, Some(PathBuf::from("/tmp/workspace")));
}
#[test]
fn test_cli_workspace_from_env() {
let env_path = if cfg!(windows) {
r"C:\temp\env-workspace"
} else {
"/tmp/env-workspace"
};
let cli = Cli::try_parse_from(["augent", "-w", env_path, "list"]).unwrap();
assert_eq!(cli.workspace, Some(PathBuf::from(env_path)));
}
#[test]
fn test_cli_workspace_flag_overrides_env() {
let env_path = if cfg!(windows) {
r"C:\temp\env-workspace"
} else {
"/tmp/env-workspace"
};
let flag_path = if cfg!(windows) {
r"C:\temp\flag-workspace"
} else {
"/tmp/flag-workspace"
};
unsafe {
std::env::set_var("AUGENT_WORKSPACE", env_path);
}
let cli = Cli::try_parse_from(["augent", "-w", flag_path, "list"]).unwrap();
assert_eq!(cli.workspace, Some(PathBuf::from(flag_path)));
unsafe {
std::env::remove_var("AUGENT_WORKSPACE");
}
}
#[test]
fn test_cli_parsing_completions() {
let cli = Cli::try_parse_from(["augent", "completions", "bash"]).unwrap();
match cli.command {
Commands::Completions(args) => {
assert_eq!(args.shell, "bash");
}
_ => panic!("Expected Completions command"),
}
}
}