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:
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# validate/lib/--env-vars-reader.sh — environment loading for the validate module
|
||||
# This module operates without env files (all input via CLI flags).
|
||||
# No env loading required.
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# validate/lib/--env-vars-validator.sh — env validation for the validate module
|
||||
# This module takes all input via --skill-dir CLI flag.
|
||||
# No environment variables required.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
. ./.scripts/validator/lib/-skill--execute.sh
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
# -------------------------------------------------------------------------------------
|
||||
#
|
||||
#
|
||||
#
|
||||
# -------------------------------------------------------------------------------------
|
||||
. ./.scripts/base/lib/--index.sh
|
||||
#
|
||||
# --> passed parameters are read & exported environment variables
|
||||
#
|
||||
. ./.scripts/validator/lib/--env-vars-reader.sh
|
||||
#
|
||||
# --> required environment variables are validated for existence
|
||||
#
|
||||
. ./.scripts/validator/lib/--env-vars-validator.sh
|
||||
#
|
||||
# --> available functions are imported/exported
|
||||
#
|
||||
. ./.scripts/validator/lib/--index-api.sh
|
||||
# -------------------------------------------------------------------------------------
|
||||
+212
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user