Bläddra i källkod

feat: add interactive installer with component registry system

- Add component registry (registry.json) with 4 installation profiles
- Create interactive installer (install.sh) with profile and custom selection
- Add auto-registration script for CI/CD integration
- Create validation script for component structure
- Add update script for keeping components current
- Include GitHub Actions workflow for auto-registry updates
- Update README with installation instructions
- Add CONTRIBUTING guide for new components

Features:
- Interactive CLI with colored output and menus
- Smart dependency resolution
- Backup existing installations
- Profile-based installation (core, developer, full, advanced)
- Custom component selection
- Component validation and testing tools
darrenhinde 4 månader sedan
förälder
incheckning
777b3fc702
8 ändrade filer med 1617 tillägg och 1 borttagningar
  1. 75 0
      .github/workflows/update-registry.yml
  2. 237 0
      CONTRIBUTING.md
  3. 78 1
      README.md
  4. 609 0
      install.sh
  5. 0 0
      registry.json
  6. 364 0
      scripts/register-component.sh
  7. 171 0
      scripts/validate-component.sh
  8. 83 0
      update.sh

+ 75 - 0
.github/workflows/update-registry.yml

@@ -0,0 +1,75 @@
+name: Update Component Registry
+
+on:
+  push:
+    branches:
+      - main
+    paths:
+      - '.opencode/**'
+      - '!registry.json'
+  workflow_dispatch:
+
+permissions:
+  contents: write
+  pull-requests: write
+
+jobs:
+  update-registry:
+    runs-on: ubuntu-latest
+    
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+      
+      - name: Install dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install -y jq
+      
+      - name: Run component registration
+        run: |
+          chmod +x scripts/register-component.sh
+          ./scripts/register-component.sh
+      
+      - name: Check for changes
+        id: check_changes
+        run: |
+          if git diff --quiet registry.json; then
+            echo "changed=false" >> $GITHUB_OUTPUT
+            echo "No changes to registry.json"
+          else
+            echo "changed=true" >> $GITHUB_OUTPUT
+            echo "Registry has been updated"
+          fi
+      
+      - name: Commit and push changes
+        if: steps.check_changes.outputs.changed == 'true'
+        run: |
+          git config --local user.email "github-actions[bot]@users.noreply.github.com"
+          git config --local user.name "github-actions[bot]"
+          git add registry.json
+          git commit -m "chore: auto-update component registry [skip ci]"
+          git push
+      
+      - name: Summary
+        if: steps.check_changes.outputs.changed == 'true'
+        run: |
+          echo "✅ Registry updated successfully" >> $GITHUB_STEP_SUMMARY
+          echo "" >> $GITHUB_STEP_SUMMARY
+          echo "### Updated Components" >> $GITHUB_STEP_SUMMARY
+          echo "" >> $GITHUB_STEP_SUMMARY
+          jq -r '
+            "- **Agents:** \(.components.agents | length)",
+            "- **Subagents:** \(.components.subagents | length)",
+            "- **Commands:** \(.components.commands | length)",
+            "- **Tools:** \(.components.tools | length)",
+            "- **Plugins:** \(.components.plugins | length)",
+            "- **Contexts:** \(.components.contexts | length)"
+          ' registry.json >> $GITHUB_STEP_SUMMARY
+      
+      - name: No changes summary
+        if: steps.check_changes.outputs.changed == 'false'
+        run: |
+          echo "ℹ️ No changes to registry" >> $GITHUB_STEP_SUMMARY

+ 237 - 0
CONTRIBUTING.md

