Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
```

Expand Down
35 changes: 21 additions & 14 deletions src/commands/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
58 changes: 54 additions & 4 deletions src/commands/skills/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
Expand All @@ -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<()> {
Expand Down Expand Up @@ -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)?;
Expand All @@ -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() {
Expand Down