- 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
364 lines
12 KiB
Bash
Executable file
364 lines
12 KiB
Bash
Executable file
#!/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 "$@"
|