@@ -0,0 +1,237 @@
+# Contributing to OpenCode Agents
+
+Thank you for your interest in contributing! This guide will help you add new components to the registry.
+
+## Quick Start
+
+1. Fork the repository
+2. Create a new branch for your feature
+3. Add your component
+4. Test it works
+5. Submit a pull request
+
+## Adding New Components
+
+### Component Types
+
+- **Agents** (`.opencode/agent/*.md`) - Main AI agents
+- **Subagents** (`.opencode/agent/subagents/*.md`) - Specialized helpers
+- **Commands** (`.opencode/command/*.md`) - Slash commands
+- **Tools** (`.opencode/tool/*/index.ts`) - Utility tools
+- **Plugins** (`.opencode/plugin/*.ts`) - Integrations
+- **Contexts** (`.opencode/context/**/*.md`) - Context files
+
+### Component Structure
+
+#### For Markdown Files (Agents, Commands, Contexts)
+
+All markdown files should include YAML frontmatter:
+
+```markdown
+---
+description: "Brief description of what this does"
+mode: primary  # For agents only
+model: claude-4-sonnet  # For agents only
+temperature: 0.1  # For agents only
+tools:  # For agents only
+  read: true
+  edit: true
+  write: true
+permissions:  # Optional
+  bash:
+    "*": "deny"
+---
+
+# Component Name
+
+Your component content here...
+```
+
+**Required fields:**
+- `description` - Brief description (all components)
+
+**Agent-specific fields:**
+- `mode` - Agent mode (primary, secondary, etc.)
+- `model` - AI model to use
+- `temperature` - Temperature setting
+- `tools` - Available tools
+- `permissions` - Security permissions
+
+#### For TypeScript Files (Tools, Plugins)
+
+Include JSDoc comments at the top:
+
+```typescript
+/**
+ * Tool Name
+ * 
+ * Brief description of what this tool does
+ */
+
+export function myTool() {
+  // Implementation
+}
+```
+
+### File Naming Conventions
+
+- **kebab-case** for file names: `my-new-agent.md`
+- **PascalCase** for TypeScript types/interfaces
+- **camelCase** for variables and functions
+
+### Adding Your Component
+
+1. **Create the component file** in the appropriate directory:
+   ```bash
+   # Example: Adding a new agent
+   touch .opencode/agent/my-new-agent.md
+   ```
+
+2. **Add frontmatter and content** following the structure above
+
+3. **Test your component**:
+   ```bash
+   # Validate structure
+   ./scripts/validate-component.sh
+   ```
+
+4. **Update the registry** (automatic on merge to main):
+   ```bash
+   # Manual update (optional)
+   ./scripts/register-component.sh
+   ```
+
+## Component Categories
+
+When adding components, they're automatically categorized:
+
+- **core** - Essential components included in minimal installs
+- **extended** - Additional features for developer profile
+- **advanced** - Experimental or specialized components
+
+The auto-registration script assigns categories based on component type and location.
+
+## Testing Your Component
+
+### Local Testing
+
+1. **Install locally**:
+   ```bash
+   # Test the installer
+   ./install.sh --list
+   ```
+
+2. **Validate structure**:
+   ```bash
+   ./scripts/validate-component.sh
+   ```
+
+3. **Test with OpenCode**:
+   ```bash
+   opencode --agent your-new-agent
+   ```
+
+### Automated Testing
+
+When you submit a PR, GitHub Actions will:
+- Validate component structure
+- Update the registry
+- Run validation checks
+
+## Pull Request Guidelines
+
+### PR Title Format
+
+Use conventional commits:
+- `feat: add new agent for X`
+- `fix: correct issue in Y command`
+- `docs: update Z documentation`
+- `chore: update dependencies`
+
+### PR Description
+
+Include:
+1. **What** - What component are you adding/changing?
+2. **Why** - Why is this useful?
+3. **How** - How does it work?
+4. **Testing** - How did you test it?
+
+Example:
+```markdown
+## What
+Adds a new `database-agent` for managing database migrations.
+
+## Why
+Automates common database tasks and ensures migration safety.
+
+## How
+- Scans migration files
+- Validates migration order
+- Runs migrations with rollback support
+
+## Testing
+- [x] Validated with `./scripts/validate-component.sh`
+- [x] Tested with PostgreSQL and MySQL
+- [x] Tested rollback scenarios
+```
+
+## Component Dependencies
+
+If your component depends on others, declare them in the registry:
+
+```json
+{
+  "id": "my-component",
+  "dependencies": ["tool:env", "agent:task-manager"]
+}
+```
+
+The installer will automatically install dependencies.
+
+## Registry Auto-Update
+
+The registry is automatically updated when:
+- You push to `main` branch
+- Changes are made to `.opencode/` directory
+
+The GitHub Action:
+1. Scans all components
+2. Extracts metadata
+3. Updates `registry.json`
+4. Commits changes
+
+You don't need to manually edit `registry.json`!
+
+## Code Style
+
+### Markdown
+- Use clear, concise language
+- Include examples
+- Add code blocks with syntax highlighting
+- Use proper heading hierarchy
+
+### TypeScript
+- Follow existing code style
+- Add JSDoc comments
+- Use TypeScript types (no `any`)
+- Export functions explicitly
+
+### Bash Scripts
+- Use `set -e` for error handling
+- Add comments for complex logic
+- Use meaningful variable names
+- Include help text
+
+## Questions?
+
+- **Issues**: Open an issue for bugs or feature requests
+- **Discussions**: Use GitHub Discussions for questions
+- **Security**: Email security issues privately
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the MIT License.
+
+---
+
+Thank you for contributing! 🎉

+ 78 - 1
README.md

@@ -23,6 +23,31 @@ https://opencode.ai/docs
 ```
 
 ### Step 2: Install Agents & Commands
+
+**Option A: Interactive Installer (Recommended)**
+```bash
+curl -fsSL https://raw.githubusercontent.com/darrenhinde/opencode-agents/main/install.sh | bash
+```
+
+The installer offers:
+- 🎯 **Quick Profiles**: Core, Developer, Full, or Advanced
+- 🎨 **Custom Selection**: Pick exactly what you need
+- 📦 **Smart Dependencies**: Auto-installs required components
+- ✨ **Interactive Menus**: User-friendly component browser
+
+**Option B: Profile-Based Install**
+```bash
+# Core essentials only
+curl -fsSL https://raw.githubusercontent.com/darrenhinde/opencode-agents/main/install.sh | bash -s core
+
+# Balanced for daily development
+curl -fsSL https://raw.githubusercontent.com/darrenhinde/opencode-agents/main/install.sh | bash -s developer
+
+# Everything included
+curl -fsSL https://raw.githubusercontent.com/darrenhinde/opencode-agents/main/install.sh | bash -s full
+```
+
+**Option C: Manual Install**
 ```bash
 # Clone this repository
 git clone https://github.com/darrenhinde/opencode-agents.git
