feat(skills): add --all option and interactive catalog flow

This commit is contained in:
nas
2026-06-04 09:34:05 -04:00
parent aebc657b85
commit 14cc81b9d2
4 changed files with 78 additions and 19 deletions
+2 -1
View File
@@ -209,11 +209,12 @@ steel skills list [--offline]
## steel skills install
Install one or more Steel Skills through `npx skills`.
Install all Steel Skills, or one or more selected Steel Skills, through `npx skills`.
### Usage
```
steel skills install --all [-a <agent>] [-g] [-y]
steel skills install <name>... [-a <agent>] [-g] [-y]
```
+21 -14
View File
@@ -10,9 +10,8 @@ pub struct Args {
#[arg(long)]
pub agent: bool,
/// Install selected Steel skills via the skills installer. With no value,
/// installs the recommended bootstrap set
#[arg(long, num_args = 0..=1, value_delimiter = ',', default_missing_value = "steel-browser,steel-developer")]
/// Open the Steel skills installer flow. With no value, lets you choose skills interactively
#[arg(long, num_args = 0..=1, value_delimiter = ',', default_missing_value = "__all__")]
pub skills: Option<Vec<String>>,
/// Skip Steel skill installation
@@ -63,24 +62,32 @@ async fn install_skills(args: &Args) -> anyhow::Result<()> {
return Ok(());
}
let selected = args
.skills
.clone()
.unwrap_or_else(|| vec!["steel-browser".to_string(), "steel-developer".to_string()]);
let install_result = match &args.skills {
Some(selected) if selected.is_empty() => return Ok(()),
Some(selected) if is_all_selection(selected) => skills::install_catalog_flow().await,
Some(selected) => skills::install_names(selected, false).await,
None => skills::install_catalog_flow().await,
};
if selected.is_empty() {
return Ok(());
}
match skills::install_names(&selected, args.agent).await {
match install_result {
Ok(()) => Ok(()),
Err(error) => {
status!("Could not install Steel skills through npx skills: {error:#}");
status!("Install manually with:");
for name in &selected {
status!(" npx skills add steel-dev/skills --skill {name}");
if let Some(selected) = &args.skills
&& !is_all_selection(selected)
{
for name in selected {
status!(" npx skills add steel-dev/skills --skill {name}");
}
} else {
status!(" npx skills add steel-dev/skills");
}
Ok(())
}
}
}
fn is_all_selection(selected: &[String]) -> bool {
selected.len() == 1 && matches!(selected[0].as_str(), "__all__" | "all")
}
+1
View File
@@ -30,6 +30,7 @@ Getting Started:
steel init Log in, verify, and install Steel skills into detected agents
--agent Print the agent onboarding guide to stdout and exit
steel skills list List available Steel Skills
steel skills install --all Install all Steel Skills through npx skills
steel skills install <name> Install a Steel Skill through npx skills
steel skills doctor Check skills installer, auth, manifest, and paths
+54 -4
View File
@@ -128,11 +128,14 @@ pub enum SkillsCommand {
#[arg(long)]
offline: bool,
},
/// Install one or more Steel skills through npx skills
/// Install all or selected Steel skills through npx skills
Install {
/// Skill name(s) to install
#[arg(required = true)]
#[arg(required_unless_present = "all")]
names: Vec<String>,
/// Install all Steel skills from the catalog
#[arg(long)]
all: bool,
/// Target agent passed to npx skills (-a)
#[arg(short = 'a', long)]
agent: Option<String>,
@@ -236,10 +239,20 @@ pub async fn run(command: SkillsCommand) -> anyhow::Result<()> {
SkillsCommand::List { offline } => list(offline).await,
SkillsCommand::Install {
names,
all,
agent,
global,
yes,
} => install_names_with_options(&names, agent.as_deref(), global, yes).await,
} => {
if all {
if !names.is_empty() {
anyhow::bail!("cannot combine --all with explicit skill names");
}
install_all_with_options(agent.as_deref(), global, yes).await
} else {
install_names_with_options(&names, agent.as_deref(), global, yes).await
}
}
SkillsCommand::Update { names, yes } => update(names, yes).await,
SkillsCommand::Doctor { offline } => doctor(offline).await,
SkillsCommand::Open { name } => open_skill(&name).await,
@@ -248,7 +261,18 @@ pub async fn run(command: SkillsCommand) -> anyhow::Result<()> {
}
pub async fn install_names(names: &[String], yes: bool) -> anyhow::Result<()> {
install_names_with_options(names, None, true, yes).await
install_names_with_options(names, None, false, yes).await
}
pub async fn install_all(yes: bool) -> anyhow::Result<()> {
install_all_with_options(None, false, yes).await
}
pub async fn install_catalog_flow() -> anyhow::Result<()> {
run_npx(&["skills", "add", REPO_SPEC])
.context("starting Steel skills installer. You can also run manually: npx skills add steel-dev/skills")?;
output::success_silent();
Ok(())
}
async fn list(offline: bool) -> anyhow::Result<()> {
@@ -286,6 +310,10 @@ async fn install_names_with_options(
global: bool,
yes: bool,
) -> anyhow::Result<()> {
if names.is_empty() {
anyhow::bail!("provide a skill name or use --all");
}
let manifest = load_manifest(false).await?;
for name in names {
ensure_known_skill(&manifest, name)?;
@@ -312,6 +340,28 @@ async fn install_names_with_options(
Ok(())
}
async fn install_all_with_options(
agent: Option<&str>,
global: bool,
yes: bool,
) -> anyhow::Result<()> {
let mut args = vec!["skills", "add", REPO_SPEC, "--all"];
if let Some(agent) = agent {
args.push("-a");
args.push(agent);
}
if global {
args.push("-g");
}
if yes {
args.push("-y");
}
run_npx(&args).context("installing all Steel skills. You can also run manually: npx skills add steel-dev/skills --all")?;
status!("Installed all Steel skills");
output::success_silent();
Ok(())
}
async fn update(names: Vec<String>, yes: bool) -> anyhow::Result<()> {
let manifest = load_manifest(false).await?;
let selected = if names.is_empty() {