OpenWriterControle/scripts/register-component.sh
darrenhinde 777b3fc702 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
2025-11-17 23:43:13 +00:00

364 lines
12 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 "$@"