@@ -220,7 +245,11 @@ A: The agents work with any language (TypeScript, Python, Go, Rust, etc.) and ad
 A: It's a teaching document explaining architecture patterns and how to extend the system.
 
 **Q: Can I use just one command or agent?**  
-A: Yes! Cherry-pick individual files with curl:
+A: Yes! Use the installer's list feature to see all components:
+```bash
+./install.sh --list
+```
+Or cherry-pick individual files with curl:
 ```bash
 curl -o ~/.opencode/agent/codebase-agent.md \
   https://raw.githubusercontent.com/darrenhinde/opencode-agents/main/.opencode/agent/codebase-agent.md
@@ -228,6 +257,54 @@ curl -o ~/.opencode/agent/codebase-agent.md \
 
 ---
 
+## Installation Profiles
+
+The installer offers four pre-configured profiles:
+
+### 🎯 Core (Minimal)
+Essential agents and commands for basic OpenCode usage.
+- **Agents**: task-manager, codebase-agent
+- **Subagents**: reviewer, tester, documentation, coder-agent, build-agent
+- **Commands**: test, commit, context
+- **Tools**: env utilities
+- **Best for**: Getting started, minimal setup
+
+### 💼 Developer (Recommended)
+Balanced setup for daily development work.
+- Everything in Core, plus:
+- **Agents**: image-specialist, workflow-orchestrator
+- **Subagents**: codebase-pattern-analyst
+- **Commands**: clean, optimize, prompt-enhancer
+- **Tools**: Gemini AI integration
+- **Plugins**: Telegram notifications
+- **Best for**: Most developers, daily use
+
+### 📦 Full
+Complete installation with all available components.
+- Everything in Developer, plus:
+- **Commands**: worktrees (git worktree management)
+- **Best for**: Power users, exploring all features
+
+### 🚀 Advanced
+Full installation plus experimental features and planning docs.
+- Everything in Full, plus:
+- **Additional**: .Building/ directory, GitHub workflows
+- **Best for**: Contributors, learning the architecture
+
+## Updating Components
+
+Keep your components up to date:
+
+```bash
+# Update all installed components
+./update.sh
+
+# Or re-run the installer
+curl -fsSL https://raw.githubusercontent.com/darrenhinde/opencode-agents/main/install.sh | bash
+```
+
+---
+
 ## Advanced
 
 ### Understanding the System

+ 609 - 0
install.sh

@@ -0,0 +1,609 @@
+#!/usr/bin/env bash
+
+#############################################################################
+# OpenCode Agents Installer
+# Interactive installer for OpenCode agents, commands, tools, and plugins
+#############################################################################
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+MAGENTA='\033[0;35m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m' # No Color
+
+# Configuration
+REPO_URL="https://github.com/darrenhinde/opencode-agents"
+RAW_URL="https://raw.githubusercontent.com/darrenhinde/opencode-agents/main"
+REGISTRY_URL="${RAW_URL}/registry.json"
+INSTALL_DIR=".opencode"
+TEMP_DIR="/tmp/opencode-installer-$$"
+
+# Global variables
+SELECTED_COMPONENTS=()
+INSTALL_MODE=""
+PROFILE=""
+
+#############################################################################
+# Utility Functions
+#############################################################################
+
+print_header() {
+    echo -e "${CYAN}${BOLD}"
+    echo "╔════════════════════════════════════════════════════════════════╗"
+    echo "║                                                                ║"
+    echo "║           OpenCode Agents Installer v1.0.0                    ║"
+    echo "║                                                                ║"
+    echo "╚════════════════════════════════════════════════════════════════╝"
+    echo -e "${NC}"
+}
+
+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${MAGENTA}${BOLD}▶${NC} $1\n"
+}
+
+#############################################################################
+# Dependency Checks
+#############################################################################
+
+check_dependencies() {
+    print_step "Checking dependencies..."
+    
+    local missing_deps=()
+    
+    if ! command -v curl &> /dev/null; then
+        missing_deps+=("curl")
+    fi
+    
+    if ! command -v jq &> /dev/null; then
+        missing_deps+=("jq")
+    fi
+    
+    if [ ${#missing_deps[@]} -ne 0 ]; then
+        print_error "Missing required dependencies: ${missing_deps[*]}"
+        echo ""
+        echo "Please install them:"
+        echo "  macOS:   brew install ${missing_deps[*]}"
+        echo "  Ubuntu:  sudo apt-get install ${missing_deps[*]}"
+        echo "  Fedora:  sudo dnf install ${missing_deps[*]}"
+        exit 1
+    fi
+    
+    print_success "All dependencies found"
+}
+
+#############################################################################
+# Registry Functions
+#############################################################################
+
+fetch_registry() {
+    print_step "Fetching component registry..."
+    
+    mkdir -p "$TEMP_DIR"
+    
+    if ! curl -fsSL "$REGISTRY_URL" -o "$TEMP_DIR/registry.json"; then
+        print_error "Failed to fetch registry from $REGISTRY_URL"
+        exit 1
+    fi
+    
+    print_success "Registry fetched successfully"
+}
+
+get_profile_components() {
+    local profile=$1
+    jq -r ".profiles.${profile}.components[]" "$TEMP_DIR/registry.json"
+}
+
+get_component_info() {
+    local component_id=$1
+    local component_type=$2
+    
+    jq -r ".components.${component_type}[] | select(.id == \"${component_id}\")" "$TEMP_DIR/registry.json"
+}
+
+resolve_dependencies() {
+    local component=$1
+    local type="${component%%:*}"
+    local id="${component##*:}"
+    
+    # Get dependencies for this component
+    local deps=$(jq -r ".components.${type}s[] | select(.id == \"${id}\") | .dependencies[]?" "$TEMP_DIR/registry.json" 2>/dev/null || echo "")
+    
+    if [ -n "$deps" ]; then
+        for dep in $deps; do
+            # Add dependency if not already in list
+            if [[ ! " ${SELECTED_COMPONENTS[@]} " =~ " ${dep} " ]]; then
+                SELECTED_COMPONENTS+=("$dep")
+                # Recursively resolve dependencies
+                resolve_dependencies "$dep"
+            fi
+        done
+    fi
+}
+
+#############################################################################
+# Installation Mode Selection
+#############################################################################
+
+show_main_menu() {
+    clear
+    print_header
+    
+    echo -e "${BOLD}Choose installation mode:${NC}\n"
+    echo "  1) Quick Install (Choose a profile)"
+    echo "  2) Custom Install (Pick individual components)"
+    echo "  3) List Available Components"
+    echo "  4) Exit"
+    echo ""
+    read -p "Enter your choice [1-4]: " choice
+    
+    case $choice in
+        1) INSTALL_MODE="profile" ;;
+        2) INSTALL_MODE="custom" ;;
+        3) list_components; show_main_menu ;;
+        4) cleanup_and_exit 0 ;;
+        *) print_error "Invalid choice"; sleep 2; show_main_menu ;;
+    esac
+}
+
+#############################################################################
+# Profile Installation
+#############################################################################
+
+show_profile_menu() {
+    clear
+    print_header
+    
+    echo -e "${BOLD}Available Installation Profiles:${NC}\n"
+    
+    # Core profile
+    local core_desc=$(jq -r '.profiles.core.description' "$TEMP_DIR/registry.json")
+    local core_count=$(jq -r '.profiles.core.components | length' "$TEMP_DIR/registry.json")
+    echo -e "  ${GREEN}1) Core${NC}"
+    echo -e "     ${core_desc}"
+    echo -e "     Components: ${core_count}\n"
+    
+    # Developer profile
+    local dev_desc=$(jq -r '.profiles.developer.description' "$TEMP_DIR/registry.json")
+    local dev_count=$(jq -r '.profiles.developer.components | length' "$TEMP_DIR/registry.json")
+    echo -e "  ${BLUE}2) Developer${NC}"
+    echo -e "     ${dev_desc}"
+    echo -e "     Components: ${dev_count}\n"
+    
+    # Full profile
+    local full_desc=$(jq -r '.profiles.full.description' "$TEMP_DIR/registry.json")
+    local full_count=$(jq -r '.profiles.full.components | length' "$TEMP_DIR/registry.json")
+    echo -e "  ${MAGENTA}3) Full${NC}"
+    echo -e "     ${full_desc}"
+    echo -e "     Components: ${full_count}\n"
+    
+    # Advanced profile
+    local adv_desc=$(jq -r '.profiles.advanced.description' "$TEMP_DIR/registry.json")
+    local adv_count=$(jq -r '.profiles.advanced.components | length' "$TEMP_DIR/registry.json")
+    echo -e "  ${YELLOW}4) Advanced${NC}"
+    echo -e "     ${adv_desc}"
+    echo -e "     Components: ${adv_count}\n"
+    
+    echo "  5) Back to main menu"
+    echo ""
+    read -p "Enter your choice [1-5]: " choice
+    
+    case $choice in
+        1) PROFILE="core" ;;
+        2) PROFILE="developer" ;;
+        3) PROFILE="full" ;;
+        4) PROFILE="advanced" ;;
+        5) show_main_menu; return ;;
+        *) print_error "Invalid choice"; sleep 2; show_profile_menu; return ;;
+    esac
+    
+    # Load profile components
+    mapfile -t SELECTED_COMPONENTS < <(get_profile_components "$PROFILE")
+    
+    show_installation_preview
+}
+
+#############################################################################
+# Custom Component Selection
+#############################################################################
+
+show_custom_menu() {
+    clear
+    print_header
+    
+    echo -e "${BOLD}Select component categories to install:${NC}\n"
+    echo "Use space to toggle, Enter to continue"
+    echo ""
+    
+    local categories=("agents" "subagents" "commands" "tools" "plugins" "contexts" "config")
+    local selected_categories=()
+    
+    # Simple selection (for now, we'll make it interactive later)
+    echo "Available categories:"
+    for i in "${!categories[@]}"; do
+        local cat="${categories[$i]}"
+        local count=$(jq -r ".components.${cat} | length" "$TEMP_DIR/registry.json")
+        echo "  $((i+1))) ${cat^} (${count} available)"
+    done
+    echo "  $((${#categories[@]}+1))) Select All"
+    echo "  $((${#categories[@]}+2))) Continue to component selection"
+    echo "  $((${#categories[@]}+3))) Back to main menu"
+    echo ""
+    
+    read -p "Enter category numbers (space-separated) or option: " -a selections
+    
+    for sel in "${selections[@]}"; do
+        if [ "$sel" -eq $((${#categories[@]}+1)) ]; then
+            selected_categories=("${categories[@]}")
+            break
+        elif [ "$sel" -eq $((${#categories[@]}+2)) ]; then
+            break
+        elif [ "$sel" -eq $((${#categories[@]}+3)) ]; then
+            show_main_menu
+            return
+        elif [ "$sel" -ge 1 ] && [ "$sel" -le ${#categories[@]} ]; then
+            selected_categories+=("${categories[$((sel-1))]}")
+        fi
+    done
+    
+    if [ ${#selected_categories[@]} -eq 0 ]; then
+        print_warning "No categories selected"
+        sleep 2
+        show_custom_menu
+        return
+    fi
+    
+    show_component_selection "${selected_categories[@]}"
+}
+
+show_component_selection() {
+    local categories=("$@")
+    clear
+    print_header
+    
+    echo -e "${BOLD}Select components to install:${NC}\n"
+    
+    local all_components=()
+    local component_details=()
+    
+    for category in "${categories[@]}"; do
+        echo -e "${CYAN}${BOLD}${category^}:${NC}"
+        
+        local components=$(jq -r ".components.${category}[] | .id" "$TEMP_DIR/registry.json")
+        
+        local idx=1
+        while IFS= read -r comp_id; do
+            local comp_name=$(jq -r ".components.${category}[] | select(.id == \"${comp_id}\") | .name" "$TEMP_DIR/registry.json")
+            local comp_desc=$(jq -r ".components.${category}[] | select(.id == \"${comp_id}\") | .description" "$TEMP_DIR/registry.json")
+            
+            echo "  ${idx}) ${comp_name}"
+            echo "     ${comp_desc}"
+            
+            all_components+=("${category}:${comp_id}")
+            component_details+=("${comp_name}|${comp_desc}")
+            
+            idx=$((idx+1))
+        done <<< "$components"
+        
+        echo ""
+    done
+    
+    echo "Enter component numbers (space-separated), 'all' for all, or 'done' to continue:"
+    read -a selections
+    
+    for sel in "${selections[@]}"; do
+        if [ "$sel" = "all" ]; then
+            SELECTED_COMPONENTS=("${all_components[@]}")
+            break
+        elif [ "$sel" = "done" ]; then
+            break
+        elif [ "$sel" -ge 1 ] && [ "$sel" -le ${#all_components[@]} ]; then
+            SELECTED_COMPONENTS+=("${all_components[$((sel-1))]}")
+        fi
+    done
+    
+    if [ ${#SELECTED_COMPONENTS[@]} -eq 0 ]; then
+        print_warning "No components selected"
+        sleep 2
+        show_custom_menu
+        return
+    fi
+    
+    # Resolve dependencies
+    print_step "Resolving dependencies..."
+    local original_count=${#SELECTED_COMPONENTS[@]}
+    for comp in "${SELECTED_COMPONENTS[@]}"; do
+        resolve_dependencies "$comp"
+    done
+    
+    if [ ${#SELECTED_COMPONENTS[@]} -gt $original_count ]; then
+        print_info "Added $((${#SELECTED_COMPONENTS[@]} - original_count)) dependencies"
+    fi
+    
+    show_installation_preview
+}
+
+#############################################################################
+# Installation Preview & Confirmation
+#############################################################################
+
+show_installation_preview() {
+    clear
+    print_header
+    
+    echo -e "${BOLD}Installation Preview${NC}\n"
+    
+    if [ -n "$PROFILE" ]; then
+        echo -e "Profile: ${GREEN}${PROFILE}${NC}"
+    else
+        echo -e "Mode: ${GREEN}Custom${NC}"
+    fi
+    
+    echo -e "\nComponents to install (${#SELECTED_COMPONENTS[@]} total):\n"
+    
+    # Group by type
+    local agents=()
+    local subagents=()
+    local commands=()
+    local tools=()
+    local plugins=()
+    local contexts=()
+    local configs=()
+    
+    for comp in "${SELECTED_COMPONENTS[@]}"; do
+        local type="${comp%%:*}"
+        case $type in
+            agent) agents+=("$comp") ;;
+            subagent) subagents+=("$comp") ;;
+            command) commands+=("$comp") ;;
+            tool) tools+=("$comp") ;;
+            plugin) plugins+=("$comp") ;;
+            context) contexts+=("$comp") ;;
+            config) configs+=("$comp") ;;
+        esac
+    done
+    
+    [ ${#agents[@]} -gt 0 ] && echo -e "${CYAN}Agents (${#agents[@]}):${NC} ${agents[*]##*:}"
+    [ ${#subagents[@]} -gt 0 ] && echo -e "${CYAN}Subagents (${#subagents[@]}):${NC} ${subagents[*]##*:}"
+    [ ${#commands[@]} -gt 0 ] && echo -e "${CYAN}Commands (${#commands[@]}):${NC} ${commands[*]##*:}"
+    [ ${#tools[@]} -gt 0 ] && echo -e "${CYAN}Tools (${#tools[@]}):${NC} ${tools[*]##*:}"
+    [ ${#plugins[@]} -gt 0 ] && echo -e "${CYAN}Plugins (${#plugins[@]}):${NC} ${plugins[*]##*:}"
+    [ ${#contexts[@]} -gt 0 ] && echo -e "${CYAN}Contexts (${#contexts[@]}):${NC} ${contexts[*]##*:}"
+    [ ${#configs[@]} -gt 0 ] && echo -e "${CYAN}Config (${#configs[@]}):${NC} ${configs[*]##*:}"
+    
+    echo ""
+    read -p "Proceed with installation? [Y/n]: " confirm
+    
+    if [[ $confirm =~ ^[Nn] ]]; then
+        print_info "Installation cancelled"
+        cleanup_and_exit 0
+    fi
+    
+    perform_installation
+}
+
+#############################################################################
+# Installation
+#############################################################################
+
+perform_installation() {
+    print_step "Installing components..."
+    
+    # Check if .opencode already exists
+    if [ -d "$INSTALL_DIR" ]; then
+        print_warning "$INSTALL_DIR directory already exists"
+        read -p "Backup and continue? [Y/n]: " backup_confirm
+        
+        if [[ ! $backup_confirm =~ ^[Nn] ]]; then
+            local backup_dir="${INSTALL_DIR}.backup.$(date +%Y%m%d-%H%M%S)"
+            mv "$INSTALL_DIR" "$backup_dir"
+            print_success "Backed up to $backup_dir"
+        else
+            print_error "Installation cancelled"
+            cleanup_and_exit 1
+        fi
+    fi
+    
+    # Create directory structure
+    mkdir -p "$INSTALL_DIR"/{agent/subagents,command,tool,plugin,context/{core,project}}
+    
+    local installed=0
+    local failed=0
+    
+    for comp in "${SELECTED_COMPONENTS[@]}"; do
+        local type="${comp%%:*}"
+        local id="${comp##*:}"
+        
+        # Get component path
+        local path=$(jq -r ".components.${type}s[] | select(.id == \"${id}\") | .path" "$TEMP_DIR/registry.json")
+        
+        if [ -z "$path" ] || [ "$path" = "null" ]; then
+            print_warning "Could not find path for ${comp}"
+            ((failed++))
+            continue
+        fi
+        
+        # Download component
+        local url="${RAW_URL}/${path}"
+        local dest="${path}"
+        
+        # Create parent directory if needed
+        mkdir -p "$(dirname "$dest")"
+        
+        if curl -fsSL "$url" -o "$dest"; then
+            print_success "Installed ${type}: ${id}"
+            ((installed++))
+        else
+            print_error "Failed to install ${type}: ${id}"
+            ((failed++))
+        fi
+    done
+    
+    # Handle additional paths for advanced profile
+    if [ "$PROFILE" = "advanced" ]; then
+        local additional_paths=$(jq -r '.profiles.advanced.additionalPaths[]?' "$TEMP_DIR/registry.json")
+        if [ -n "$additional_paths" ]; then
+            print_step "Installing additional paths..."
+            while IFS= read -r path; do
+                # For directories, we'd need to recursively download
+                # For now, just note them
+                print_info "Additional path: $path (manual download required)"
+            done <<< "$additional_paths"
+        fi
+    fi
+    
+    echo ""
+    print_success "Installation complete!"
+    echo -e "  Installed: ${GREEN}${installed}${NC}"
+    [ $failed -gt 0 ] && echo -e "  Failed: ${RED}${failed}${NC}"
+    
+    show_post_install
+}
+
+#############################################################################
+# Post-Installation
+#############################################################################
+
+show_post_install() {
+    echo ""
+    print_step "Next Steps"
+    
+    echo "1. Review the installed components in .opencode/"
+    echo "2. Copy env.example to .env and configure:"
+    echo "   ${CYAN}cp env.example .env${NC}"
+    echo "3. Start using OpenCode agents:"
+    echo "   ${CYAN}opencode${NC}"
+    echo ""
+    
+    print_info "Documentation: ${REPO_URL}"
+    echo ""
+    
+    cleanup_and_exit 0
+}
+
+#############################################################################
+# Component Listing
+#############################################################################
+
+list_components() {
+    clear
+    print_header
+    
+    echo -e "${BOLD}Available Components${NC}\n"
+    
+    local categories=("agents" "subagents" "commands" "tools" "plugins" "contexts")
+    
+    for category in "${categories[@]}"; do
+        echo -e "${CYAN}${BOLD}${category^}:${NC}"
+        
+        local components=$(jq -r ".components.${category}[] | \"\(.id)|\(.name)|\(.description)\"" "$TEMP_DIR/registry.json")
+        
+        while IFS='|' read -r id name desc; do
+            echo -e "  ${GREEN}${name}${NC} (${id})"
+            echo -e "    ${desc}"
+        done <<< "$components"
+        
+        echo ""
+    done
+    
+    read -p "Press Enter to continue..."
+}
+
+#############################################################################
+# Cleanup
+#############################################################################
+
+cleanup_and_exit() {
+    rm -rf "$TEMP_DIR"
+    exit "$1"
+}
+
+trap 'cleanup_and_exit 1' INT TERM
+
+#############################################################################
+# Main
+#############################################################################
+
+main() {
+    # Parse command line arguments
+    case "${1:-}" in
+        --core)
+            INSTALL_MODE="profile"
+            PROFILE="core"
+            ;;
+        --developer)
+            INSTALL_MODE="profile"
+            PROFILE="developer"
+            ;;
+        --full)
+            INSTALL_MODE="profile"
+            PROFILE="full"
+            ;;
+        --advanced)
+            INSTALL_MODE="profile"
+            PROFILE="advanced"
+            ;;
+        --list)
+            check_dependencies
+            fetch_registry
+            list_components
+            cleanup_and_exit 0
+            ;;
+        --help|-h)
+            print_header
+            echo "Usage: $0 [OPTIONS]"
+            echo ""
+            echo "Options:"
+            echo "  --core        Install core profile"
+            echo "  --developer   Install developer profile"
+            echo "  --full        Install full profile"
+            echo "  --advanced    Install advanced profile"
+            echo "  --list        List all available components"
+            echo "  --help        Show this help message"
+            echo ""
+            echo "Without options, runs in interactive mode"
+            exit 0
+            ;;
+    esac
+    
+    check_dependencies
+    fetch_registry
+    
+    if [ -n "$PROFILE" ]; then
+        # Non-interactive mode
+        mapfile -t SELECTED_COMPONENTS < <(get_profile_components "$PROFILE")
+        show_installation_preview
+    else
+        # Interactive mode
+        show_main_menu
+        
+        if [ "$INSTALL_MODE" = "profile" ]; then
+            show_profile_menu
+        elif [ "$INSTALL_MODE" = "custom" ]; then
+            show_custom_menu
+        fi
+    fi
+}
+
+main "$@"

+ 0 - 0
registry.json


+ 364 - 0
scripts/register-component.sh

@@ -0,0 +1,364 @@
+#!/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 "$@"

+ 171 - 0
scripts/validate-component.sh

@@ -0,0 +1,171 @@
+#!/usr/bin/env bash
+
+#############################################################################
+# Component Validation Script
+# Validates component structure and metadata for PRs
+#############################################################################
+
+set -e
+
+# Colors
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+ERRORS=0
+WARNINGS=0
+
+print_success() { echo -e "${GREEN}✓${NC} $1"; }
+print_error() { echo -e "${RED}✗${NC} $1"; ((ERRORS++)); }
+print_warning() { echo -e "${YELLOW}⚠${NC} $1"; ((WARNINGS++)); }
+print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
+
+validate_markdown_frontmatter() {
+    local file=$1
+    
+    print_info "Validating $file"
+    
+    # Check if file has frontmatter
+    if ! head -n 1 "$file" | grep -q "^---$"; then
+        print_warning "Missing frontmatter in $file"
+        return
+    fi
+    
+    # Extract frontmatter
+    local frontmatter=$(awk '/^---$/{if(++n==2)exit;next}n==1' "$file")
+    
+    # Check for description
+    if ! echo "$frontmatter" | grep -q "^description:"; then
+        print_warning "Missing 'description' in frontmatter of $file"
+    else
+        print_success "Has description"
+    fi
+    
+    # For agents, check for mode
+    if [[ "$file" == *"/agent/"* ]] && [[ "$file" != *"/subagents/"* ]]; then
+        if ! echo "$frontmatter" | grep -q "^mode:"; then
+            print_warning "Missing 'mode' in agent frontmatter of $file"
+        fi
+    fi
+}
+
+validate_typescript_file() {
+    local file=$1
+    
+    print_info "Validating $file"
+    
+    # Check for basic TypeScript syntax (very basic check)
+    if ! grep -q "export" "$file"; then
+        print_warning "No exports found in $file"
+    else
+        print_success "Has exports"
+    fi
+    
+    # Check for comments/documentation
+    if ! grep -q "^\s*\*" "$file"; then
+        print_warning "No JSDoc comments found in $file"
+    else
+        print_success "Has documentation"
+    fi
+}
+
+validate_directory_structure() {
+    print_info "Validating directory structure"
+    
+    local required_dirs=(
+        ".opencode"
+        ".opencode/agent"
+        ".opencode/command"
+        ".opencode/tool"
+    )
+    
+    for dir in "${required_dirs[@]}"; do
+        if [ ! -d "$dir" ]; then
+            print_error "Missing required directory: $dir"
+        else
+            print_success "Directory exists: $dir"
+        fi
+    done
+}
+
+validate_registry() {
+    print_info "Validating registry.json"
+    
+    if [ ! -f "registry.json" ]; then
+        print_error "registry.json not found"
+        return
+    fi
+    
+    # Check if valid JSON
+    if ! jq empty registry.json 2>/dev/null; then
+        print_error "registry.json is not valid JSON"
+        return
+    fi
+    
+    print_success "registry.json is valid JSON"
+    
+    # Check required fields
+    local required_fields=("version" "repository" "components" "profiles" "metadata")
+    
+    for field in "${required_fields[@]}"; do
+        if ! jq -e ".$field" registry.json > /dev/null 2>&1; then
+            print_error "Missing required field in registry.json: $field"
+        else
+            print_success "Has field: $field"
+        fi
+    done
+}
+
+main() {
+    echo "╔════════════════════════════════════════════════════════════════╗"
+    echo "║           Component Validation                                ║"
+    echo "╚════════════════════════════════════════════════════════════════╝"
+    echo ""
+    
+    # Validate directory structure
+    validate_directory_structure
+    echo ""
+    
+    # Validate registry
+    validate_registry
+    echo ""
+    
+    # Validate all markdown files
+    echo "Validating markdown files..."
+    while IFS= read -r -d '' file; do
+        validate_markdown_frontmatter "$file"
+    done < <(find .opencode -name "*.md" -type f -print0 2>/dev/null)
+    echo ""
+    
+    # Validate TypeScript files
+    echo "Validating TypeScript files..."
+    while IFS= read -r -d '' file; do
+        validate_typescript_file "$file"
+    done < <(find .opencode -name "*.ts" -type f -not -path "*/node_modules/*" -print0 2>/dev/null)
+    echo ""
+    
+    # Summary
+    echo "════════════════════════════════════════════════════════════════"
+    echo "Validation Summary:"
+    echo "  Errors:   $ERRORS"
+    echo "  Warnings: $WARNINGS"
+    echo "════════════════════════════════════════════════════════════════"
+    
+    if [ $ERRORS -gt 0 ]; then
+        echo ""
+        print_error "Validation failed with $ERRORS error(s)"
+        exit 1
+    elif [ $WARNINGS -gt 0 ]; then
+        echo ""
+        print_warning "Validation passed with $WARNINGS warning(s)"
+        exit 0
+    else
+        echo ""
+        print_success "All validations passed!"
+        exit 0
+    fi
+}
+
+main "$@"

+ 83 - 0
update.sh

@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+
+#############################################################################
+# OpenCode Agents Updater
+# Updates existing OpenCode components to latest versions
+#############################################################################
+
+set -e
+
+# Colors
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+CYAN='\033[0;36m'
+BOLD='\033[1m'
+NC='\033[0m'
+
+REPO_URL="https://raw.githubusercontent.com/darrenhinde/opencode-agents/main"
+INSTALL_DIR=".opencode"
+
+print_success() { echo -e "${GREEN}✓${NC} $1"; }
+print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
+print_step() { echo -e "\n${CYAN}${BOLD}▶${NC} $1\n"; }
+
+print_header() {
+    echo -e "${CYAN}${BOLD}"
+    echo "╔════════════════════════════════════════════════════════════════╗"
+    echo "║                                                                ║"
+    echo "║           OpenCode Agents Updater v1.0.0                     ║"
+    echo "║                                                                ║"
+    echo "╚════════════════════════════════════════════════════════════════╝"
+    echo -e "${NC}"
+}
+
+update_component() {
+    local path=$1
+    local url="${REPO_URL}/${path}"
+    
+    if [ ! -f "$path" ]; then
+        print_info "Skipping $path (not installed)"
+        return
+    fi
+    
+    # Backup existing file
+    cp "$path" "${path}.backup"
+    
+    if curl -fsSL "$url" -o "$path"; then
+        print_success "Updated $path"
+        rm "${path}.backup"
+    else
+        print_info "Failed to update $path, restoring backup"
+        mv "${path}.backup" "$path"
+    fi
+}
+
+main() {
+    print_header
+    
+    if [ ! -d "$INSTALL_DIR" ]; then
+        echo "Error: $INSTALL_DIR directory not found"
+        echo "Run install.sh first to install components"
+        exit 1
+    fi
+    
+    print_step "Updating components..."
+    
+    # Update all markdown files in .opencode
+    while IFS= read -r -d '' file; do
+        update_component "$file"
+    done < <(find "$INSTALL_DIR" -name "*.md" -type f -print0)
+    
+    # Update TypeScript files
+    while IFS= read -r -d '' file; do
+        update_component "$file"
+    done < <(find "$INSTALL_DIR" -name "*.ts" -type f -not -path "*/node_modules/*" -print0)
+    
+    # Update config files
+    [ -f "env.example" ] && update_component "env.example"
+    
+    print_success "Update complete!"
+}
+
+main "$@"