toggle-external.sh 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #!/usr/bin/env bash
  2. # ============================================================
  3. # lib/toggle-external.sh — enable/disable non-plugin tools
  4. #
  5. # Marketplace plugins are toggled by `claude plugin enable|disable`.
  6. # Tools distributed outside the marketplace (gstack submodule, emil
  7. # curl install, npx-installed skills) have no such lever — they live
  8. # as symlinks inside skills/. This script moves those symlinks
  9. # to/from skills-disabled/ so Claude Code stops/starts scanning them.
  10. #
  11. # Usage:
  12. # toggle-external.sh list
  13. # toggle-external.sh status <tool>
  14. # toggle-external.sh enable <tool>
  15. # toggle-external.sh disable <tool>
  16. #
  17. # Managed tools:
  18. # gstack — per-skill symlinks populated by gstack's own setup
  19. # emil-design-eng — single symlink → skills-external/emil-design-eng
  20. # darwin-skill — single symlink → ~/.agents/skills/darwin-skill
  21. # find-skills — single symlink → ~/.agents/skills/find-skills
  22. # ============================================================
  23. set -euo pipefail
  24. REPO="$(cd "$(dirname "$0")/.." && pwd)"
  25. SKILLS_DIR="$REPO/skills"
  26. DISABLED_DIR="$REPO/skills-disabled"
  27. GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
  28. ok() { echo -e "${GREEN}✓${NC} $1"; }
  29. warn() { echo -e "${YELLOW}⚠${NC} $1"; }
  30. err() { echo -e "${RED}✗${NC} $1"; }
  31. # All non-plugin tools this script can toggle.
  32. MANAGED_TOOLS=(gstack emil-design-eng darwin-skill find-skills)
  33. # Prints the names (directory basenames) that belong to "gstack".
  34. # Source of truth: skills-external/gstack/*/SKILL.md. The repo's
  35. # skills/<name> symlinks are generated from these by gstack ./setup.
  36. gstack_skills() {
  37. local gstack_src="$REPO/skills-external/gstack"
  38. [ -d "$gstack_src" ] || return 0
  39. for d in "$gstack_src"/*/; do
  40. [ -f "${d}SKILL.md" ] || continue
  41. basename "$d"
  42. done
  43. }
  44. # Prints "enabled" / "disabled" / "missing" for a tool.
  45. status_tool() {
  46. local tool="$1"
  47. case "$tool" in
  48. gstack)
  49. [ -d "$REPO/skills-external/gstack" ] || { echo "missing"; return; }
  50. while read -r name; do
  51. [ -e "$SKILLS_DIR/$name" ] && { echo "enabled"; return; }
  52. done < <(gstack_skills)
  53. echo "disabled"
  54. ;;
  55. emil-design-eng)
  56. [ -d "$REPO/skills-external/emil-design-eng" ] || { echo "missing"; return; }
  57. [ -e "$SKILLS_DIR/emil-design-eng" ] && echo "enabled" || echo "disabled"
  58. ;;
  59. darwin-skill|find-skills)
  60. [ -d "$HOME/.agents/skills/$tool" ] || { echo "missing"; return; }
  61. [ -e "$SKILLS_DIR/$tool" ] && echo "enabled" || echo "disabled"
  62. ;;
  63. *)
  64. echo "unknown"; return 1 ;;
  65. esac
  66. }
  67. disable_tool() {
  68. local tool="$1"
  69. mkdir -p "$DISABLED_DIR"
  70. case "$tool" in
  71. gstack)
  72. local moved=0
  73. while read -r name; do
  74. [ -e "$SKILLS_DIR/$name" ] || continue
  75. # Clobber any stale destination. gstack ./setup now creates
  76. # skills/<name>/ as directories, so mv onto an existing dir
  77. # would nest it (gstack__<name>/<name>/) instead of renaming.
  78. # Content is symlinks to the submodule — `gstack ./setup` regenerates.
  79. rm -rf "$DISABLED_DIR/gstack__$name"
  80. mv "$SKILLS_DIR/$name" "$DISABLED_DIR/gstack__$name"
  81. moved=$((moved + 1))
  82. done < <(gstack_skills)
  83. ok "gstack disabled ($moved symlinks moved)"
  84. ;;
  85. emil-design-eng|darwin-skill|find-skills)
  86. if [ -e "$SKILLS_DIR/$tool" ]; then
  87. rm -rf "${DISABLED_DIR:?}/${tool:?}"
  88. mv "$SKILLS_DIR/$tool" "$DISABLED_DIR/$tool"
  89. ok "$tool disabled"
  90. else
  91. warn "$tool already disabled"
  92. fi
  93. ;;
  94. *) err "Unknown tool: $tool"; return 1 ;;
  95. esac
  96. }
  97. enable_tool() {
  98. local tool="$1"
  99. case "$tool" in
  100. gstack)
  101. local moved=0
  102. if [ -d "$DISABLED_DIR" ]; then
  103. for entry in "$DISABLED_DIR"/gstack__*; do
  104. [ -e "$entry" ] || continue
  105. local name
  106. name="$(basename "$entry" | sed 's/^gstack__//')"
  107. rm -rf "${SKILLS_DIR:?}/${name:?}"
  108. mv "$entry" "$SKILLS_DIR/$name"
  109. moved=$((moved + 1))
  110. done
  111. fi
  112. if [ "$moved" -eq 0 ]; then
  113. warn "gstack was not disabled — re-run gstack setup to (re)create symlinks"
  114. else
  115. ok "gstack enabled ($moved symlinks restored)"
  116. fi
  117. ;;
  118. emil-design-eng|darwin-skill|find-skills)
  119. if [ -e "$DISABLED_DIR/$tool" ]; then
  120. rm -rf "${SKILLS_DIR:?}/${tool:?}"
  121. mv "$DISABLED_DIR/$tool" "$SKILLS_DIR/$tool"
  122. ok "$tool enabled"
  123. elif [ -e "$SKILLS_DIR/$tool" ]; then
  124. warn "$tool already enabled"
  125. else
  126. err "$tool not installed — run: make plugin"
  127. return 1
  128. fi
  129. ;;
  130. *) err "Unknown tool: $tool"; return 1 ;;
  131. esac
  132. }
  133. list_all() {
  134. printf "%-20s %s\n" "TOOL" "STATUS"
  135. printf "%-20s %s\n" "----" "------"
  136. for t in "${MANAGED_TOOLS[@]}"; do
  137. printf "%-20s %s\n" "$t" "$(status_tool "$t")"
  138. done
  139. }
  140. usage() {
  141. sed -n '3,23p' "$0" | sed 's/^# \?//'
  142. exit "${1:-0}"
  143. }
  144. main() {
  145. local cmd="${1:-}"
  146. case "$cmd" in
  147. list) list_all ;;
  148. status) [ $# -ge 2 ] || usage 1; status_tool "$2" ;;
  149. enable) [ $# -ge 2 ] || usage 1; enable_tool "$2" ;;
  150. disable) [ $# -ge 2 ] || usage 1; disable_tool "$2" ;;
  151. ""|-h|--help|help) usage 0 ;;
  152. *) err "Unknown command: $cmd"; usage 1 ;;
  153. esac
  154. }
  155. main "$@"