register-component.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. #!/usr/bin/env bash
  2. #############################################################################
  3. # Component Registration Script
  4. # Automatically scans .opencode/ and updates registry.json
  5. #############################################################################
  6. set -e
  7. # Colors
  8. RED='\033[0;31m'
  9. GREEN='\033[0;32m'
  10. YELLOW='\033[1;33m'
  11. BLUE='\033[0;34m'
  12. CYAN='\033[0;36m'
  13. BOLD='\033[1m'
  14. NC='\033[0m'
  15. # Configuration
  16. OPENCODE_DIR=".opencode"
  17. REGISTRY_FILE="registry.json"
  18. TEMP_REGISTRY="/tmp/registry-temp-$$.json"
  19. print_success() { echo -e "${GREEN}✓${NC} $1"; }
  20. print_error() { echo -e "${RED}✗${NC} $1"; }
  21. print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
  22. print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
  23. print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
  24. #############################################################################
  25. # Validation
  26. #############################################################################
  27. check_dependencies() {
  28. if ! command -v jq &> /dev/null; then
  29. print_error "jq is required but not installed"
  30. echo "Install with: brew install jq (macOS) or apt-get install jq (Linux)"
  31. exit 1
  32. fi
  33. }
  34. validate_registry() {
  35. if [ ! -f "$REGISTRY_FILE" ]; then
  36. print_error "Registry file not found: $REGISTRY_FILE"
  37. exit 1
  38. fi
  39. if ! jq empty "$REGISTRY_FILE" 2>/dev/null; then
  40. print_error "Invalid JSON in registry file"
  41. exit 1
  42. fi
  43. }
  44. #############################################################################
  45. # Component Discovery
  46. #############################################################################
  47. extract_frontmatter() {
  48. local file=$1
  49. local field=$2
  50. # Extract YAML frontmatter between --- markers
  51. awk -v field="$field" '
  52. BEGIN { in_fm=0; }
  53. /^---$/ {
  54. if (in_fm == 0) { in_fm=1; next; }
  55. else { exit; }
  56. }
  57. in_fm && $0 ~ "^" field ":" {
  58. sub("^" field ": *", "");
  59. gsub(/^["\047]|["\047]$/, "");
  60. print;
  61. exit;
  62. }
  63. ' "$file"
  64. }
  65. scan_agents() {
  66. print_step "Scanning agents..."
  67. local json_array="[]"
  68. while IFS= read -r -d '' file; do
  69. local id=$(basename "$file" .md)
  70. local name=$(extract_frontmatter "$file" "name")
  71. local desc=$(extract_frontmatter "$file" "description")
  72. # Use defaults if not found in frontmatter
  73. [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
  74. [ -z "$desc" ] && desc="Agent: $name"
  75. local path="${file#./}"
  76. # Build JSON using jq
  77. json_array=$(echo "$json_array" | jq \
  78. --arg id "$id" \
  79. --arg name "$name" \
  80. --arg path "$path" \
  81. --arg desc "$desc" \
  82. '. += [{
  83. "id": $id,
  84. "name": $name,
  85. "type": "agent",
  86. "path": $path,
  87. "description": $desc,
  88. "tags": [],
  89. "dependencies": [],
  90. "category": "core"
  91. }]')
  92. print_info "Found agent: $id"
  93. done < <(find "$OPENCODE_DIR/agent" -maxdepth 1 -name "*.md" -type f -print0 2>/dev/null)
  94. echo "$json_array"
  95. }
  96. scan_subagents() {
  97. print_step "Scanning subagents..."
  98. local subagents=()
  99. while IFS= read -r -d '' file; do
  100. local id=$(basename "$file" .md)
  101. local name=$(extract_frontmatter "$file" "name")
  102. local desc=$(extract_frontmatter "$file" "description")
  103. [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
  104. [ -z "$desc" ] && desc="Subagent: $name"
  105. local path="${file#./}"
  106. subagents+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"subagent\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
  107. print_info "Found subagent: $id"
  108. done < <(find "$OPENCODE_DIR/agent/subagents" -name "*.md" -type f -print0 2>/dev/null)
  109. if [ ${#subagents[@]} -gt 0 ]; then
  110. echo "[$(IFS=,; echo "${subagents[*]}")]"
  111. else
  112. echo "[]"
  113. fi
  114. }
  115. scan_commands() {
  116. print_step "Scanning commands..."
  117. local commands=()
  118. while IFS= read -r -d '' file; do
  119. local id=$(basename "$file" .md)
  120. local name=$(extract_frontmatter "$file" "name")
  121. local desc=$(extract_frontmatter "$file" "description")
  122. [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
  123. [ -z "$desc" ] && desc="Command: $name"
  124. local path="${file#./}"
  125. commands+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"command\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
  126. print_info "Found command: $id"
  127. done < <(find "$OPENCODE_DIR/command" -name "*.md" -type f -print0 2>/dev/null)
  128. if [ ${#commands[@]} -gt 0 ]; then
  129. echo "[$(IFS=,; echo "${commands[*]}")]"
  130. else
  131. echo "[]"
  132. fi
  133. }
  134. scan_tools() {
  135. print_step "Scanning tools..."
  136. local tools=()
  137. # Look for directories with index.ts
  138. while IFS= read -r -d '' dir; do
  139. local id=$(basename "$dir")
  140. # Skip node_modules and template
  141. [[ "$id" == "node_modules" || "$id" == "template" ]] && continue
  142. local index_file="$dir/index.ts"
  143. [ ! -f "$index_file" ] && continue
  144. local name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
  145. local desc="Tool: $name"
  146. # Try to extract description from comments
  147. local comment_desc=$(grep -m 1 "^\s*\*.*" "$index_file" | sed 's/^\s*\*\s*//' || echo "")
  148. [ -n "$comment_desc" ] && desc="$comment_desc"
  149. local path="${index_file#./}"
  150. tools+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"tool\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
  151. print_info "Found tool: $id"
  152. done < <(find "$OPENCODE_DIR/tool" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
  153. if [ ${#tools[@]} -gt 0 ]; then
  154. echo "[$(IFS=,; echo "${tools[*]}")]"
  155. else
  156. echo "[]"
  157. fi
  158. }
  159. scan_plugins() {
  160. print_step "Scanning plugins..."
  161. local plugins=()
  162. while IFS= read -r -d '' file; do
  163. local id=$(basename "$file" .ts)
  164. # Skip lib directory files
  165. [[ "$file" == *"/lib/"* ]] && continue
  166. local name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
  167. local desc="Plugin: $name"
  168. # Try to extract description from comments
  169. local comment_desc=$(grep -m 1 "^\s*\*.*" "$file" | sed 's/^\s*\*\s*//' || echo "")
  170. [ -n "$comment_desc" ] && desc="$comment_desc"
  171. local path="${file#./}"
  172. plugins+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"plugin\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"extended\"}")
  173. print_info "Found plugin: $id"
  174. done < <(find "$OPENCODE_DIR/plugin" -maxdepth 1 -name "*.ts" -type f -print0 2>/dev/null)
  175. if [ ${#plugins[@]} -gt 0 ]; then
  176. echo "[$(IFS=,; echo "${plugins[*]}")]"
  177. else
  178. echo "[]"
  179. fi
  180. }
  181. scan_contexts() {
  182. print_step "Scanning contexts..."
  183. local contexts=()
  184. while IFS= read -r -d '' file; do
  185. local id=$(basename "$file" .md)
  186. local name=$(extract_frontmatter "$file" "name")
  187. local desc=$(extract_frontmatter "$file" "description")
  188. [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
  189. [ -z "$desc" ] && desc="Context: $name"
  190. local path="${file#./}"
  191. contexts+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"context\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
  192. print_info "Found context: $id"
  193. done < <(find "$OPENCODE_DIR/context" -name "*.md" -type f -print0 2>/dev/null)
  194. if [ ${#contexts[@]} -gt 0 ]; then
  195. echo "[$(IFS=,; echo "${contexts[*]}")]"
  196. else
  197. echo "[]"
  198. fi
  199. }
  200. #############################################################################
  201. # Registry Update
  202. #############################################################################
  203. update_registry() {
  204. print_step "Updating registry..."
  205. # Scan all components
  206. local agents_json=$(scan_agents)
  207. local subagents_json=$(scan_subagents)
  208. local commands_json=$(scan_commands)
  209. local tools_json=$(scan_tools)
  210. local plugins_json=$(scan_plugins)
  211. local contexts_json=$(scan_contexts)
  212. # Read existing registry
  213. local existing_registry=$(cat "$REGISTRY_FILE")
  214. # Update components while preserving profiles and metadata
  215. local updated_registry=$(echo "$existing_registry" | jq \
  216. --argjson agents "$agents_json" \
  217. --argjson subagents "$subagents_json" \
  218. --argjson commands "$commands_json" \
  219. --argjson tools "$tools_json" \
  220. --argjson plugins "$plugins_json" \
  221. --argjson contexts "$contexts_json" \
  222. '
  223. .components.agents = $agents |
  224. .components.subagents = $subagents |
  225. .components.commands = $commands |
  226. .components.tools = $tools |
  227. .components.plugins = $plugins |
  228. .components.contexts = $contexts |
  229. .metadata.lastUpdated = (now | strftime("%Y-%m-%d"))
  230. ')
  231. # Write to temp file first
  232. echo "$updated_registry" | jq '.' > "$TEMP_REGISTRY"
  233. # Validate
  234. if jq empty "$TEMP_REGISTRY" 2>/dev/null; then
  235. mv "$TEMP_REGISTRY" "$REGISTRY_FILE"
  236. print_success "Registry updated successfully"
  237. else
  238. print_error "Generated invalid JSON, registry not updated"
  239. rm -f "$TEMP_REGISTRY"
  240. exit 1
  241. fi
  242. }
  243. #############################################################################
  244. # Statistics
  245. #############################################################################
  246. show_statistics() {
  247. print_step "Registry Statistics"
  248. local agents=$(jq '.components.agents | length' "$REGISTRY_FILE")
  249. local subagents=$(jq '.components.subagents | length' "$REGISTRY_FILE")
  250. local commands=$(jq '.components.commands | length' "$REGISTRY_FILE")
  251. local tools=$(jq '.components.tools | length' "$REGISTRY_FILE")
  252. local plugins=$(jq '.components.plugins | length' "$REGISTRY_FILE")
  253. local contexts=$(jq '.components.contexts | length' "$REGISTRY_FILE")
  254. local total=$((agents + subagents + commands + tools + plugins + contexts))
  255. echo " Agents: $agents"
  256. echo " Subagents: $subagents"
  257. echo " Commands: $commands"
  258. echo " Tools: $tools"
  259. echo " Plugins: $plugins"
  260. echo " Contexts: $contexts"
  261. echo " ─────────────"
  262. echo " Total: $total"
  263. echo ""
  264. }
  265. #############################################################################
  266. # Main
  267. #############################################################################
  268. main() {
  269. echo -e "${CYAN}${BOLD}"
  270. echo "╔════════════════════════════════════════════════════════════════╗"
  271. echo "║ ║"
  272. echo "║ Component Registration Script ║"
  273. echo "║ ║"
  274. echo "╚════════════════════════════════════════════════════════════════╝"
  275. echo -e "${NC}"
  276. check_dependencies
  277. validate_registry
  278. update_registry
  279. show_statistics
  280. print_success "Done!"
  281. }
  282. main "$@"