Files
newznab-tmux-NNTmux/scripts/generate-global-skills-installer.sh
T
2026-08-05 10:50:00 +02:00

223 lines
8.7 KiB
Bash

#!/usr/bin/env bash
set -Eeuo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
LOCK_FILE="${1:-${PROJECT_ROOT}/skills-lock.json}"
OUTPUT_FILE="${2:-${SCRIPT_DIR}/install-global-skills.sh}"
die() {
printf 'Error: %s\n' "$1" >&2
exit 1
}
for command_name in awk chmod mkdir mktemp mv rm sort; do
if ! command -v "${command_name}" >/dev/null 2>&1; then
die "Required command not found: ${command_name}"
fi
done
[[ -f "${LOCK_FILE}" ]] || die "Skills lockfile not found: ${LOCK_FILE}"
mapfile -t records < <(
awk '
/^ "[^"]+": \{$/ {
skill = $1
gsub(/[":]/, "", skill)
next
}
$1 == "\"source\":" {
source = $2
gsub(/[",]/, "", source)
print source "\t" skill
skill = ""
}
' "${LOCK_FILE}" | LC_ALL=C sort -t $'\t' -k1,1 -k2,2
)
(( ${#records[@]} > 0 )) || die "No skills found in lockfile: ${LOCK_FILE}"
temporary_file="$(mktemp "${OUTPUT_FILE}.tmp.XXXXXX")"
cleanup() {
rm -f -- "${temporary_file}"
}
trap cleanup EXIT
{
printf '%s\n' '#!/usr/bin/env bash'
printf '\n'
printf '%s\n' '# This file is generated by scripts/generate-global-skills-installer.sh.'
printf '%s\n' '# Do not edit manually; regenerate it after changing skills-lock.json.'
printf '%s\n' '# shellcheck shell=bash'
printf '\n'
printf '%s\n' 'set -Eeuo pipefail'
printf '\n'
printf 'SKILL_COUNT=%q\n' "${#records[@]}"
printf 'DRY_RUN=0\n'
printf 'COPY_MODE=0\n'
printf 'SHARED_SKILLS_DIR="${SKILLS_GLOBAL_DIR:-${HOME:?HOME must be set}/.agents/skills}"\n'
printf 'CODEX_HOME_DIR="${CODEX_HOME:-${HOME:?HOME must be set}/.codex}"\n'
printf 'CODEX_SKILLS_DIR="${CODEX_HOME_DIR}/skills"\n'
printf '\n'
printf '%s\n' 'usage() {'
printf '%s\n' ' cat <<'"'"'USAGE'"'"''
printf '%s\n' 'Usage: scripts/install-global-skills.sh [options]'
printf '\n'
printf '%s\n' 'Install the repository skill baseline globally and bridge it into Codex.'
printf '\n'
printf '%s\n' 'Options:'
printf '%s\n' ' --dry-run Show commands and filesystem changes without applying them.'
printf '%s\n' ' --copy Copy skills into Codex instead of creating symlinks.'
printf '%s\n' ' --help Show this help text.'
printf '\n'
printf '%s\n' 'Environment:'
printf '%s\n' ' SKILLS_GLOBAL_DIR Shared global skills directory (default: ~/.agents/skills).'
printf '%s\n' ' CODEX_HOME Codex home directory (default: ~/.codex).'
printf '%s\n' 'USAGE'
printf '%s\n' '}'
printf '\n'
printf '%s\n' 'die() {'
printf '%s\n' ' printf '"'"'Error: %s\n'"'"' "$1" >&2'
printf '%s\n' ' exit 1'
printf '%s\n' '}'
printf '\n'
printf '%s\n' 'print_command() {'
printf '%s\n' ' printf '"'"'+'"'"''
printf '%s\n' ' printf '"'"' %q'"'"' "$@"'
printf '%s\n' ' printf '"'"'\n'"'"''
printf '%s\n' '}'
printf '\n'
printf '%s\n' 'run_command() {'
printf '%s\n' ' if (( DRY_RUN )); then'
printf '%s\n' ' print_command "$@"'
printf '%s\n' ' return 0'
printf '%s\n' ' fi'
printf '%s\n' ' "$@"'
printf '%s\n' '}'
printf '\n'
printf '%s\n' 'require_command() {'
printf '%s\n' ' command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"'
printf '%s\n' '}'
printf '\n'
printf '%s\n' 'while (( $# > 0 )); do'
printf '%s\n' ' case "$1" in'
printf '%s\n' ' --dry-run)'
printf '%s\n' ' DRY_RUN=1'
printf '%s\n' ' ;;'
printf '%s\n' ' --copy)'
printf '%s\n' ' COPY_MODE=1'
printf '%s\n' ' ;;'
printf '%s\n' ' --help|-h)'
printf '%s\n' ' usage'
printf '%s\n' ' exit 0'
printf '%s\n' ' ;;'
printf '%s\n' ' *)'
printf '%s\n' ' die "Unknown option: $1 (use --help for usage)"'
printf '%s\n' ' ;;'
printf '%s\n' ' esac'
printf '%s\n' ' shift'
printf '%s\n' 'done'
printf '\n'
printf '%s\n' 'require_command bash'
printf '%s\n' 'require_command npx'
printf '%s\n' 'require_command mkdir'
printf '%s\n' 'require_command ln'
printf '%s\n' 'require_command readlink'
printf '%s\n' 'if (( COPY_MODE )); then'
printf '%s\n' ' require_command cp'
printf '%s\n' ' require_command diff'
printf '%s\n' 'fi'
printf '\n'
printf '%s\n' 'printf '"'"'Installing %d skills globally...\n'"'"' "$SKILL_COUNT"'
printf '\n'
current_source=""
source_skill_args=()
for record in "${records[@]}"; do
source="${record%%$'\t'*}"
skill="${record#*$'\t'}"
if [[ -n "${current_source}" && "${source}" != "${current_source}" ]]; then
printf 'run_command npx --yes skills add %q --global --yes' "${current_source}"
for skill_arg in "${source_skill_args[@]}"; do
printf ' --skill %q' "${skill_arg}"
done
printf '\n\n'
source_skill_args=()
fi
current_source="${source}"
source_skill_args+=("${skill}")
done
printf 'run_command npx --yes skills add %q --global --yes' "${current_source}"
for skill_arg in "${source_skill_args[@]}"; do
printf ' --skill %q' "${skill_arg}"
done
printf '\n\n'
printf '%s\n' 'SKILLS=('
for record in "${records[@]}"; do
skill="${record#*$'\t'}"
printf ' %q\n' "${skill}"
done
printf '%s\n' ')'
printf '\n'
printf '%s\n' 'if (( DRY_RUN )); then'
printf '%s\n' ' printf '"'"'Would bridge skills from %s to %s\n'"'"' "$SHARED_SKILLS_DIR" "$CODEX_SKILLS_DIR"'
printf '%s\n' ' for skill in "${SKILLS[@]}"; do'
printf '%s\n' ' if (( COPY_MODE )); then'
printf '%s\n' ' printf '"'"'+ copy %s -> %s/%s\n'"'"' "$SHARED_SKILLS_DIR/$skill" "$CODEX_SKILLS_DIR" "$skill"'
printf '%s\n' ' else'
printf '%s\n' ' printf '"'"'+ ln -s %s %s/%s\n'"'"' "$SHARED_SKILLS_DIR/$skill" "$CODEX_SKILLS_DIR" "$skill"'
printf '%s\n' ' fi'
printf '%s\n' ' done'
printf '%s\n' ' exit 0'
printf '%s\n' 'fi'
printf '\n'
printf '%s\n' 'run_command mkdir -p "$CODEX_SKILLS_DIR"'
printf '%s\n' 'conflict_count=0'
printf '%s\n' 'for skill in "${SKILLS[@]}"; do'
printf '%s\n' ' source_path="$SHARED_SKILLS_DIR/$skill"'
printf '%s\n' ' target_path="$CODEX_SKILLS_DIR/$skill"'
printf '%s\n' ' [[ -d "$source_path" ]] || { printf '"'"'Error: Installed skill is missing: %s\n'"'"' "$source_path" >&2; conflict_count=$((conflict_count + 1)); continue; }'
printf '%s\n' ' if [[ -L "$target_path" ]]; then'
printf '%s\n' ' if [[ "$(readlink "$target_path")" == "$source_path" ]]; then'
printf '%s\n' ' printf '"'"'Already linked: %s\n'"'"' "$skill"'
printf '%s\n' ' continue'
printf '%s\n' ' fi'
printf '%s\n' ' printf '"'"'Error: Refusing to replace unmanaged symlink: %s\n'"'"' "$target_path" >&2'
printf '%s\n' ' conflict_count=$((conflict_count + 1))'
printf '%s\n' ' continue'
printf '%s\n' ' fi'
printf '%s\n' ' if [[ -e "$target_path" ]]; then'
printf '%s\n' ' if (( COPY_MODE )) && [[ -d "$target_path" ]] && diff -qr -- "$source_path" "$target_path" >/dev/null; then'
printf '%s\n' ' printf '"'"'Already copied: %s\n'"'"' "$skill"'
printf '%s\n' ' continue'
printf '%s\n' ' fi'
printf '%s\n' ' printf '"'"'Error: Refusing to overwrite existing path: %s\n'"'"' "$target_path" >&2'
printf '%s\n' ' conflict_count=$((conflict_count + 1))'
printf '%s\n' ' continue'
printf '%s\n' ' fi'
printf '%s\n' ' if (( COPY_MODE )); then'
printf '%s\n' ' run_command cp -R -- "$source_path" "$target_path"'
printf '%s\n' ' printf '"'"'Copied: %s\n'"'"' "$skill"'
printf '%s\n' ' else'
printf '%s\n' ' run_command ln -s -- "$source_path" "$target_path"'
printf '%s\n' ' printf '"'"'Linked: %s\n'"'"' "$skill"'
printf '%s\n' ' fi'
printf '%s\n' 'done'
printf '\n'
printf '%s\n' '(( conflict_count == 0 )) || die "${conflict_count} skill bridge conflict(s) require manual resolution"'
printf '%s\n' 'printf '"'"'Global skills installation complete.\n'"'"''
} >"${temporary_file}"
chmod 755 "${temporary_file}"
mkdir -p -- "$(dirname -- "${OUTPUT_FILE}")"
mv -- "${temporary_file}" "${OUTPUT_FILE}"
trap - EXIT
printf 'Generated %s from %s (%d skills).\n' "${OUTPUT_FILE}" "${LOCK_FILE}" "${#records[@]}"