feat: list available skills when running /skill with no args

- Add list_skills Tauri command to scan ~/.claude/skills/
- Only lists directories containing a SKILL.md file
- Show helpful message when no skills are found
- Display bullet-point list of available skills with usage hint
This commit is contained in:
2026-01-23 15:11:14 -08:00
committed by Naomi Carrigan
parent b8170b87c5
commit 5455b5c148
3 changed files with 62 additions and 5 deletions
+42
View File
@@ -180,3 +180,45 @@ pub async fn answer_question(
let mut manager = bridge_manager.lock();
manager.send_tool_result(&conversation_id, &tool_use_id, answers)
}
#[tauri::command]
pub async fn list_skills() -> Result<Vec<String>, String> {
use std::path::Path;
use std::fs;
// Get the home directory
let home = std::env::var_os("HOME")
.ok_or_else(|| "Could not determine home directory".to_string())?;
let skills_dir = Path::new(&home).join(".claude").join("skills");
// If the skills directory doesn't exist, return empty list
if !skills_dir.exists() {
return Ok(Vec::new());
}
// Read the directory and collect skill names
let mut skills = Vec::new();
let entries = fs::read_dir(&skills_dir)
.map_err(|e| format!("Failed to read skills directory: {}", e))?;
for entry in entries {
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
let path = entry.path();
// Only include directories that contain a SKILL.md file
if path.is_dir() {
let skill_file = path.join("SKILL.md");
if skill_file.exists() {
if let Some(name) = path.file_name() {
skills.push(name.to_string_lossy().to_string());
}
}
}
}
// Sort alphabetically
skills.sort();
Ok(skills)
}
+1
View File
@@ -55,6 +55,7 @@ pub fn run() {
send_wsl_notification,
send_vbs_notification,
validate_directory,
list_skills,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
+19 -5
View File
@@ -186,7 +186,7 @@ export const slashCommands: SlashCommand[] = [
{
name: "skill",
description: "Invoke a Claude Code skill from ~/.claude/skills/",
usage: "/skill <name> <data>",
usage: "/skill [name] [data]",
execute: async (args: string) => {
const conversationId = get(claudeStore.activeConversationId);
if (!conversationId) {
@@ -198,11 +198,25 @@ export const slashCommands: SlashCommand[] = [
const skillName = parts[0];
const skillData = parts.slice(1).join(" ");
// If no skill name provided, list available skills
if (!skillName) {
claudeStore.addLine(
"error",
"Usage: /skill <skill-name> [data]\nSkills are loaded from ~/.claude/skills/<skill-name>/SKILL.md\nExample: /skill onboard-mentee Discord ID: 123, GitHub: username"
);
try {
const skills = await invoke<string[]>("list_skills");
if (skills.length === 0) {
claudeStore.addLine(
"system",
"No skills found in ~/.claude/skills/\nCreate a skill by adding a folder with a SKILL.md file."
);
} else {
const skillList = skills.map((s) => `${s}`).join("\n");
claudeStore.addLine(
"system",
`Available skills:\n${skillList}\n\nUsage: /skill <skill-name> [data]`
);
}
} catch (error) {
claudeStore.addLine("error", `Failed to list skills: ${error}`);
}
return;
}