#!/usr/bin/env bash # Skill lifecycle: install / activate / deactivate / uninstall / list. # # Usage: # scripts/skill.sh install # scripts/skill.sh activate # scripts/skill.sh deactivate # scripts/skill.sh uninstall # scripts/skill.sh list # # Layout (machine-scoped, under the data root): # /skills-available/ installed, not loadable # /skills-enabled/ loadable by agent launches # # A skill not in skills-enabled is not enabled or available for use. # Lifecycle: install -> available; activate -> enabled; deactivate -> # available; uninstall -> removed (only from available). set -euo pipefail cd "$(dirname "$0")/.." # shellcheck source=common.sh source scripts/common.sh load_config load_release bootstrap_runtime_dir ENABLED="$MOSAIC_DEV_DIR/skills-enabled" AVAILABLE="$MOSAIC_DEV_DIR/skills-available" mkdir -p "$ENABLED" "$AVAILABLE" die() { local code="$1"; shift; echo "skill: $*" >&2; exit "$code"; } is_skill_dir() { [ -f "$1/SKILL.md" ]; } resolve_source() { # bundled name (skills/ in repo) or explicit path if [ -d "skills/$1" ]; then printf 'skills/%s' "$1"; return; fi if [ -d "$1" ]; then printf '%s' "$1"; return; fi die 4 "source skill dir not found: $1" } frontmatter_name() { # extract name: from SKILL.md frontmatter node -e ' const fs = require("fs"); const t = fs.readFileSync(process.argv[1], "utf8"); const m = t.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!m) process.exit(1); const name = (m[1].match(/^name:\s*(.+)$/m) || [])[1]; if (!name) process.exit(1); process.stdout.write(name.trim()); ' "$1/SKILL.md" } case "${1:-list}" in install) src="${2:?usage: skill.sh install }" resolve_source "$src" >/dev/null || die 4 "source skill dir not found: $src" SRC_DIR="$(resolve_source "$src")" is_skill_dir "$SRC_DIR" || die 4 "not a skill (missing SKILL.md): $SRC_DIR" NAME="$(basename "$SRC_DIR")" [ -d "$AVAILABLE/$NAME" ] && die 1 "already installed: $NAME" mkdir -p "$AVAILABLE" cp -r "$SRC_DIR" "$AVAILABLE/$NAME" echo "skill: installed $NAME -> skills-available" echo "skill: activate with scripts/skill.sh activate $NAME" ;; activate) name="${2:?usage: skill.sh activate }" [ -d "$AVAILABLE/$name" ] || die 4 "not installed: $name" [ -d "$ENABLED/$name" ] && die 1 "already enabled: $name" mv "$AVAILABLE/$name" "$ENABLED/$name" echo "skill: enabled $name" ;; deactivate) name="${2:?usage: skill.sh deactivate }" [ -d "$ENABLED/$name" ] || die 4 "not enabled: $name" mv "$ENABLED/$name" "$AVAILABLE/$name" echo "skill: deactivated $name -> skills-available" ;; uninstall) name="${2:?usage: skill.sh uninstall }" if [ -d "$ENABLED/$name" ]; then die 1 "refusing: $name is enabled - deactivate first" fi [ -d "$AVAILABLE/$name" ] || die 4 "not installed: $name" rm -rf "$AVAILABLE/$name" echo "skill: uninstalled $name" ;; list) echo "enabled:" for d in "$ENABLED"/*/; do [ -d "$d" ] && echo " $(basename "$d")" done echo "available:" for d in "$AVAILABLE"/*/; do [ -d "$d" ] && echo " $(basename "$d")" done ;; *) echo "usage: scripts/skill.sh install | activate | deactivate | uninstall | list" >&2 exit 4 ;; esac