#!/bin/bash # Claude Code Agents Sync Script # Syncs local agents with GitHub repository and backs up to Gitea set -euo pipefail # Configuration AGENTS_DIR="${HOME}/.claude/agents" BACKUP_DIR="${AGENTS_DIR}.backup.$(date +%Y%m%d-%H%M%S)" GITHUB_REPO="https://github.com/contains-studio/agents" TEMP_DIR="/tmp/claude-agents-sync-$RANDOM" UPSTREAM_DIR="$TEMP_DIR/upstream" LOG_FILE="${AGENTS_DIR}/update.log" # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Logging function log() { local level=$1 shift local message="$@" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE" } # Print colored message print_msg() { local color=$1 shift echo -e "${color}$*${NC}" } # Create backup create_backup() { print_msg "$BLUE" "📦 Creating backup..." if cp -r "$AGENTS_DIR" "$BACKUP_DIR"; then print_msg "$GREEN" "✓ Backup created: $BACKUP_DIR" log "INFO" "Backup created at $BACKUP_DIR" else print_msg "$RED" "✗ Failed to create backup" log "ERROR" "Backup creation failed" exit 1 fi } # Download upstream agents download_upstream() { print_msg "$BLUE" "📥 Downloading agents from $GITHUB_REPO..." mkdir -p "$TEMP_DIR" if command -v git &> /dev/null; then # Use git if available (faster) git clone --depth 1 "$GITHUB_REPO" "$UPSTREAM_DIR" 2>/dev/null || { print_msg "$RED" "✗ Failed to clone repository" log "ERROR" "Git clone failed" exit 1 } else # Fallback to wget/curl print_msg "$YELLOW" "⚠ Git not found, downloading archive..." local archive="$TEMP_DIR/agents.tar.gz" if command -v wget &> /dev/null; then wget -q "$GITHUB_REPO/archive/main.tar.gz" -O "$archive" elif command -v curl &> /dev/null; then curl -sL "$GITHUB_REPO/archive/main.tar.gz" -o "$archive" else print_msg "$RED" "✗ Need git, wget, or curl" exit 1 fi mkdir -p "$UPSTREAM_DIR" tar -xzf "$archive" -C "$UPSTREAM_DIR" --strip-components=1 fi print_msg "$GREEN" "✓ Downloaded upstream agents" log "INFO" "Downloaded agents from $GITHUB_REPO" } # Compare and sync agents sync_agents() { print_msg "$BLUE" "🔄 Syncing agents..." local new_agents=() local updated_agents=() local custom_agents=() # Find all agent files in upstream while IFS= read -r upstream_file; do local rel_path="${upstream_file#$UPSTREAM_DIR/}" local local_file="$AGENTS_DIR/$rel_path" if [[ ! -f "$local_file" ]]; then # New agent new_agents+=("$rel_path") mkdir -p "$(dirname "$local_file")" cp "$upstream_file" "$local_file" log "INFO" "Added new agent: $rel_path" elif ! diff -q "$upstream_file" "$local_file" &>/dev/null; then # Updated agent - check if customized if grep -q "CUSTOMIZED" "$local_file" 2>/dev/null || \ [[ -f "${local_file}.local" ]]; then custom_agents+=("$rel_path") log "WARN" "Skipped customized agent: $rel_path" else updated_agents+=("$rel_path") cp "$upstream_file" "$local_file" log "INFO" "Updated agent: $rel_path" fi fi done < <(find "$UPSTREAM_DIR" -name "*.md" -type f) # Report results echo "" print_msg "$GREEN" "✨ New agents (${#new_agents[@]}):" for agent in "${new_agents[@]}"; do echo " + $agent" done | head -20 echo "" print_msg "$YELLOW" "📝 Updated agents (${#updated_agents[@]}):" for agent in "${updated_agents[@]}"; do echo " ~ $agent" done | head -20 if [[ ${#custom_agents[@]} -gt 0 ]]; then echo "" print_msg "$YELLOW" "⚠️ Preserved custom agents (${#custom_agents[@]}):" for agent in "${custom_agents[@]}"; do echo " • $agent" done | head -20 fi # Summary local total_changes=$((${#new_agents[@]} + ${#updated_agents[@]})) log "INFO" "Sync complete: ${#new_agents[@]} new, ${#updated_agents[@]} updated, ${#custom_agents[@]} preserved" # If REPO_AGENTS_DIR is set, also update repository agents if [[ -n "${REPO_AGENTS_DIR:-}" && -d "$REPO_AGENTS_DIR" ]]; then print_msg "$BLUE" "📦 Updating repository agents directory..." for category in engineering marketing product studio-operations project-management testing design bonus; do if [ -d "$AGENTS_DIR/$category" ]; then mkdir -p "$REPO_AGENTS_DIR/$category" cp -f "$AGENTS_DIR/$category"/*.md "$REPO_AGENTS_DIR/$category/" 2>/dev/null || true fi done if [ -f "$AGENTS_DIR/README.md" ]; then cp -f "$AGENTS_DIR/README.md" "$REPO_AGENTS_DIR/README.md" 2>/dev/null || true fi print_msg "$GREEN" "✓ Repository agents updated" log "INFO" "Repository agents updated at $REPO_AGENTS_DIR" fi } # Commit to git commit_to_git() { print_msg "$BLUE" "💾 Committing to git..." cd "$AGENTS_DIR" # Check if there are changes if git diff --quiet && git diff --cached --quiet; then print_msg "$YELLOW" "⚠️ No changes to commit" return fi # Add all agents git add . -- '*.md' # Commit with descriptive message local commit_msg="Update agents from upstream $(date '+%Y-%m-%d %H:%M:%S') Changes: - $(git diff --cached --name-only | wc -l) files updated - From: $GITHUB_REPO" git commit -m "$commit_msg" 2>/dev/null || { print_msg "$YELLOW" "⚠️ Nothing to commit or git not configured" log "WARN" "Git commit skipped" return } print_msg "$GREEN" "✓ Committed to local git" log "INFO" "Committed changes to git" } # Push to Gitea push_to_gitea() { if [[ -z "${GITEA_REPO_URL:-}" ]]; then print_msg "$YELLOW" "⚠️ GITEA_REPO_URL not set, skipping push" print_msg "$YELLOW" " Set it with: export GITEA_REPO_URL='your-gitea-repo-url'" log "WARN" "GITEA_REPO_URL not set, push skipped" return fi print_msg "$BLUE" "📤 Pushing to Gitea..." cd "$AGENTS_DIR" # Ensure remote exists if ! git remote get-url origin &>/dev/null; then git remote add origin "$GITEA_REPO_URL" fi if git push -u origin main 2>/dev/null || git push -u origin master 2>/dev/null; then print_msg "$GREEN" "✓ Pushed to Gitea" log "INFO" "Pushed to Gitea: $GITEA_REPO_URL" else print_msg "$YELLOW" "⚠️ Push failed (check credentials/URL)" log "ERROR" "Push to Gitea failed" fi } # Cleanup cleanup() { rm -rf "$TEMP_DIR" } # Rollback function rollback() { print_msg "$RED" "🔄 Rolling back to backup..." if [[ -d "$BACKUP_DIR" ]]; then rm -rf "$AGENTS_DIR" mv "$BACKUP_DIR" "$AGENTS_DIR" print_msg "$GREEN" "✓ Rolled back successfully" log "INFO" "Rolled back to $BACKUP_DIR" else print_msg "$RED" "✗ No backup found!" log "ERROR" "Rollback failed - no backup" fi } # Main execution main() { print_msg "$BLUE" "🚀 Claude Code Agents Sync" print_msg "$BLUE" "════════════════════════════" echo "" trap cleanup EXIT trap rollback ERR create_backup download_upstream sync_agents commit_to_git push_to_gitea echo "" print_msg "$GREEN" "✅ Sync complete!" print_msg "$BLUE" "💾 Backup: $BACKUP_DIR" print_msg "$BLUE" "📋 Log: $LOG_FILE" echo "" print_msg "$YELLOW" "To rollback: rm -rf $AGENTS_DIR && mv $BACKUP_DIR $AGENTS_DIR" } # Run main function main "$@"