- release.sh health gate runs with MOSAIC_ENSURE_SKIP=1: the gated task run cannot re-enter release self-determination - run-task.sh warns on release drift instead of silently using a stale image - ROADMAP: M20 decision recorded (packages/* monorepo at usurpation, continuity-first); restructure sequenced as M20 phase 1 Closes #39
103 lines
3.5 KiB
Bash
Executable File
103 lines
3.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Skill lifecycle: install / activate / deactivate / uninstall / list.
|
|
#
|
|
# Usage:
|
|
# scripts/skill.sh install <source-dir | bundled-name>
|
|
# scripts/skill.sh activate <name>
|
|
# scripts/skill.sh deactivate <name>
|
|
# scripts/skill.sh uninstall <name>
|
|
# scripts/skill.sh list
|
|
#
|
|
# Layout (machine-scoped, under the data root):
|
|
# <dataRoot>/skills-available/<name> installed, not loadable
|
|
# <dataRoot>/skills-enabled/<name> 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/<name> 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 <source-dir|bundled-name>}"
|
|
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 <name>}"
|
|
[ -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 <name>}"
|
|
[ -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 <name>}"
|
|
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 <src> | activate <name> | deactivate <name> | uninstall <name> | list" >&2
|
|
exit 4
|
|
;;
|
|
esac
|