Files
Zach Lloyd 15142ef26b Add common skills management scripts (#10)
* Add common skills management scripts

Co-Authored-By: Oz <oz-agent@warp.dev>

* Install all common skills dynamically

Co-Authored-By: Oz <oz-agent@warp.dev>

* Share common skills script helpers

Co-Authored-By: Oz <oz-agent@warp.dev>

* Update common skills script structure

Co-Authored-By: Oz <oz-agent@warp.dev>

* Handle verify-only without lock file

Co-Authored-By: Oz <oz-agent@warp.dev>

* Refine common skills installer

Co-Authored-By: Oz <oz-agent@warp.dev>

* Add common skills repair helpers

Co-Authored-By: Oz <oz-agent@warp.dev>

* Add update-skill lockfile test marker

Co-Authored-By: Oz <oz-agent@warp.dev>

* Add common skills installer update path

Co-Authored-By: Oz <oz-agent@warp.dev>

* Refine common skills update workflow

Co-Authored-By: Oz <oz-agent@warp.dev>

* Run skills CLI non-interactively

Co-Authored-By: Oz <oz-agent@warp.dev>

* Remove common skills script dead code

Co-Authored-By: Oz <oz-agent@warp.dev>

* Document common skills client invocation

Co-Authored-By: Oz <oz-agent@warp.dev>

* Fix common skills lock restore flows

Ensure no-target non-interactive flows fail before attempting TTY prompts, and restore global common skills from a temporary lock checkout so restore paths do not rewrite the client repo lock.

Co-Authored-By: Oz <oz-agent@warp.dev>

* Validate common skills script dependencies

Co-Authored-By: Oz <oz-agent@warp.dev>

---------

Co-authored-by: Oz <oz-agent@warp.dev>
2026-05-11 10:26:04 -06:00

297 lines
6.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -eo pipefail
REPO_ROOT="${WARP_COMMON_SKILLS_TARGET_REPO_ROOT:-$(pwd)}"
LOCK_FILE=""
STAMP_RELATIVE_PATH="warp/common-skills-lock.hash"
CLEAR_LOCK=0
TARGET="${WARP_COMMON_SKILLS_INSTALL_TARGET:-project}"
resolve_repo_root() {
cd "$1" && pwd
}
project_stamp_file() {
local git_path=""
if git_path="$(git -C "${REPO_ROOT}" rev-parse --git-path "${STAMP_RELATIVE_PATH}" 2>/dev/null)"; then
case "${git_path}" in
/*)
echo "${git_path}"
;;
*)
echo "${REPO_ROOT}/${git_path}"
;;
esac
return
fi
echo "${REPO_ROOT}/.agents/skills/.common-skills-lock.hash"
}
project_exclude_file() {
local git_path=""
if git_path="$(git -C "${REPO_ROOT}" rev-parse --git-path "info/exclude" 2>/dev/null)"; then
case "${git_path}" in
/*)
echo "${git_path}"
;;
*)
echo "${REPO_ROOT}/${git_path}"
;;
esac
return
fi
return 1
}
global_stamp_file() {
echo "${HOME}/.agents/warp/common-skills-lock.hash"
}
stamp_file() {
if [[ "${TARGET}" = "global" ]]; then
global_stamp_file
else
project_stamp_file
fi
}
normalize_common_skills_target() {
local target
target="$(printf "%s" "$1" | tr '[:upper:]' '[:lower:]')"
case "${target}" in
""|p|project|1)
echo "project"
;;
g|global|2)
echo "global"
;;
*)
return 1
;;
esac
}
locked_skill_names() {
node -e '
const fs = require("fs");
const lock = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
for (const name of Object.keys(lock.skills || {})) {
console.log(name);
}
' "${LOCK_FILE}"
}
locked_skill_paths() {
node -e '
const fs = require("fs");
const lock = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
for (const skill of Object.values(lock.skills || {})) {
console.log(skill.skillPath);
}
' "${LOCK_FILE}"
}
target_description() {
if [[ "${TARGET}" = "global" ]]; then
echo "~/.agents/skills"
else
echo ".agents/skills"
fi
}
validate_required_commands() {
local missing=0
local tool=""
for tool in "$@"; do
if ! command -v "${tool}" >/dev/null 2>&1; then
echo "error: required command '${tool}' is not installed or not on PATH." >&2
missing=1
fi
done
if [[ "${missing}" -eq 1 ]]; then
echo "Install the missing tools, then rerun scripts/remove_common_skills." >&2
exit 1
fi
}
usage() {
cat <<EOF
Usage: scripts/remove_common_skills [options]
Remove common agent skills listed in the standard project skills-lock.json.
Options:
--repo-root <path> Repository containing skills-lock.json and project skills.
--project Remove from this checkout's .agents/skills directory (default).
--global Remove from ~/.agents/skills for the current user.
--clear-lock Also remove skills-lock.json after removing locked skills.
-h, --help Show this help message.
Environment:
WARP_COMMON_SKILLS_INSTALL_TARGET=project|global
Choose the removal target when no explicit --project/--global flag is provided.
WARP_COMMON_SKILLS_TARGET_REPO_ROOT=/path/to/repo
Repository containing skills-lock.json and project-local .agents/skills.
EOF
}
remove_project_from_lock() {
local path=""
local skill_dir=""
while IFS= read -r path; do
if [[ -z "${path}" ]]; then
continue
fi
case "${path}" in
.agents/skills/*/SKILL.md)
skill_dir="$(dirname "${REPO_ROOT}/${path}")"
;;
*)
echo "error: refusing to remove unexpected skill path from lock file: ${path}" >&2
exit 1
;;
esac
case "${skill_dir}" in
"${REPO_ROOT}/.agents/skills/"*)
rm -rf -- "${skill_dir}"
;;
*)
echo "error: refusing to remove path outside .agents/skills: ${skill_dir}" >&2
exit 1
;;
esac
done < <(locked_skill_paths)
}
remove_global_from_lock() {
local name=""
local skill_dir=""
while IFS= read -r name; do
if [[ -z "${name}" ]]; then
continue
fi
case "${name}" in
*/*|.|..)
echo "error: refusing to remove unexpected skill name from lock file: ${name}" >&2
exit 1
;;
esac
skill_dir="${HOME}/.agents/skills/${name}"
rm -rf -- "${skill_dir}"
done < <(locked_skill_names)
}
remove_from_lock() {
if [[ "${TARGET}" = "global" ]]; then
remove_global_from_lock
else
remove_project_from_lock
remove_project_common_skills_ignore_block
fi
rm -f -- "$(stamp_file)"
}
remove_project_common_skills_ignore_block() {
local exclude_file=""
local tmp_file=""
local marker_start="# BEGIN warp common-skills"
local marker_end="# END warp common-skills"
if ! exclude_file="$(project_exclude_file)"; then
return 0
fi
if [[ ! -f "${exclude_file}" ]]; then
return 0
fi
tmp_file="$(mktemp)"
awk -v start="${marker_start}" -v end="${marker_end}" '
$0 == start { skipping = 1; next }
$0 == end { skipping = 0; next }
!skipping { print }
' "${exclude_file}" > "${tmp_file}"
cat "${tmp_file}" > "${exclude_file}"
rm -f "${tmp_file}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--repo-root)
if [[ -n "${2:-}" ]]; then
REPO_ROOT="$2"
shift 2
else
echo "error: argument for $1 is missing" >&2
usage >&2
exit 1
fi
;;
--project)
TARGET="project"
shift
;;
--global)
TARGET="global"
shift
;;
--clear-lock)
CLEAR_LOCK=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "error: unknown argument: $1" >&2
usage >&2
exit 1
;;
esac
done
if ! REPO_ROOT="$(resolve_repo_root "${REPO_ROOT}")"; then
echo "error: repo root does not exist: ${REPO_ROOT}" >&2
exit 1
fi
LOCK_FILE="${REPO_ROOT}/skills-lock.json"
if [[ "${TARGET}" != "project" && "${TARGET}" != "global" ]]; then
echo "error: invalid common skills removal target: ${TARGET} (expected project or global)" >&2
usage >&2
exit 1
fi
if [[ ! -f "${LOCK_FILE}" ]]; then
if [[ "${CLEAR_LOCK}" -eq 1 ]]; then
echo "Skills lock file is already absent: ${LOCK_FILE}"
exit 0
fi
echo "error: missing skills lock file: ${LOCK_FILE}" >&2
exit 1
fi
validate_required_commands git node
echo "Removing common agent skills listed in skills-lock.json from $(target_description)."
remove_from_lock
if [[ "${CLEAR_LOCK}" -eq 1 ]]; then
rm -f -- "${LOCK_FILE}"
fi