Add skill-manager skill (migrated from skill-creator) with deploy action

- Skill content moved from ~/.agents/skills/skill-creator, renamed to skill-manager
- New deploy CLI action: installs any skill via absolute-path symlinks (or copies)
  into $HOME/.agents/skills, $HOME/.claude/skills and $HOME/.cline/skills
- Taskfile + shell module wrappers (task deploy / cli:deploy)
- SKILL.md: deploy docs, origin-repository/origin-path metadata

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 22:32:42 +03:00
co-authored by Claude Fable 5
parent cc30987dd2
commit 6042272ccc
62 changed files with 3474 additions and 1 deletions
+212
View File
@@ -0,0 +1,212 @@
#!/bin/bash
# validator/lib/-skill--execute.sh — validate a skill directory against the agentskills.io spec
_validator__skill__execute() {
local FUNCTION_NAME="_validator__skill__execute"
local SKILL_DIR=""
# ── Parse args ──────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--skill-dir=*) SKILL_DIR="${1#*=}"; shift ;;
--skill-dir) SKILL_DIR="$2"; shift 2 ;;
--help|-h)
cat <<EOF
Usage: task -t scripts/Taskfile.yml validate-shell -- --skill-dir="<path>"
Validate an Agent Skill directory against the agentskills.io specification.
Options:
--skill-dir PATH Path to the skill directory to validate (required)
Checks:
- SKILL.md exists
- YAML frontmatter is present (--- delimiters)
- 'name' field is present and valid (a-z, 0-9, hyphens only)
- 'name' matches the directory name
- 'name' is <= 64 characters, no leading/trailing/consecutive hyphens
- 'description' field is present and <= 1024 characters
- SKILL.md body is <= 500 lines (warning if exceeded)
- Optional directories: scripts/, references/, assets/
Exit codes:
0 All checks passed
1 One or more checks failed
2 Usage error
Examples:
task -t scripts/Taskfile.yml validate-shell -- --skill-dir=".agents/skills/my-skill"
EOF
return 0
;;
*) echo "Error: unknown argument: $1" >&2; return 2 ;;
esac
done
# ── Validate required args ──────────────────────────────────────────────────
if [[ -z "$SKILL_DIR" ]]; then
echo "Error: --skill-dir is required." >&2
echo "Usage: task -t scripts/Taskfile.yml validate-shell -- --skill-dir=\"<path>\"" >&2
return 2
fi
SKILL_DIR="${SKILL_DIR%/}" # strip trailing slash
# ── Counters ────────────────────────────────────────────────────────────────
local PASS=0 FAIL=0 WARN=0
_pass() { echo " ✅ $1"; ((PASS++)) || true; }
_fail() { echo " ❌ $1"; ((FAIL++)) || true; }
_warn() { echo " ⚠️ $1"; ((WARN++)) || true; }
_info() { echo " ℹ️ $1"; }
echo ""
echo "Validating skill: $SKILL_DIR"
echo "──────────────────────────────────────────"
# ── Check: directory exists ─────────────────────────────────────────────────
if [[ ! -d "$SKILL_DIR" ]]; then
echo "Error: directory not found: $SKILL_DIR" >&2
return 2
fi
local SKILL_FILE="$SKILL_DIR/SKILL.md"
local DIR_NAME
DIR_NAME="$(basename "$SKILL_DIR")"
# ── Check: SKILL.md exists ──────────────────────────────────────────────────
if [[ ! -f "$SKILL_FILE" ]]; then
_fail "SKILL.md not found in $SKILL_DIR"
echo ""; echo "Result: ❌ FAILED"; return 1
fi
_pass "SKILL.md exists"
# ── Check: frontmatter delimiters ───────────────────────────────────────────
local CONTENT
CONTENT="$(cat "$SKILL_FILE")"
if ! echo "$CONTENT" | grep -q "^---$"; then
_fail "SKILL.md is missing YAML frontmatter (--- delimiters)"
echo ""; echo "Result: ❌ FAILED"; return 1
fi
_pass "YAML frontmatter delimiters found"
# ── Extract frontmatter ─────────────────────────────────────────────────────
local FRONTMATTER
FRONTMATTER="$(awk '/^---$/{if(p==0){p=1;next}else{exit}} p{print}' "$SKILL_FILE")"
# ── Check: name field ───────────────────────────────────────────────────────
local NAME_RAW NAME
NAME_RAW="$(echo "$FRONTMATTER" | grep "^name:" | head -1 | sed 's/^name:[[:space:]]*//')"
NAME="$(echo "$NAME_RAW" | tr -d "'\"" | tr -d '[:space:]')"
if [[ -z "$NAME" ]]; then
_fail "'name' field is missing or empty"
else
_pass "'name' field is present: $NAME"
if [[ "$NAME" != "$DIR_NAME" ]]; then
_fail "'name' ($NAME) does not match directory name ($DIR_NAME)"
else
_pass "'name' matches directory name"
fi
local NAME_LEN="${#NAME}"
if [[ $NAME_LEN -gt 64 ]]; then
_fail "'name' is $NAME_LEN characters (max 64)"
else
_pass "'name' length is $NAME_LEN (max 64)"
fi
if echo "$NAME" | grep -q '[^a-z0-9-]'; then
_fail "'name' contains invalid characters (only a-z, 0-9, - allowed)"
else
_pass "'name' uses only allowed characters (a-z, 0-9, -)"
fi
if [[ "$NAME" == -* ]]; then
_fail "'name' must not start with a hyphen"
else
_pass "'name' does not start with a hyphen"
fi
if [[ "$NAME" == *- ]]; then
_fail "'name' must not end with a hyphen"
else
_pass "'name' does not end with a hyphen"
fi
if echo "$NAME" | grep -q "\-\-"; then
_fail "'name' must not contain consecutive hyphens (--)"
else
_pass "'name' has no consecutive hyphens"
fi
fi
# ── Check: description field ────────────────────────────────────────────────
local DESC_LINE
DESC_LINE="$(echo "$FRONTMATTER" | grep "^description:" | head -1)"
if [[ -z "$DESC_LINE" ]]; then
_fail "'description' field is missing"
else
local DESC_VALUE
DESC_VALUE="$(echo "$DESC_LINE" | sed 's/^description:[[:space:]]*//')"
local DESC_FULL
if [[ "$DESC_VALUE" == ">" || "$DESC_VALUE" == "|" || -z "$DESC_VALUE" ]]; then
local DESC_BODY
DESC_BODY="$(awk '/^description:/{found=1; next} found && /^ /{print; next} found && /^[^ ]/{exit}' "$SKILL_FILE" | sed 's/^ //')"
DESC_FULL="$(echo "$DESC_BODY" | tr '\n' ' ' | sed 's/[[:space:]]*$//')"
else
DESC_FULL="$DESC_VALUE"
fi
if [[ -z "$DESC_FULL" ]]; then
_fail "'description' is empty"
else
local DESC_LEN="${#DESC_FULL}"
_pass "'description' is present (${DESC_LEN} chars)"
if [[ $DESC_LEN -gt 1024 ]]; then
_fail "'description' is $DESC_LEN characters (max 1024)"
else
_pass "'description' length is $DESC_LEN (max 1024)"
fi
fi
fi
# ── Check: body length ──────────────────────────────────────────────────────
local BODY_LINES
BODY_LINES="$(awk '/^---$/{n++} n>=2{print}' "$SKILL_FILE" | wc -l | tr -d ' ')"
if [[ $BODY_LINES -gt 500 ]]; then
_warn "SKILL.md body is $BODY_LINES lines (recommended max 500). Consider moving detail to references/."
else
_pass "SKILL.md body is $BODY_LINES lines (max 500 recommended)"
fi
# ── Optional directories ────────────────────────────────────────────────────
echo ""
_info "Optional directories:"
for dir in scripts references assets; do
if [[ -d "$SKILL_DIR/$dir" ]]; then
local FILE_COUNT
FILE_COUNT="$(find "$SKILL_DIR/$dir" -type f | wc -l | tr -d ' ')"
_info " $dir/ — found ($FILE_COUNT files)"
else
_info " $dir/ — not present"
fi
done
# ── Summary ─────────────────────────────────────────────────────────────────
echo ""
echo "──────────────────────────────────────────"
echo "Passed: $PASS | Failed: $FAIL | Warnings: $WARN"
echo ""
if [[ $FAIL -gt 0 ]]; then
echo "Result: ❌ FAILED"; return 1
elif [[ $WARN -gt 0 ]]; then
echo "Result: ✅ PASSED (with warnings)"; return 0
else
echo "Result: ✅ PASSED"; return 0
fi
}