mirror of
https://github.com/obra/superpowers.git
synced 2026-04-16 02:02:41 +00:00
Add unified scripts system with find-skills and run
Consolidates skill discovery and adds generic runner for cross-platform compatibility. Changes: - Created scripts/find-skills: Unified tool (show all + filter by pattern) - Shows descriptions by default - Searches personal first, then core (shadowing) - Logs searches for gap analysis - Bash 3.2 compatible - Created scripts/run: Generic runner for any skill script - Searches personal superpowers first, then core - Enables running arbitrary skill scripts without CLAUDE_PLUGIN_ROOT env var - Example: scripts/run skills/collaboration/remembering-conversations/tool/search-conversations - Fixed bash 3.2 compatibility in list-skills, skills-search - Replaced associative arrays with newline-delimited lists - Works on macOS default bash (3.2) and Linux bash 4+ - Updated all documentation to reference scripts/find-skills - Removed redundant wrapper scripts This solves the CLAUDE_PLUGIN_ROOT environment variable issue - scripts can now be called from anywhere without needing the env var set.
This commit is contained in:
142
scripts/find-skills
Executable file
142
scripts/find-skills
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env bash
|
||||
# find-skills - Find and list skills with descriptions
|
||||
# Shows all skills by default, filters by pattern if provided
|
||||
# Searches personal superpowers first, then core (personal shadows core)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Determine directories
|
||||
PERSONAL_SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
PERSONAL_SKILLS_DIR="${PERSONAL_SUPERPOWERS_DIR}/skills"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CORE_SKILLS_DIR="${PLUGIN_ROOT}/skills"
|
||||
|
||||
LOG_FILE="${PERSONAL_SUPERPOWERS_DIR}/search-log.jsonl"
|
||||
|
||||
# Show help
|
||||
if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
|
||||
cat <<'EOF'
|
||||
find-skills - Find and list skills with descriptions
|
||||
|
||||
USAGE:
|
||||
find-skills Show all skills with descriptions
|
||||
find-skills PATTERN Filter skills by grep pattern
|
||||
find-skills --help Show this help
|
||||
|
||||
EXAMPLES:
|
||||
find-skills # All skills
|
||||
find-skills test # Skills matching "test"
|
||||
find-skills 'test.*driven|TDD' # Regex pattern
|
||||
|
||||
OUTPUT:
|
||||
Each line shows: skill-path - description
|
||||
Personal skills listed first, then core skills
|
||||
Personal skills shadow core skills when paths match
|
||||
|
||||
SEARCH:
|
||||
Searches both skill content AND path names.
|
||||
Personal skills at: ~/.config/superpowers/skills/
|
||||
Core skills at: plugin installation directory
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get pattern (optional)
|
||||
PATTERN="${1:-}"
|
||||
|
||||
# Function to extract description from SKILL.md
|
||||
get_description() {
|
||||
local file="$1"
|
||||
grep "^description:" "$file" 2>/dev/null | sed 's/description: *//' || echo ""
|
||||
}
|
||||
|
||||
# Function to get relative skill path
|
||||
get_skill_path() {
|
||||
local file="$1"
|
||||
local base_dir="$2"
|
||||
local rel_path="${file#$base_dir/}"
|
||||
echo "${rel_path%/SKILL.md}"
|
||||
}
|
||||
|
||||
# Collect all matching skills (use simple list for bash 3.2 compatibility)
|
||||
seen_skills_list=""
|
||||
results=()
|
||||
|
||||
# If pattern provided, log the search
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
echo "{\"timestamp\":\"$timestamp\",\"query\":\"$PATTERN\"}" >> "$LOG_FILE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Search personal skills first
|
||||
if [[ -d "$PERSONAL_SKILLS_DIR" ]]; then
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
|
||||
skill_path=$(get_skill_path "$file" "$PERSONAL_SKILLS_DIR")
|
||||
description=$(get_description "$file")
|
||||
|
||||
seen_skills_list="${seen_skills_list}${skill_path}"$'\n'
|
||||
results+=("$skill_path|$description")
|
||||
done < <(
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
# Pattern mode: search content and paths
|
||||
{
|
||||
grep -E -r "$PATTERN" "$PERSONAL_SKILLS_DIR/" --include="SKILL.md" -l 2>/dev/null || true
|
||||
find "$PERSONAL_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null | grep -E "$PATTERN" 2>/dev/null || true
|
||||
} | sort -u
|
||||
else
|
||||
# Show all
|
||||
find "$PERSONAL_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null || true
|
||||
fi
|
||||
)
|
||||
fi
|
||||
|
||||
# Search core skills (only if not shadowed)
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
|
||||
skill_path=$(get_skill_path "$file" "$CORE_SKILLS_DIR")
|
||||
|
||||
# Skip if shadowed by personal skill
|
||||
echo "$seen_skills_list" | grep -q "^${skill_path}$" && continue
|
||||
|
||||
description=$(get_description "$file")
|
||||
results+=("$skill_path|$description")
|
||||
done < <(
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
# Pattern mode: search content and paths
|
||||
{
|
||||
grep -E -r "$PATTERN" "$CORE_SKILLS_DIR/" --include="SKILL.md" -l 2>/dev/null || true
|
||||
find "$CORE_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null | grep -E "$PATTERN" 2>/dev/null || true
|
||||
} | sort -u
|
||||
else
|
||||
# Show all
|
||||
find "$CORE_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null || true
|
||||
fi
|
||||
)
|
||||
|
||||
# Check if we found anything
|
||||
if [[ ${#results[@]} -eq 0 ]]; then
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
echo "❌ No skills found matching: $PATTERN"
|
||||
echo ""
|
||||
echo "Search logged. If a skill should exist, consider writing it!"
|
||||
else
|
||||
echo "❌ No skills found"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Sort and display results
|
||||
printf "%s\n" "${results[@]}" | sort | while IFS='|' read -r skill_path description; do
|
||||
if [[ -n "$description" ]]; then
|
||||
echo "skills/$skill_path - $description"
|
||||
else
|
||||
echo "skills/$skill_path"
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
55
scripts/run
Executable file
55
scripts/run
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generic runner for skill scripts
|
||||
# Searches personal superpowers first, then core plugin
|
||||
#
|
||||
# Usage: scripts/run <skill-relative-path> [args...]
|
||||
# Example: scripts/run skills/collaboration/remembering-conversations/tool/search-conversations "query"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
cat <<'EOF'
|
||||
Usage: scripts/run <skill-relative-path> [args...]
|
||||
|
||||
Runs scripts from skills, checking personal superpowers first, then core.
|
||||
|
||||
Examples:
|
||||
scripts/run skills/collaboration/remembering-conversations/tool/search-conversations "query"
|
||||
scripts/run skills/getting-started/list-skills
|
||||
scripts/run skills/getting-started/skills-search "pattern"
|
||||
|
||||
The script will be found at:
|
||||
1. ~/.config/superpowers/<skill-relative-path> (personal, if exists)
|
||||
2. ${CLAUDE_PLUGIN_ROOT}/<skill-relative-path> (core plugin)
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the script path to run
|
||||
SCRIPT_PATH="$1"
|
||||
shift # Remove script path from args, leaving remaining args
|
||||
|
||||
# Determine directories
|
||||
PERSONAL_SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Try personal superpowers first
|
||||
PERSONAL_SCRIPT="${PERSONAL_SUPERPOWERS_DIR}/${SCRIPT_PATH}"
|
||||
if [[ -x "$PERSONAL_SCRIPT" ]]; then
|
||||
exec "$PERSONAL_SCRIPT" "$@"
|
||||
fi
|
||||
|
||||
# Fall back to core plugin
|
||||
CORE_SCRIPT="${PLUGIN_ROOT}/${SCRIPT_PATH}"
|
||||
if [[ -x "$CORE_SCRIPT" ]]; then
|
||||
exec "$CORE_SCRIPT" "$@"
|
||||
fi
|
||||
|
||||
# Not found
|
||||
echo "Error: Script not found: $SCRIPT_PATH" >&2
|
||||
echo "" >&2
|
||||
echo "Searched:" >&2
|
||||
echo " $PERSONAL_SCRIPT (personal)" >&2
|
||||
echo " $CORE_SCRIPT (core)" >&2
|
||||
exit 1
|
||||
Reference in New Issue
Block a user