auto-detect-components.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. #!/usr/bin/env bash
  2. #############################################################################
  3. # Auto-Detect Components Script
  4. # Scans .opencode directory for new components not in registry
  5. # Suggests additions with proper metadata
  6. #############################################################################
  7. set -e
  8. # Colors
  9. RED='\033[0;31m'
  10. GREEN='\033[0;32m'
  11. YELLOW='\033[1;33m'
  12. BLUE='\033[0;34m'
  13. CYAN='\033[0;36m'
  14. BOLD='\033[1m'
  15. NC='\033[0m'
  16. # Configuration
  17. REGISTRY_FILE="registry.json"
  18. REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
  19. AUTO_ADD=false
  20. DRY_RUN=false
  21. # Arrays to store new components
  22. declare -a NEW_COMPONENTS
  23. #############################################################################
  24. # Utility Functions
  25. #############################################################################
  26. print_header() {
  27. echo -e "${CYAN}${BOLD}"
  28. echo "╔════════════════════════════════════════════════════════════════╗"
  29. echo "║ ║"
  30. echo "║ Auto-Detect Components v1.0.0 ║"
  31. echo "║ ║"
  32. echo "╚════════════════════════════════════════════════════════════════╝"
  33. echo -e "${NC}"
  34. }
  35. print_success() {
  36. echo -e "${GREEN}✓${NC} $1"
  37. }
  38. print_error() {
  39. echo -e "${RED}✗${NC} $1"
  40. }
  41. print_warning() {
  42. echo -e "${YELLOW}⚠${NC} $1"
  43. }
  44. print_info() {
  45. echo -e "${BLUE}ℹ${NC} $1"
  46. }
  47. usage() {
  48. echo "Usage: $0 [OPTIONS]"
  49. echo ""
  50. echo "Options:"
  51. echo " -a, --auto-add Automatically add new components to registry"
  52. echo " -d, --dry-run Show what would be added without modifying registry"
  53. echo " -h, --help Show this help message"
  54. echo ""
  55. exit 0
  56. }
  57. #############################################################################
  58. # Component Detection
  59. #############################################################################
  60. extract_metadata_from_file() {
  61. local file=$1
  62. local id=""
  63. local name=""
  64. local description=""
  65. # Try to extract from frontmatter (YAML)
  66. if grep -q "^---$" "$file" 2>/dev/null; then
  67. # Extract description from frontmatter
  68. description=$(sed -n '/^---$/,/^---$/p' "$file" | grep "^description:" | sed 's/description: *"\?\(.*\)"\?/\1/' | head -1)
  69. fi
  70. # If no description in frontmatter, try to get from first heading or paragraph
  71. if [ -z "$description" ]; then
  72. description=$(grep -m 1 "^# " "$file" | sed 's/^# //' || echo "")
  73. fi
  74. # Generate ID from filename
  75. local filename
  76. filename=$(basename "$file" .md)
  77. id=$(echo "$filename" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
  78. # Generate name from filename (capitalize words)
  79. name=$(echo "$filename" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
  80. echo "${id}|${name}|${description}"
  81. }
  82. detect_component_type() {
  83. local path=$1
  84. if [[ "$path" == *"/agent/subagents/"* ]]; then
  85. echo "subagent"
  86. elif [[ "$path" == *"/agent/"* ]]; then
  87. echo "agent"
  88. elif [[ "$path" == *"/command/"* ]]; then
  89. echo "command"
  90. elif [[ "$path" == *"/tool/"* ]]; then
  91. echo "tool"
  92. elif [[ "$path" == *"/plugin/"* ]]; then
  93. echo "plugin"
  94. elif [[ "$path" == *"/context/"* ]]; then
  95. echo "context"
  96. else
  97. echo "unknown"
  98. fi
  99. }
  100. get_registry_key() {
  101. local type=$1
  102. case "$type" in
  103. config) echo "config" ;;
  104. *) echo "${type}s" ;;
  105. esac
  106. }
  107. scan_for_new_components() {
  108. print_info "Scanning for new components..."
  109. echo ""
  110. # Get all paths from registry
  111. local registry_paths
  112. registry_paths=$(jq -r '.components | to_entries[] | .value[] | .path' "$REGISTRY_FILE" 2>/dev/null | sort -u)
  113. # Scan .opencode directory
  114. local categories=("agent" "command" "tool" "plugin" "context")
  115. for category in "${categories[@]}"; do
  116. local category_dir="$REPO_ROOT/.opencode/$category"
  117. if [ ! -d "$category_dir" ]; then
  118. continue
  119. fi
  120. # Find all .md files (excluding node_modules, tests, docs)
  121. while IFS= read -r file; do
  122. local rel_path="${file#$REPO_ROOT/}"
  123. # Skip node_modules, tests, docs, templates
  124. if [[ "$rel_path" == *"/node_modules/"* ]] || \
  125. [[ "$rel_path" == *"/tests/"* ]] || \
  126. [[ "$rel_path" == *"/docs/"* ]] || \
  127. [[ "$rel_path" == *"/template"* ]] || \
  128. [[ "$rel_path" == *"README.md" ]] || \
  129. [[ "$rel_path" == *"index.md" ]]; then
  130. continue
  131. fi
  132. # Check if this path is in registry
  133. # shellcheck disable=SC2143
  134. if ! echo "$registry_paths" | grep -q "^${rel_path}$"; then
  135. # Extract metadata
  136. local metadata
  137. metadata=$(extract_metadata_from_file "$file")
  138. IFS='|' read -r id name description <<< "$metadata"
  139. # Detect component type
  140. local comp_type
  141. comp_type=$(detect_component_type "$rel_path")
  142. if [ "$comp_type" != "unknown" ]; then
  143. NEW_COMPONENTS+=("${comp_type}|${id}|${name}|${description}|${rel_path}")
  144. print_warning "New ${comp_type}: ${name} (${id})"
  145. echo " Path: ${rel_path}"
  146. [ -n "$description" ] && echo " Description: ${description}"
  147. echo ""
  148. fi
  149. fi
  150. done < <(find "$category_dir" -type f -name "*.md" 2>/dev/null)
  151. done
  152. }
  153. add_component_to_registry() {
  154. local comp_type=$1
  155. local id=$2
  156. local name=$3
  157. local description=$4
  158. local path=$5
  159. # Default description if empty
  160. if [ -z "$description" ]; then
  161. description="Component: ${name}"
  162. fi
  163. # Escape quotes and special characters in description
  164. description=$(echo "$description" | sed 's/"/\\"/g' | sed "s/'/\\'/g")
  165. # Get registry key (agents, subagents, commands, etc.)
  166. local registry_key
  167. registry_key=$(get_registry_key "$comp_type")
  168. # Use jq to properly construct JSON (avoids escaping issues)
  169. local temp_file="${REGISTRY_FILE}.tmp"
  170. jq --arg id "$id" \
  171. --arg name "$name" \
  172. --arg type "$comp_type" \
  173. --arg path "$path" \
  174. --arg desc "$description" \
  175. ".components.${registry_key} += [{
  176. \"id\": \$id,
  177. \"name\": \$name,
  178. \"type\": \$type,
  179. \"path\": \$path,
  180. \"description\": \$desc,
  181. \"tags\": [],
  182. \"dependencies\": [],
  183. \"category\": \"standard\"
  184. }]" "$REGISTRY_FILE" > "$temp_file"
  185. if [ $? -eq 0 ]; then
  186. mv "$temp_file" "$REGISTRY_FILE"
  187. print_success "Added ${comp_type}: ${name}"
  188. else
  189. print_error "Failed to add ${comp_type}: ${name}"
  190. rm -f "$temp_file"
  191. return 1
  192. fi
  193. }
  194. #############################################################################
  195. # Main
  196. #############################################################################
  197. main() {
  198. # Parse arguments
  199. while [ $# -gt 0 ]; do
  200. case "$1" in
  201. -a|--auto-add)
  202. AUTO_ADD=true
  203. shift
  204. ;;
  205. -d|--dry-run)
  206. DRY_RUN=true
  207. shift
  208. ;;
  209. -h|--help)
  210. usage
  211. ;;
  212. *)
  213. echo "Unknown option: $1"
  214. usage
  215. ;;
  216. esac
  217. done
  218. print_header
  219. # Check dependencies
  220. if ! command -v jq &> /dev/null; then
  221. print_error "jq is required but not installed"
  222. exit 1
  223. fi
  224. # Validate registry file
  225. if [ ! -f "$REGISTRY_FILE" ]; then
  226. print_error "Registry file not found: $REGISTRY_FILE"
  227. exit 1
  228. fi
  229. if ! jq empty "$REGISTRY_FILE" 2>/dev/null; then
  230. print_error "Registry file is not valid JSON"
  231. exit 1
  232. fi
  233. # Scan for new components
  234. scan_for_new_components
  235. # Summary
  236. echo ""
  237. echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
  238. echo -e "${BOLD}Summary${NC}"
  239. echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
  240. echo ""
  241. if [ ${#NEW_COMPONENTS[@]} -eq 0 ]; then
  242. print_success "No new components found. Registry is up to date!"
  243. exit 0
  244. fi
  245. echo -e "Found ${YELLOW}${#NEW_COMPONENTS[@]}${NC} new component(s)"
  246. echo ""
  247. # Add components if auto-add is enabled
  248. if [ "$AUTO_ADD" = true ] && [ "$DRY_RUN" = false ]; then
  249. print_info "Adding new components to registry..."
  250. echo ""
  251. local added=0
  252. for entry in "${NEW_COMPONENTS[@]}"; do
  253. IFS='|' read -r comp_type id name description path <<< "$entry"
  254. if add_component_to_registry "$comp_type" "$id" "$name" "$description" "$path"; then
  255. added=$((added + 1))
  256. fi
  257. done
  258. # Update timestamp
  259. jq '.metadata.lastUpdated = (now | strftime("%Y-%m-%d"))' "$REGISTRY_FILE" > "${REGISTRY_FILE}.tmp"
  260. mv "${REGISTRY_FILE}.tmp" "$REGISTRY_FILE"
  261. echo ""
  262. print_success "Added ${added} component(s) to registry"
  263. elif [ "$DRY_RUN" = true ]; then
  264. print_info "Dry run mode - no changes made to registry"
  265. echo ""
  266. echo "Run without --dry-run to add these components"
  267. else
  268. print_info "Run with --auto-add to add these components to registry"
  269. echo ""
  270. echo "Or manually add them to registry.json"
  271. fi
  272. exit 0
  273. }
  274. main "$@"