feat: show installed skills in UI (#7910)
Signed-off-by: Vincenzo Palazzo <vincenzopalazzodev@gmail.com> Signed-off-by: Douwe Osinga <douwe@squareup.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -33,6 +33,10 @@ static COMMANDS: &[CommandDef] = &[
|
||||
name: "clear",
|
||||
description: "Clear the conversation history",
|
||||
},
|
||||
CommandDef {
|
||||
name: "skills",
|
||||
description: "List installed skills and other available sources",
|
||||
},
|
||||
];
|
||||
|
||||
pub fn list_commands() -> &'static [CommandDef] {
|
||||
@@ -72,6 +76,7 @@ impl Agent {
|
||||
"prompt" => self.handle_prompt_command(¶ms, session_id).await,
|
||||
"compact" => self.handle_compact_command(session_id).await,
|
||||
"clear" => self.handle_clear_command(session_id).await,
|
||||
"skills" => self.handle_skills_command(session_id).await,
|
||||
_ => {
|
||||
self.handle_recipe_command(command, params_str, session_id)
|
||||
.await
|
||||
@@ -129,6 +134,49 @@ impl Agent {
|
||||
)))
|
||||
}
|
||||
|
||||
async fn handle_skills_command(&self, session_id: &str) -> Result<Option<Message>> {
|
||||
use super::platform_extensions::summon::{list_installed_sources, SourceKind};
|
||||
|
||||
let working_dir = self
|
||||
.config
|
||||
.session_manager
|
||||
.get_session(session_id, false)
|
||||
.await
|
||||
.ok()
|
||||
.map(|s| s.working_dir);
|
||||
let sources = list_installed_sources(working_dir.as_deref());
|
||||
let skills: Vec<_> = sources
|
||||
.iter()
|
||||
.filter(|s| matches!(s.kind, SourceKind::Skill | SourceKind::BuiltinSkill))
|
||||
.collect();
|
||||
|
||||
let mut output = String::new();
|
||||
if skills.is_empty() {
|
||||
output.push_str("No skills installed.\n\n");
|
||||
output.push_str("Skills are loaded from SKILL.md files in:\n");
|
||||
output.push_str(" - ~/.claude/skills/\n");
|
||||
output.push_str(" - ~/.config/agents/skills/\n");
|
||||
output.push_str(" - .goose/skills/ (in current directory)\n");
|
||||
output.push_str(" - .claude/skills/ (in current directory)\n");
|
||||
output.push_str(" - .agents/skills/ (in current directory)\n");
|
||||
} else {
|
||||
output.push_str(&format!("**Installed skills ({}):**\n\n", skills.len()));
|
||||
for skill in &skills {
|
||||
let kind_label = if skill.kind == SourceKind::BuiltinSkill {
|
||||
" *(builtin)*"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
output.push_str(&format!(
|
||||
"- **{}**{}: {}\n",
|
||||
skill.name, kind_label, skill.description
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Message::assistant().with_text(output)))
|
||||
}
|
||||
|
||||
async fn handle_prompts_command(
|
||||
&self,
|
||||
params: &[&str],
|
||||
|
||||
@@ -390,6 +390,16 @@ fn scan_agents_from_dir(
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all discovered sources (skills, recipes, agents) from the given working directory.
|
||||
/// If no directory is provided, falls back to `std::env::current_dir()`.
|
||||
/// This is useful for listing what's available without needing a full SummonClient instance.
|
||||
pub fn list_installed_sources(working_dir: Option<&Path>) -> Vec<Source> {
|
||||
let dir = working_dir
|
||||
.map(|p| p.to_path_buf())
|
||||
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
|
||||
discover_filesystem_sources(&dir)
|
||||
}
|
||||
|
||||
fn discover_filesystem_sources(working_dir: &Path) -> Vec<Source> {
|
||||
let mut sources: Vec<Source> = Vec::new();
|
||||
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||
|
||||
Reference in New Issue
Block a user