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
This commit is contained in:
parent
bd6aec49c6
commit
777b3fc702
8 changed files with 1617 additions and 1 deletions
75
.github/workflows/update-registry.yml
vendored
Normal file
75
.github/workflows/update-registry.yml
vendored
Normal file
|
|
@ -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
CONTRIBUTING.md
Normal file
237
CONTRIBUTING.md
Normal file
|
|
@ -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! 🎉
|
||||
79
README.md
79
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
install.sh
Executable file
609
install.sh
Executable file
|
|
@ -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
registry.json
Normal file
0
registry.json
Normal file
364
scripts/register-component.sh
Executable file
364
scripts/register-component.sh
Executable file
|
|
@ -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
scripts/validate-component.sh
Executable file
171
scripts/validate-component.sh
Executable file
|
|
@ -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
update.sh
Executable file
83
update.sh
Executable file
|
|
@ -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 "$@"
|
||||
Loading…
Reference in a new issue