use clap::{Arg, ArgAction, ArgMatches, Command};
use skill_embed::{AgentSelector, Error, InstallOptions, Installer, Result, Scope};
#[must_use]
pub fn command(skills: &Installer) -> Command {
let tool = skills.tool_name().to_owned();
Command::new(skills.command_name().to_owned())
.about(format!("Manage the agent skills embedded in {tool}"))
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(flags(
Command::new("install").about(format!(
"Install the agent skills embedded in {tool}, \
and remove the ones it no longer carries"
)),
skills,
))
.subcommand(flags(
Command::new("uninstall")
.visible_alias("remove")
.about(format!("Remove the agent skills embedded in {tool}")),
skills,
))
.subcommand(flags(
Command::new("list").visible_alias("ls").about(format!(
"Show the agent skills embedded in {tool}, and where each one stands"
)),
skills,
))
}
fn flags(command: Command, skills: &Installer) -> Command {
let defaults: Vec<String> = skills.default_agents().iter().map(ToString::to_string).collect();
command
.arg(
Arg::new("agent")
.long("agent")
.value_name("AGENT")
.action(ArgAction::Append)
.value_delimiter(',')
.default_value(defaults.join(","))
.help(format!("Target agent: {}, or all, or detected", skills.agent_choices())),
)
.arg(
Arg::new("dir")
.long("dir")
.value_name("DIR")
.help("Install to a custom directory (overrides --agent and --scope)"),
)
.arg(
Arg::new("scope")
.long("scope")
.value_name("SCOPE")
.value_parser(["project", "user"])
.default_value(skills.default_scope().as_str())
.help("Installation scope"),
)
.arg(
Arg::new("force")
.long("force")
.short('f')
.action(ArgAction::SetTrue)
.help("Overwrite existing skills"),
)
.arg(
Arg::new("dry-run")
.long("dry-run")
.action(ArgAction::SetTrue)
.help("Report what would happen without writing"),
)
.arg(
Arg::new("skill")
.value_name("SKILL")
.action(ArgAction::Append)
.help("The skills to act on, or every embedded skill"),
)
}
pub fn options(matches: &ArgMatches) -> Result<InstallOptions> {
let given: Vec<&String> = matches.get_many::<String>("agent").into_iter().flatten().collect();
let agents: Vec<AgentSelector> =
given.iter().flat_map(|v| AgentSelector::parse_list(v).collect::<Vec<_>>()).collect();
if !given.is_empty() && agents.is_empty() {
return Err(Error::NoAgentSelected);
}
let scope = match matches.get_one::<String>("scope") {
Some(s) => Some(s.parse::<Scope>()?),
None => None,
};
Ok(InstallOptions {
agents,
scope,
dir: matches.get_one::<String>("dir").map(Into::into),
force: matches.get_flag("force"),
dry_run: matches.get_flag("dry-run"),
names: matches.get_many::<String>("skill").into_iter().flatten().cloned().collect(),
cancel: None,
})
}
pub fn run(skills: &Installer, matches: &ArgMatches) -> Result<()> {
let Some((name, args)) = matches.subcommand() else {
return Err(Error::Usage(format!("{} needs a subcommand", skills.command_name())));
};
let options = options(args)?;
match name {
"list" => {
let statuses = skills.status(&options)?;
skills.write_report(&skill_embed::render_status(&statuses))
}
"install" | "uninstall" => {
let (results, outcome) = if name == "install" {
skills.install(&options)
} else {
skills.uninstall(&options)
};
skills.write_report(&skill_embed::render_results(&results, options.dry_run))?;
outcome
}
_ => Err(Error::Usage(format!("unknown {} subcommand {name:?}", skills.command_name()))),
}
}