140 lines
3.8 KiB
Bash
Executable File
140 lines
3.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# gameservice-fe-agent skill linker
|
||
# 공통 저장소의 skills/* 를 프로젝트의 .claude/skills/* 로 심볼릭 링크합니다.
|
||
# 심볼릭 링크이므로 submodule 업데이트 시 skill 도 자동으로 최신 버전이 됩니다.
|
||
#
|
||
# 사용법:
|
||
# bash .claude/common/scripts/link-skills.sh # 모든 skill 링크
|
||
# bash .claude/common/scripts/link-skills.sh <skill-name> # 특정 skill 만
|
||
# bash .claude/common/scripts/link-skills.sh --dry-run # 실제 링크 없이 미리보기
|
||
# bash .claude/common/scripts/link-skills.sh --force # 기존 링크/폴더 덮어쓰기
|
||
# bash .claude/common/scripts/link-skills.sh --unlink # 공통 skill 링크 제거
|
||
#
|
||
set -euo pipefail
|
||
|
||
COMMON_PATH=".claude/common"
|
||
SKILLS_SRC="$COMMON_PATH/skills"
|
||
SKILLS_DEST=".claude/skills"
|
||
|
||
MODE="safe" # safe | force | dry-run | unlink
|
||
TARGET=""
|
||
|
||
for arg in "$@"; do
|
||
case "$arg" in
|
||
--dry-run) MODE="dry-run" ;;
|
||
--force) MODE="force" ;;
|
||
--unlink) MODE="unlink" ;;
|
||
-h|--help)
|
||
grep '^#' "$0" | sed 's/^# \{0,1\}//'
|
||
exit 0
|
||
;;
|
||
-*)
|
||
echo "❌ 알 수 없는 옵션: $arg" >&2
|
||
exit 1
|
||
;;
|
||
*)
|
||
TARGET="$arg"
|
||
;;
|
||
esac
|
||
done
|
||
|
||
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||
echo "❌ 현재 디렉토리는 Git 저장소가 아닙니다." >&2
|
||
exit 1
|
||
fi
|
||
|
||
cd "$(git rev-parse --show-toplevel)"
|
||
|
||
if [[ ! -d "$SKILLS_SRC" ]]; then
|
||
echo "❌ $SKILLS_SRC 가 없습니다. 먼저 submodule 을 설치/업데이트하세요:" >&2
|
||
echo " git submodule update --init --recursive" >&2
|
||
exit 1
|
||
fi
|
||
|
||
mkdir -p "$SKILLS_DEST"
|
||
|
||
# 링크 대상 결정
|
||
declare -a skills
|
||
if [[ -n "$TARGET" ]]; then
|
||
if [[ ! -d "$SKILLS_SRC/$TARGET" ]]; then
|
||
echo "❌ '$TARGET' skill 을 $SKILLS_SRC 에서 찾지 못했습니다." >&2
|
||
echo " 사용 가능한 skill:" >&2
|
||
ls -1 "$SKILLS_SRC" 2>/dev/null | grep -v '^README' | sed 's/^/ - /' >&2
|
||
exit 1
|
||
fi
|
||
skills=("$TARGET")
|
||
else
|
||
shopt -s nullglob
|
||
for dir in "$SKILLS_SRC"/*/; do
|
||
name="$(basename "$dir")"
|
||
# README 같은 파일은 이미 걸러지지만 추가 보호
|
||
[[ "$name" == "README"* ]] && continue
|
||
skills+=("$name")
|
||
done
|
||
fi
|
||
|
||
if [[ ${#skills[@]} -eq 0 ]]; then
|
||
echo "ℹ️ 링크할 skill 이 없습니다."
|
||
exit 0
|
||
fi
|
||
|
||
echo "🔗 공통 skill 링크 (mode=$MODE)"
|
||
echo " source: $SKILLS_SRC"
|
||
echo " dest: $SKILLS_DEST"
|
||
echo ""
|
||
|
||
linked=0
|
||
skipped=0
|
||
removed=0
|
||
|
||
for name in "${skills[@]}"; do
|
||
src="../common/skills/$name" # 심볼릭 링크의 상대 경로 (.claude/skills 기준)
|
||
dest="$SKILLS_DEST/$name"
|
||
|
||
case "$MODE" in
|
||
unlink)
|
||
if [[ -L "$dest" ]]; then
|
||
rm "$dest"
|
||
echo " 🗑 $dest (링크 제거)"
|
||
removed=$((removed + 1))
|
||
else
|
||
echo " ⏭ $dest (링크 아님 - 건너뜀)"
|
||
fi
|
||
;;
|
||
dry-run)
|
||
if [[ -e "$dest" || -L "$dest" ]]; then
|
||
echo " ⏭ $dest (이미 존재)"
|
||
else
|
||
echo " ➕ ln -s $src $dest"
|
||
fi
|
||
;;
|
||
force)
|
||
rm -rf "$dest"
|
||
ln -s "$src" "$dest"
|
||
echo " ✅ $dest → $src (덮어씀)"
|
||
linked=$((linked + 1))
|
||
;;
|
||
safe)
|
||
if [[ -L "$dest" ]]; then
|
||
echo " ⏭ $dest (이미 링크됨)"
|
||
skipped=$((skipped + 1))
|
||
elif [[ -e "$dest" ]]; then
|
||
echo " ⚠️ $dest (실제 파일/폴더가 존재. --force 로 덮어쓰세요)"
|
||
skipped=$((skipped + 1))
|
||
else
|
||
ln -s "$src" "$dest"
|
||
echo " ✅ $dest → $src"
|
||
linked=$((linked + 1))
|
||
fi
|
||
;;
|
||
esac
|
||
done
|
||
|
||
echo ""
|
||
case "$MODE" in
|
||
unlink) echo "🎉 제거 완료: $removed 개 링크 제거됨";;
|
||
dry-run) echo "🔍 미리보기 완료 (실제 변경 없음)";;
|
||
*) echo "🎉 완료: 링크 $linked개 / 건너뜀 $skipped개";;
|
||
esac
|