auto-detect-components.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. if ! echo "$registry_paths" | grep -q "^${rel_path}$"; then
  134. # Extract metadata
  135. local metadata
  136. metadata=$(extract_metadata_from_file "$file")
  137. IFS='|' read -r id name description <<< "$metadata"
  138. # Detect component type
  139. local comp_type
  140. comp_type=$(detect_component_type "$rel_path")
  141. if [ "$comp_type" != "unknown" ]; then
  142. NEW_COMPONENTS+=("${comp_type}|${id}|${name}|${description}|${rel_path}")
  143. print_warning "New ${comp_type}: ${name} (${id})"
  144. echo " Path: ${rel_path}"
  145. [ -n "$description" ] && echo " Description: ${description}"
  146. echo ""
  147. fi
  148. fi
  149. done < <(find "$category_dir" -type f -name "*.md" 2>/dev/null)
  150. done
  151. }
  152. add_component_to_registry() {
  153. local comp_type=$1
  154. local id=$2
  155. local name=$3
  156. local description=$4
  157. local path=$5
  158. # Default description if empty
  159. if [ -z "$description" ]; then
  160. description="Component: ${name}"
  161. fi
  162. # Escape quotes and special characters in description
  163. description=$(echo "$description" | sed 's/"/\\"/g' | sed "s/'/\\'/g")
  164. # Get registry key (agents, subagents, commands, etc.)
  165. local registry_key
  166. registry_key=$(get_registry_key "$comp_type")
  167. # Use jq to properly construct JSON (avoids escaping issues)
  168. local temp_file="${REGISTRY_FILE}.tmp"
  169. jq --arg id "$id" \
  170. --arg name "$name" \
  171. --arg type "$comp_type" \
  172. --arg path "$path" \
  173. --arg desc "$description" \
  174. ".components.${registry_key} += [{
  175. \"id\": \$id,
  176. \"name\": \$name,
  177. \"type\": \$type,
  178. \"path\": \$path,
  179. \"description\": \$desc,
  180. \"tags\": [],
  181. \"dependencies\": [],
  182. \"category\": \"standard\"
  183. }]" "$REGISTRY_FILE" > "$temp_file"
  184. if [ $? -eq 0 ]; then
  185. mv "$temp_file" "$REGISTRY_FILE"
  186. print_success "Added ${comp_type}: ${name}"
  187. else
  188. print_error "Failed to add ${comp_type}: ${name}"
  189. rm -f "$temp_file"
  190. return 1
  191. fi
  192. }
  193. #############################################################################
  194. # Main
  195. #############################################################################
  196. main() {
  197. # Parse arguments
  198. while [ $# -gt 0 ]; do
  199. case "$1" in
  200. -a|--auto-add)
  201. AUTO_ADD=true
  202. shift
  203. ;;
  204. -d|--dry-run)
  205. DRY_RUN=true
  206. shift
  207. ;;
  208. -h|--help)
  209. usage
  210. ;;
  211. *)
  212. echo "Unknown option: $1"
  213. usage
  214. ;;
  215. esac
  216. done
  217. print_header
  218. # Check dependencies
  219. if ! command -v jq &> /dev/null; then
  220. print_error "jq is required but not installed"
  221. exit 1
  222. fi
  223. # Validate registry file
  224. if [ ! -f "$REGISTRY_FILE" ]; then
  225. print_error "Registry file not found: $REGISTRY_FILE"
  226. exit 1
  227. fi
  228. if ! jq empty "$REGISTRY_FILE" 2>/dev/null; then
  229. print_error "Registry file is not valid JSON"
  230. exit 1
  231. fi
  232. # Scan for new components
  233. scan_for_new_components
  234. # Summary
  235. echo ""
  236. echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
  237. echo -e "${BOLD}Summary${NC}"
  238. echo -e "${BOLD}═══════════════════════════════════════════════════════════════${NC}"
  239. echo ""
  240. if [ ${#NEW_COMPONENTS[@]} -eq 0 ]; then
  241. print_success "No new components found. Registry is up to date!"
  242. exit 0
  243. fi
  244. echo -e "Found ${YELLOW}${#NEW_COMPONENTS[@]}${NC} new component(s)"
  245. echo ""
  246. # Add components if auto-add is enabled
  247. if [ "$AUTO_ADD" = true ] && [ "$DRY_RUN" = false ]; then
  248. print_info "Adding new components to registry..."
  249. echo ""
  250. local added=0
  251. for entry in "${NEW_COMPONENTS[@]}"; do
  252. IFS='|' read -r comp_type id name description path <<< "$entry"
  253. if add_component_to_registry "$comp_type" "$id" "$name" "$description" "$path"; then
  254. added=$((added + 1))
  255. fi
  256. done
  257. # Update timestamp
  258. jq '.metadata.lastUpdated = (now | strftime("%Y-%m-%d"))' "$REGISTRY_FILE" > "${REGISTRY_FILE}.tmp"
  259. mv "${REGISTRY_FILE}.tmp" "$REGISTRY_FILE"
  260. echo ""
  261. print_success "Added ${added} component(s) to registry"
  262. elif [ "$DRY_RUN" = true ]; then
  263. print_info "Dry run mode - no changes made to registry"
  264. echo ""
  265. echo "Run without --dry-run to add these components"
  266. else
  267. print_info "Run with --auto-add to add these components to registry"
  268. echo ""
  269. echo "Or manually add them to registry.json"
  270. fi
  271. exit 0
  272. }
  273. main "$@"