| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- #!/usr/bin/env bash
- #############################################################################
- # Component Registration Script
- # Automatically scans .opencode/ and updates registry.json
- #############################################################################
- set -e
- # Colors
- RED='\033[0;31m'
- GREEN='\033[0;32m'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- CYAN='\033[0;36m'
- BOLD='\033[1m'
- NC='\033[0m'
- # Configuration
- OPENCODE_DIR=".opencode"
- REGISTRY_FILE="registry.json"
- TEMP_REGISTRY="/tmp/registry-temp-$$.json"
- print_success() { echo -e "${GREEN}✓${NC} $1"; }
- print_error() { echo -e "${RED}✗${NC} $1"; }
- print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
- print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
- print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
- #############################################################################
- # Validation
- #############################################################################
- check_dependencies() {
- if ! command -v jq &> /dev/null; then
- print_error "jq is required but not installed"
- echo "Install with: brew install jq (macOS) or apt-get install jq (Linux)"
- exit 1
- fi
- }
- validate_registry() {
- if [ ! -f "$REGISTRY_FILE" ]; then
- print_error "Registry file not found: $REGISTRY_FILE"
- exit 1
- fi
-
- if ! jq empty "$REGISTRY_FILE" 2>/dev/null; then
- print_error "Invalid JSON in registry file"
- exit 1
- fi
- }
- #############################################################################
- # Component Discovery
- #############################################################################
- extract_frontmatter() {
- local file=$1
- local field=$2
-
- # Extract YAML frontmatter between --- markers
- awk -v field="$field" '
- BEGIN { in_fm=0; }
- /^---$/ {
- if (in_fm == 0) { in_fm=1; next; }
- else { exit; }
- }
- in_fm && $0 ~ "^" field ":" {
- sub("^" field ": *", "");
- gsub(/^["\047]|["\047]$/, "");
- print;
- exit;
- }
- ' "$file"
- }
- scan_agents() {
- print_step "Scanning agents..."
-
- local json_array="[]"
-
- while IFS= read -r -d '' file; do
- local id=$(basename "$file" .md)
- local name=$(extract_frontmatter "$file" "name")
- local desc=$(extract_frontmatter "$file" "description")
-
- # Use defaults if not found in frontmatter
- [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
- [ -z "$desc" ] && desc="Agent: $name"
-
- local path="${file#./}"
-
- # Build JSON using jq
- json_array=$(echo "$json_array" | jq \
- --arg id "$id" \
- --arg name "$name" \
- --arg path "$path" \
- --arg desc "$desc" \
- '. += [{
- "id": $id,
- "name": $name,
- "type": "agent",
- "path": $path,
- "description": $desc,
- "tags": [],
- "dependencies": [],
- "category": "core"
- }]')
-
- print_info "Found agent: $id"
- done < <(find "$OPENCODE_DIR/agent" -maxdepth 1 -name "*.md" -type f -print0 2>/dev/null)
-
- echo "$json_array"
- }
- scan_subagents() {
- print_step "Scanning subagents..."
-
- local subagents=()
-
- while IFS= read -r -d '' file; do
- local id=$(basename "$file" .md)
- local name=$(extract_frontmatter "$file" "name")
- local desc=$(extract_frontmatter "$file" "description")
-
- [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
- [ -z "$desc" ] && desc="Subagent: $name"
-
- local path="${file#./}"
-
- subagents+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"subagent\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
-
- print_info "Found subagent: $id"
- done < <(find "$OPENCODE_DIR/agent/subagents" -name "*.md" -type f -print0 2>/dev/null)
-
- if [ ${#subagents[@]} -gt 0 ]; then
- echo "[$(IFS=,; echo "${subagents[*]}")]"
- else
- echo "[]"
- fi
- }
- scan_commands() {
- print_step "Scanning commands..."
-
- local commands=()
-
- while IFS= read -r -d '' file; do
- local id=$(basename "$file" .md)
- local name=$(extract_frontmatter "$file" "name")
- local desc=$(extract_frontmatter "$file" "description")
-
- [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
- [ -z "$desc" ] && desc="Command: $name"
-
- local path="${file#./}"
-
- commands+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"command\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
-
- print_info "Found command: $id"
- done < <(find "$OPENCODE_DIR/command" -name "*.md" -type f -print0 2>/dev/null)
-
- if [ ${#commands[@]} -gt 0 ]; then
- echo "[$(IFS=,; echo "${commands[*]}")]"
- else
- echo "[]"
- fi
- }
- scan_tools() {
- print_step "Scanning tools..."
-
- local tools=()
-
- # Look for directories with index.ts
- while IFS= read -r -d '' dir; do
- local id=$(basename "$dir")
-
- # Skip node_modules and template
- [[ "$id" == "node_modules" || "$id" == "template" ]] && continue
-
- local index_file="$dir/index.ts"
- [ ! -f "$index_file" ] && continue
-
- local name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
- local desc="Tool: $name"
-
- # Try to extract description from comments
- local comment_desc=$(grep -m 1 "^\s*\*.*" "$index_file" | sed 's/^\s*\*\s*//' || echo "")
- [ -n "$comment_desc" ] && desc="$comment_desc"
-
- local path="${index_file#./}"
-
- tools+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"tool\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
-
- print_info "Found tool: $id"
- done < <(find "$OPENCODE_DIR/tool" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null)
-
- if [ ${#tools[@]} -gt 0 ]; then
- echo "[$(IFS=,; echo "${tools[*]}")]"
- else
- echo "[]"
- fi
- }
- scan_plugins() {
- print_step "Scanning plugins..."
-
- local plugins=()
-
- while IFS= read -r -d '' file; do
- local id=$(basename "$file" .ts)
-
- # Skip lib directory files
- [[ "$file" == *"/lib/"* ]] && continue
-
- local name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
- local desc="Plugin: $name"
-
- # Try to extract description from comments
- local comment_desc=$(grep -m 1 "^\s*\*.*" "$file" | sed 's/^\s*\*\s*//' || echo "")
- [ -n "$comment_desc" ] && desc="$comment_desc"
-
- local path="${file#./}"
-
- plugins+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"plugin\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"extended\"}")
-
- print_info "Found plugin: $id"
- done < <(find "$OPENCODE_DIR/plugin" -maxdepth 1 -name "*.ts" -type f -print0 2>/dev/null)
-
- if [ ${#plugins[@]} -gt 0 ]; then
- echo "[$(IFS=,; echo "${plugins[*]}")]"
- else
- echo "[]"
- fi
- }
- scan_contexts() {
- print_step "Scanning contexts..."
-
- local contexts=()
-
- while IFS= read -r -d '' file; do
- local id=$(basename "$file" .md)
- local name=$(extract_frontmatter "$file" "name")
- local desc=$(extract_frontmatter "$file" "description")
-
- [ -z "$name" ] && name=$(echo "$id" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++)sub(/./,toupper(substr($i,1,1)),$i)}1')
- [ -z "$desc" ] && desc="Context: $name"
-
- local path="${file#./}"
-
- contexts+=("{\"id\":\"$id\",\"name\":\"$name\",\"type\":\"context\",\"path\":\"$path\",\"description\":\"$desc\",\"tags\":[],\"dependencies\":[],\"category\":\"core\"}")
-
- print_info "Found context: $id"
- done < <(find "$OPENCODE_DIR/context" -name "*.md" -type f -print0 2>/dev/null)
-
- if [ ${#contexts[@]} -gt 0 ]; then
- echo "[$(IFS=,; echo "${contexts[*]}")]"
- else
- echo "[]"
- fi
- }
- #############################################################################
- # Registry Update
- #############################################################################
- update_registry() {
- print_step "Updating registry..."
-
- # Scan all components
- local agents_json=$(scan_agents)
- local subagents_json=$(scan_subagents)
- local commands_json=$(scan_commands)
- local tools_json=$(scan_tools)
- local plugins_json=$(scan_plugins)
- local contexts_json=$(scan_contexts)
-
- # Read existing registry
- local existing_registry=$(cat "$REGISTRY_FILE")
-
- # Update components while preserving profiles and metadata
- local updated_registry=$(echo "$existing_registry" | jq \
- --argjson agents "$agents_json" \
- --argjson subagents "$subagents_json" \
- --argjson commands "$commands_json" \
- --argjson tools "$tools_json" \
- --argjson plugins "$plugins_json" \
- --argjson contexts "$contexts_json" \
- '
- .components.agents = $agents |
- .components.subagents = $subagents |
- .components.commands = $commands |
- .components.tools = $tools |
- .components.plugins = $plugins |
- .components.contexts = $contexts |
- .metadata.lastUpdated = (now | strftime("%Y-%m-%d"))
- ')
-
- # Write to temp file first
- echo "$updated_registry" | jq '.' > "$TEMP_REGISTRY"
-
- # Validate
- if jq empty "$TEMP_REGISTRY" 2>/dev/null; then
- mv "$TEMP_REGISTRY" "$REGISTRY_FILE"
- print_success "Registry updated successfully"
- else
- print_error "Generated invalid JSON, registry not updated"
- rm -f "$TEMP_REGISTRY"
- exit 1
- fi
- }
- #############################################################################
- # Statistics
- #############################################################################
- show_statistics() {
- print_step "Registry Statistics"
-
- local agents=$(jq '.components.agents | length' "$REGISTRY_FILE")
- local subagents=$(jq '.components.subagents | length' "$REGISTRY_FILE")
- local commands=$(jq '.components.commands | length' "$REGISTRY_FILE")
- local tools=$(jq '.components.tools | length' "$REGISTRY_FILE")
- local plugins=$(jq '.components.plugins | length' "$REGISTRY_FILE")
- local contexts=$(jq '.components.contexts | length' "$REGISTRY_FILE")
- local total=$((agents + subagents + commands + tools + plugins + contexts))
-
- echo " Agents: $agents"
- echo " Subagents: $subagents"
- echo " Commands: $commands"
- echo " Tools: $tools"
- echo " Plugins: $plugins"
- echo " Contexts: $contexts"
- echo " ─────────────"
- echo " Total: $total"
- echo ""
- }
- #############################################################################
- # Main
- #############################################################################
- main() {
- echo -e "${CYAN}${BOLD}"
- echo "╔════════════════════════════════════════════════════════════════╗"
- echo "║ ║"
- echo "║ Component Registration Script ║"
- echo "║ ║"
- echo "╚════════════════════════════════════════════════════════════════╝"
- echo -e "${NC}"
-
- check_dependencies
- validate_registry
-
- update_registry
- show_statistics
-
- print_success "Done!"
- }
- main "$@"
|