Files
Claude-Code-Workflow/Install-Claude.sh
catlog22 be2d0f24b6 Sync Install-Claude.sh with Install-Claude.ps1: Selective folder replacement
**Summary:**
Applied the same selective replacement logic from Install-Claude.ps1 to Install-Claude.sh (Bash version).

**Changes:**
- Added `backup_and_replace_directory` function for folder-level operations
- Modified `install_global` to use selective replacement for .claude, .codex, .gemini, .qwen
- Modified `install_path` to use selective replacement for local folders and config directories
- Maintained `merge_directory_contents` for backward compatibility

**New Installation Flow:**
1. Backup entire existing directory to timestamped backup folder
2. Only remove items in destination that match source item names
3. Copy source items to destination
4. Preserve all other files in destination directory

**Benefits:**
- Protects user-created files and session data
- Only updates files that come from installation source
- Safer incremental updates
- Consistent behavior across PowerShell and Bash versions

**Example:**
Source: .claude/{commands, workflows, scripts}
Destination before: .claude/{commands, workflows, custom.json, .workflow/}
Destination after: .claude/{commands, workflows, scripts, custom.json, .workflow/}

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-11 23:57:12 +08:00

981 lines
34 KiB
Bash

#!/usr/bin/env bash
# Claude Code Workflow System Interactive Installer
# Installation script for Claude Code Workflow System with Agent coordination and distributed memory system.
# Installs globally to user profile directory (~/.claude) by default.
set -e # Exit on error
# Script metadata
SCRIPT_NAME="Claude Code Workflow System Installer"
VERSION="2.1.0"
# Colors for output
COLOR_RESET='\033[0m'
COLOR_SUCCESS='\033[0;32m'
COLOR_INFO='\033[0;36m'
COLOR_WARNING='\033[0;33m'
COLOR_ERROR='\033[0;31m'
COLOR_PROMPT='\033[0;35m'
# Default parameters
INSTALL_MODE=""
TARGET_PATH=""
FORCE=false
NON_INTERACTIVE=false
BACKUP_ALL=true # Enabled by default
NO_BACKUP=false
SOURCE_VERSION="" # Version from remote installer
SOURCE_BRANCH="" # Branch from remote installer
SOURCE_COMMIT="" # Commit SHA from remote installer
# Functions
function write_color() {
local message="$1"
local color="${2:-$COLOR_RESET}"
echo -e "${color}${message}${COLOR_RESET}"
}
function show_banner() {
echo ""
# CLAUDE - Cyan color
write_color ' ______ __ __ ' "$COLOR_INFO"
write_color ' / \ | \ | \ ' "$COLOR_INFO"
write_color '| $$$$$$\| $$ ______ __ __ ____| $$ ______ ' "$COLOR_INFO"
write_color '| $$ \$$| $$ | \ | \ | \ / $$ / \ ' "$COLOR_INFO"
write_color '| $$ | $$ \$$$$$$\| $$ | $$| $$$$$$$| $$$$$$\ ' "$COLOR_INFO"
write_color '| $$ __ | $$ / $$| $$ | $$| $$ | $$| $$ $$ ' "$COLOR_INFO"
write_color '| $$__/ \| $$| $$$$$$$| $$__/ $$| $$__| $$| $$$$$$$$ ' "$COLOR_INFO"
write_color ' \$$ $$| $$ \$$ $$ \$$ $$ \$$ $$ \$$ \ ' "$COLOR_INFO"
write_color ' \$$$$$$ \$$ \$$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$$ ' "$COLOR_INFO"
echo ""
# CODE - Green color
write_color ' ______ __ ' "$COLOR_SUCCESS"
write_color '/ \ | \ ' "$COLOR_SUCCESS"
write_color '| $$$$$$\ ______ ____| $$ ______ ' "$COLOR_SUCCESS"
write_color '| $$ \$$ / \ / $$ / \ ' "$COLOR_SUCCESS"
write_color '| $$ | $$$$$$\| $$$$$$$| $$$$$$\ ' "$COLOR_SUCCESS"
write_color '| $$ __ | $$ | $$| $$ | $$| $$ $$ ' "$COLOR_SUCCESS"
write_color '| $$__/ \| $$__/ $$| $$__| $$| $$$$$$$$ ' "$COLOR_SUCCESS"
write_color ' \$$ $$ \$$ $$ \$$ $$ \$$ \ ' "$COLOR_SUCCESS"
write_color ' \$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$$ ' "$COLOR_SUCCESS"
echo ""
# WORKFLOW - Yellow color
write_color '__ __ __ ______ __ ' "$COLOR_WARNING"
write_color '| \ _ | \ | \ / \ | \ ' "$COLOR_WARNING"
write_color '| $$ / \ | $$ ______ ______ | $$ __ | $$$$$$\| $$ ______ __ __ __ ' "$COLOR_WARNING"
write_color '| $$/ $\| $$ / \ / \ | $$ / \| $$_ \$$| $$ / \ | \ | \ | \' "$COLOR_WARNING"
write_color '| $$ $$$\ $$| $$$$$$\| $$$$$$\| $$_/ $$| $$ \ | $$| $$$$$$\| $$ | $$ | $$' "$COLOR_WARNING"
write_color '| $$ $$\$$\$$| $$ | $$| $$ \$$| $$ $$ | $$$$ | $$| $$ | $$| $$ | $$ | $$' "$COLOR_WARNING"
write_color '| $$$$ \$$$$| $$__/ $$| $$ | $$$$$$\ | $$ | $$| $$__/ $$| $$_/ $$_/ $$' "$COLOR_WARNING"
write_color '| $$$ \$$$ \$$ $$| $$ | $$ \$$\| $$ | $$ \$$ $$ \$$ $$ $$' "$COLOR_WARNING"
write_color ' \$$ \$$ \$$$$$$ \$$ \$$ \$$ \$$ \$$ \$$$$$$ \$$$$$\$$$$' "$COLOR_WARNING"
echo ""
}
function show_header() {
show_banner
write_color " $SCRIPT_NAME v$VERSION" "$COLOR_INFO"
write_color " Unified workflow system with comprehensive coordination" "$COLOR_INFO"
write_color "========================================================================" "$COLOR_INFO"
if [ "$NO_BACKUP" = true ]; then
write_color "WARNING: Backup disabled - existing files will be overwritten!" "$COLOR_WARNING"
else
write_color "Auto-backup enabled - existing files will be backed up" "$COLOR_SUCCESS"
fi
echo ""
}
function test_prerequisites() {
# Test bash version
if [ "${BASH_VERSINFO[0]}" -lt 2 ]; then
write_color "ERROR: Bash 2.0 or higher is required" "$COLOR_ERROR"
write_color "Current version: ${BASH_VERSION}" "$COLOR_ERROR"
return 1
fi
# Test source files exist
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local claude_dir="$script_dir/.claude"
local claude_md="$script_dir/CLAUDE.md"
local codex_dir="$script_dir/.codex"
local gemini_dir="$script_dir/.gemini"
local qwen_dir="$script_dir/.qwen"
if [ ! -d "$claude_dir" ]; then
write_color "ERROR: .claude directory not found in $script_dir" "$COLOR_ERROR"
return 1
fi
if [ ! -f "$claude_md" ]; then
write_color "ERROR: CLAUDE.md file not found in $script_dir" "$COLOR_ERROR"
return 1
fi
if [ ! -d "$codex_dir" ]; then
write_color "ERROR: .codex directory not found in $script_dir" "$COLOR_ERROR"
return 1
fi
if [ ! -d "$gemini_dir" ]; then
write_color "ERROR: .gemini directory not found in $script_dir" "$COLOR_ERROR"
return 1
fi
if [ ! -d "$qwen_dir" ]; then
write_color "ERROR: .qwen directory not found in $script_dir" "$COLOR_ERROR"
return 1
fi
write_color "✓ Prerequisites check passed" "$COLOR_SUCCESS"
return 0
}
function get_user_choice() {
local prompt="$1"
shift
local options=("$@")
local default_index=0
if [ "$NON_INTERACTIVE" = true ]; then
write_color "Non-interactive mode: Using default '${options[$default_index]}'" "$COLOR_INFO" >&2
echo "${options[$default_index]}"
return
fi
# Output prompts to stderr so they don't interfere with function return value
echo "" >&2
write_color "$prompt" "$COLOR_PROMPT" >&2
echo "" >&2
for i in "${!options[@]}"; do
echo " $((i + 1)). ${options[$i]}" >&2
done
echo "" >&2
while true; do
read -p "Please select (1-${#options[@]}): " choice
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "${#options[@]}" ]; then
echo "${options[$((choice - 1))]}"
return
fi
write_color "Invalid selection. Please enter a number between 1 and ${#options[@]}" "$COLOR_WARNING" >&2
done
}
function confirm_action() {
local message="$1"
local default_yes="${2:-false}"
if [ "$FORCE" = true ]; then
write_color "Force mode: Proceeding with '$message'" "$COLOR_INFO"
return 0
fi
if [ "$NON_INTERACTIVE" = true ]; then
if [ "$default_yes" = true ]; then
write_color "Non-interactive mode: $message - Yes" "$COLOR_INFO"
return 0
else
write_color "Non-interactive mode: $message - No" "$COLOR_INFO"
return 1
fi
fi
local prompt
if [ "$default_yes" = true ]; then
prompt="(Y/n)"
else
prompt="(y/N)"
fi
while true; do
read -p "$message $prompt " response
if [ -z "$response" ]; then
[ "$default_yes" = true ] && return 0 || return 1
fi
case "${response,,}" in
y|yes) return 0 ;;
n|no) return 1 ;;
*) write_color "Please answer 'y' or 'n'" "$COLOR_WARNING" ;;
esac
done
}
function get_backup_directory() {
local target_dir="$1"
local timestamp=$(date +"%Y%m%d-%H%M%S")
local backup_dir="${target_dir}/claude-backup-${timestamp}"
mkdir -p "$backup_dir"
echo "$backup_dir"
}
function backup_file_to_folder() {
local file_path="$1"
local backup_folder="$2"
if [ ! -f "$file_path" ]; then
return 1
fi
local file_name=$(basename "$file_path")
local file_dir=$(dirname "$file_path")
local relative_path=""
# Try to determine relative path structure
if [[ "$file_dir" == *".claude"* ]]; then
relative_path="${file_dir#*.claude/}"
fi
# Create subdirectory structure in backup if needed
local backup_sub_dir="$backup_folder"
if [ -n "$relative_path" ]; then
backup_sub_dir="${backup_folder}/${relative_path}"
mkdir -p "$backup_sub_dir"
fi
local backup_file_path="${backup_sub_dir}/${file_name}"
if cp "$file_path" "$backup_file_path"; then
write_color "Backed up: $file_name" "$COLOR_INFO"
return 0
else
write_color "WARNING: Failed to backup file $file_path" "$COLOR_WARNING"
return 1
fi
}
function backup_directory_to_folder() {
local dir_path="$1"
local backup_folder="$2"
if [ ! -d "$dir_path" ]; then
return 1
fi
local dir_name=$(basename "$dir_path")
local backup_dir_path="${backup_folder}/${dir_name}"
if cp -r "$dir_path" "$backup_dir_path"; then
write_color "Backed up directory: $dir_name" "$COLOR_INFO"
return 0
else
write_color "WARNING: Failed to backup directory $dir_path" "$COLOR_WARNING"
return 1
fi
}
function copy_directory_recursive() {
local source="$1"
local destination="$2"
if [ ! -d "$source" ]; then
write_color "ERROR: Source directory does not exist: $source" "$COLOR_ERROR"
return 1
fi
mkdir -p "$destination"
if cp -r "$source/"* "$destination/"; then
write_color "✓ Directory copied: $source -> $destination" "$COLOR_SUCCESS"
return 0
else
write_color "ERROR: Failed to copy directory" "$COLOR_ERROR"
return 1
fi
}
function copy_file_to_destination() {
local source="$1"
local destination="$2"
local description="${3:-file}"
local backup_folder="${4:-}"
if [ -f "$destination" ]; then
# Use BackupAll mode for automatic backup
if [ "$BACKUP_ALL" = true ] && [ "$NO_BACKUP" = false ]; then
if [ -n "$backup_folder" ]; then
backup_file_to_folder "$destination" "$backup_folder"
write_color "Auto-backed up: $description" "$COLOR_SUCCESS"
fi
cp "$source" "$destination"
write_color "$description updated (with backup)" "$COLOR_SUCCESS"
return 0
elif [ "$NO_BACKUP" = true ]; then
if confirm_action "$description already exists. Replace it? (NO BACKUP)" false; then
cp "$source" "$destination"
write_color "$description updated (no backup)" "$COLOR_WARNING"
return 0
else
write_color "Skipping $description installation" "$COLOR_WARNING"
return 1
fi
elif confirm_action "$description already exists. Replace it?" false; then
if [ -n "$backup_folder" ]; then
backup_file_to_folder "$destination" "$backup_folder"
write_color "Existing $description backed up" "$COLOR_SUCCESS"
fi
cp "$source" "$destination"
write_color "$description updated" "$COLOR_SUCCESS"
return 0
else
write_color "Skipping $description installation" "$COLOR_WARNING"
return 1
fi
else
# Ensure destination directory exists
local dest_dir=$(dirname "$destination")
mkdir -p "$dest_dir"
cp "$source" "$destination"
write_color "$description installed" "$COLOR_SUCCESS"
return 0
fi
}
function backup_and_replace_directory() {
local source="$1"
local destination="$2"
local description="${3:-directory}"
local backup_folder="${4:-}"
if [ ! -d "$source" ]; then
write_color "WARNING: Source $description not found: $source" "$COLOR_WARNING"
return 1
fi
# Backup destination if it exists
if [ -d "$destination" ]; then
write_color "Found existing $description at: $destination" "$COLOR_INFO"
# Backup entire directory if backup is enabled
if [ "$NO_BACKUP" = false ] && [ -n "$backup_folder" ]; then
write_color "Backing up entire $description..." "$COLOR_INFO"
if backup_directory_to_folder "$destination" "$backup_folder"; then
write_color "Backed up $description to: $backup_folder" "$COLOR_SUCCESS"
fi
elif [ "$NO_BACKUP" = true ]; then
if ! confirm_action "Replace existing $description without backup?" false; then
write_color "Skipping $description installation" "$COLOR_WARNING"
return 1
fi
fi
# Get all items from source to determine what to clear in destination
write_color "Clearing conflicting items in destination $description..." "$COLOR_INFO"
while IFS= read -r -d '' source_item; do
local item_name=$(basename "$source_item")
local dest_item_path="${destination}/${item_name}"
if [ -e "$dest_item_path" ]; then
write_color "Removing existing: $item_name" "$COLOR_INFO"
rm -rf "$dest_item_path"
fi
done < <(find "$source" -mindepth 1 -maxdepth 1 -print0)
write_color "Cleared conflicting items in destination" "$COLOR_SUCCESS"
else
# Create destination directory if it doesn't exist
mkdir -p "$destination"
write_color "Created destination directory: $destination" "$COLOR_INFO"
fi
# Copy all items from source to destination
write_color "Copying $description from $source to $destination..." "$COLOR_INFO"
while IFS= read -r -d '' item; do
local item_name=$(basename "$item")
local dest_path="${destination}/${item_name}"
cp -r "$item" "$dest_path"
done < <(find "$source" -mindepth 1 -maxdepth 1 -print0)
write_color "$description installed successfully" "$COLOR_SUCCESS"
return 0
}
function merge_directory_contents() {
local source="$1"
local destination="$2"
local description="${3:-directory contents}"
local backup_folder="${4:-}"
if [ ! -d "$source" ]; then
write_color "WARNING: Source $description not found: $source" "$COLOR_WARNING"
return 1
fi
mkdir -p "$destination"
write_color "Created destination directory: $destination" "$COLOR_INFO"
local merged_count=0
local skipped_count=0
# Find all files recursively
while IFS= read -r -d '' file; do
local relative_path="${file#$source/}"
local dest_path="${destination}/${relative_path}"
local dest_dir=$(dirname "$dest_path")
mkdir -p "$dest_dir"
if [ -f "$dest_path" ]; then
local file_name=$(basename "$relative_path")
if [ "$BACKUP_ALL" = true ] && [ "$NO_BACKUP" = false ]; then
if [ -n "$backup_folder" ]; then
backup_file_to_folder "$dest_path" "$backup_folder"
write_color "Auto-backed up: $file_name" "$COLOR_INFO"
fi
cp "$file" "$dest_path"
((merged_count++))
elif [ "$NO_BACKUP" = true ]; then
if confirm_action "File '$relative_path' already exists. Replace it? (NO BACKUP)" false; then
cp "$file" "$dest_path"
((merged_count++))
else
write_color "Skipped $file_name (no backup)" "$COLOR_WARNING"
((skipped_count++))
fi
elif confirm_action "File '$relative_path' already exists. Replace it?" false; then
if [ -n "$backup_folder" ]; then
backup_file_to_folder "$dest_path" "$backup_folder"
write_color "Backed up existing $file_name" "$COLOR_INFO"
fi
cp "$file" "$dest_path"
((merged_count++))
else
write_color "Skipped $file_name" "$COLOR_WARNING"
((skipped_count++))
fi
else
cp "$file" "$dest_path"
((merged_count++))
fi
done < <(find "$source" -type f -print0)
write_color "✓ Merged $merged_count files, skipped $skipped_count files" "$COLOR_SUCCESS"
return 0
}
function install_global() {
write_color "Installing Claude Code Workflow System globally..." "$COLOR_INFO"
local user_home="$HOME"
local global_claude_dir="${user_home}/.claude"
local global_claude_md="${global_claude_dir}/CLAUDE.md"
local global_codex_dir="${user_home}/.codex"
local global_gemini_dir="${user_home}/.gemini"
local global_qwen_dir="${user_home}/.qwen"
write_color "Global installation path: $user_home" "$COLOR_INFO"
# Source paths
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local source_claude_dir="${script_dir}/.claude"
local source_claude_md="${script_dir}/CLAUDE.md"
local source_codex_dir="${script_dir}/.codex"
local source_gemini_dir="${script_dir}/.gemini"
local source_qwen_dir="${script_dir}/.qwen"
# Create backup folder if needed
local backup_folder=""
if [ "$NO_BACKUP" = false ]; then
local has_existing_files=false
if [ -d "$global_claude_dir" ] && [ "$(ls -A "$global_claude_dir" 2>/dev/null)" ]; then
has_existing_files=true
elif [ -d "$global_codex_dir" ] && [ "$(ls -A "$global_codex_dir" 2>/dev/null)" ]; then
has_existing_files=true
elif [ -d "$global_gemini_dir" ] && [ "$(ls -A "$global_gemini_dir" 2>/dev/null)" ]; then
has_existing_files=true
elif [ -d "$global_qwen_dir" ] && [ "$(ls -A "$global_qwen_dir" 2>/dev/null)" ]; then
has_existing_files=true
elif [ -f "$global_claude_md" ]; then
has_existing_files=true
fi
if [ "$has_existing_files" = true ]; then
backup_folder=$(get_backup_directory "$user_home")
write_color "Backup folder created: $backup_folder" "$COLOR_INFO"
fi
fi
# Replace .claude directory (backup → clear conflicting → copy)
write_color "Installing .claude directory..." "$COLOR_INFO"
backup_and_replace_directory "$source_claude_dir" "$global_claude_dir" ".claude directory" "$backup_folder"
# Handle CLAUDE.md file
write_color "Installing CLAUDE.md to global .claude directory..." "$COLOR_INFO"
copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"
# Replace .codex directory (backup → clear conflicting → copy)
write_color "Installing .codex directory..." "$COLOR_INFO"
backup_and_replace_directory "$source_codex_dir" "$global_codex_dir" ".codex directory" "$backup_folder"
# Replace .gemini directory (backup → clear conflicting → copy)
write_color "Installing .gemini directory..." "$COLOR_INFO"
backup_and_replace_directory "$source_gemini_dir" "$global_gemini_dir" ".gemini directory" "$backup_folder"
# Replace .qwen directory (backup → clear conflicting → copy)
write_color "Installing .qwen directory..." "$COLOR_INFO"
backup_and_replace_directory "$source_qwen_dir" "$global_qwen_dir" ".qwen directory" "$backup_folder"
# Remove empty backup folder
if [ -n "$backup_folder" ] && [ -d "$backup_folder" ]; then
if [ -z "$(ls -A "$backup_folder" 2>/dev/null)" ]; then
rm -rf "$backup_folder"
write_color "Removed empty backup folder" "$COLOR_INFO"
fi
fi
# Create version.json in global .claude directory
write_color "Creating version.json..." "$COLOR_INFO"
create_version_json "$global_claude_dir" "Global"
return 0
}
function install_path() {
local target_dir="$1"
write_color "Installing Claude Code Workflow System in hybrid mode..." "$COLOR_INFO"
write_color "Local path: $target_dir" "$COLOR_INFO"
local user_home="$HOME"
local global_claude_dir="${user_home}/.claude"
write_color "Global path: $user_home" "$COLOR_INFO"
# Source paths
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local source_claude_dir="${script_dir}/.claude"
local source_claude_md="${script_dir}/CLAUDE.md"
local source_codex_dir="${script_dir}/.codex"
local source_gemini_dir="${script_dir}/.gemini"
local source_qwen_dir="${script_dir}/.qwen"
# Local paths
local local_claude_dir="${target_dir}/.claude"
local local_codex_dir="${target_dir}/.codex"
local local_gemini_dir="${target_dir}/.gemini"
local local_qwen_dir="${target_dir}/.qwen"
# Create backup folder if needed
local backup_folder=""
if [ "$NO_BACKUP" = false ]; then
if [ -d "$local_claude_dir" ] || [ -d "$local_codex_dir" ] || [ -d "$local_gemini_dir" ] || [ -d "$local_qwen_dir" ] || [ -d "$global_claude_dir" ]; then
backup_folder=$(get_backup_directory "$target_dir")
write_color "Backup folder created: $backup_folder" "$COLOR_INFO"
fi
fi
# Create local .claude directory
mkdir -p "$local_claude_dir"
write_color "✓ Created local .claude directory" "$COLOR_SUCCESS"
# Local folders to install
local local_folders=("agents" "commands" "output-styles")
write_color "Installing local components (agents, commands, output-styles)..." "$COLOR_INFO"
for folder in "${local_folders[@]}"; do
local source_folder="${source_claude_dir}/${folder}"
local dest_folder="${local_claude_dir}/${folder}"
if [ -d "$source_folder" ]; then
# Use new backup and replace logic for local folders
write_color "Installing local folder: $folder..." "$COLOR_INFO"
backup_and_replace_directory "$source_folder" "$dest_folder" "$folder folder" "$backup_folder"
write_color "✓ Installed local folder: $folder" "$COLOR_SUCCESS"
else
write_color "WARNING: Source folder not found: $folder" "$COLOR_WARNING"
fi
done
# Global components - exclude local folders
write_color "Installing global components to $global_claude_dir..." "$COLOR_INFO"
local merged_count=0
while IFS= read -r -d '' file; do
local relative_path="${file#$source_claude_dir/}"
local top_folder=$(echo "$relative_path" | cut -d'/' -f1)
# Skip local folders
if [[ " ${local_folders[*]} " =~ " ${top_folder} " ]]; then
continue
fi
local dest_path="${global_claude_dir}/${relative_path}"
local dest_dir=$(dirname "$dest_path")
mkdir -p "$dest_dir"
if [ -f "$dest_path" ]; then
if [ "$BACKUP_ALL" = true ] && [ "$NO_BACKUP" = false ]; then
if [ -n "$backup_folder" ]; then
backup_file_to_folder "$dest_path" "$backup_folder"
fi
cp "$file" "$dest_path"
((merged_count++))
elif [ "$NO_BACKUP" = true ]; then
if confirm_action "File '$relative_path' already exists in global location. Replace it? (NO BACKUP)" false; then
cp "$file" "$dest_path"
((merged_count++))
fi
elif confirm_action "File '$relative_path' already exists in global location. Replace it?" false; then
if [ -n "$backup_folder" ]; then
backup_file_to_folder "$dest_path" "$backup_folder"
fi
cp "$file" "$dest_path"
((merged_count++))
fi
else
cp "$file" "$dest_path"
((merged_count++))
fi
done < <(find "$source_claude_dir" -type f -print0)
write_color "✓ Merged $merged_count files to global location" "$COLOR_SUCCESS"
# Handle CLAUDE.md file in global .claude directory
local global_claude_md="${global_claude_dir}/CLAUDE.md"
write_color "Installing CLAUDE.md to global .claude directory..." "$COLOR_INFO"
copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"
# Replace .codex directory to local location (backup → clear conflicting → copy)
write_color "Installing .codex directory to local location..." "$COLOR_INFO"
backup_and_replace_directory "$source_codex_dir" "$local_codex_dir" ".codex directory" "$backup_folder"
# Replace .gemini directory to local location (backup → clear conflicting → copy)
write_color "Installing .gemini directory to local location..." "$COLOR_INFO"
backup_and_replace_directory "$source_gemini_dir" "$local_gemini_dir" ".gemini directory" "$backup_folder"
# Replace .qwen directory to local location (backup → clear conflicting → copy)
write_color "Installing .qwen directory to local location..." "$COLOR_INFO"
backup_and_replace_directory "$source_qwen_dir" "$local_qwen_dir" ".qwen directory" "$backup_folder"
# Remove empty backup folder
if [ -n "$backup_folder" ] && [ -d "$backup_folder" ]; then
if [ -z "$(ls -A "$backup_folder" 2>/dev/null)" ]; then
rm -rf "$backup_folder"
write_color "Removed empty backup folder" "$COLOR_INFO"
fi
fi
# Create version.json in local .claude directory
write_color "Creating version.json in local directory..." "$COLOR_INFO"
create_version_json "$local_claude_dir" "Path"
# Also create version.json in global .claude directory
write_color "Creating version.json in global directory..." "$COLOR_INFO"
create_version_json "$global_claude_dir" "Global"
return 0
}
function get_installation_mode() {
if [ -n "$INSTALL_MODE" ]; then
write_color "Installation mode: $INSTALL_MODE" "$COLOR_INFO" >&2
echo "$INSTALL_MODE"
return
fi
local modes=(
"Global - Install to user profile (~/.claude/)"
"Path - Install to custom directory (partial local + global)"
)
local selection=$(get_user_choice "Choose installation mode:" "${modes[@]}")
if [[ "$selection" == Global* ]]; then
echo "Global"
elif [[ "$selection" == Path* ]]; then
echo "Path"
else
echo "Global"
fi
}
function get_installation_path() {
local mode="$1"
if [ "$mode" = "Global" ]; then
echo "$HOME"
return
fi
if [ -n "$TARGET_PATH" ]; then
if [ -d "$TARGET_PATH" ]; then
echo "$TARGET_PATH"
return
fi
write_color "WARNING: Specified target path does not exist: $TARGET_PATH" "$COLOR_WARNING"
fi
# Interactive path selection
while true; do
echo ""
write_color "Enter the target directory path for installation:" "$COLOR_PROMPT"
write_color "(This will install agents, commands, output-styles locally, other files globally)" "$COLOR_INFO"
read -p "Path: " path
if [ -z "$path" ]; then
write_color "Path cannot be empty" "$COLOR_WARNING"
continue
fi
# Expand ~ and environment variables
path=$(eval echo "$path")
if [ -d "$path" ]; then
echo "$path"
return
fi
write_color "Path does not exist: $path" "$COLOR_WARNING"
if confirm_action "Create this directory?" true; then
if mkdir -p "$path"; then
write_color "✓ Directory created successfully" "$COLOR_SUCCESS"
echo "$path"
return
else
write_color "ERROR: Failed to create directory" "$COLOR_ERROR"
fi
fi
done
}
function create_version_json() {
local target_claude_dir="$1"
local installation_mode="$2"
# Determine version from source parameter (passed from install-remote.sh)
local version_number="${SOURCE_VERSION:-unknown}"
local source_branch="${SOURCE_BRANCH:-unknown}"
local commit_sha="${SOURCE_COMMIT:-unknown}"
# Get current UTC timestamp
local installation_date_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
# Create version.json content
local version_json_path="${target_claude_dir}/version.json"
cat > "$version_json_path" << EOF
{
"version": "$version_number",
"commit_sha": "$commit_sha",
"installation_mode": "$installation_mode",
"installation_path": "$target_claude_dir",
"installation_date_utc": "$installation_date_utc",
"source_branch": "$source_branch",
"installer_version": "$VERSION"
}
EOF
if [ -f "$version_json_path" ]; then
write_color "Created version.json: $version_number ($commit_sha) - $installation_mode" "$COLOR_SUCCESS"
return 0
else
write_color "WARNING: Failed to create version.json" "$COLOR_WARNING"
return 1
fi
}
function show_summary() {
local mode="$1"
local path="$2"
local success="$3"
echo ""
if [ "$success" = true ]; then
write_color "✓ Installation completed successfully!" "$COLOR_SUCCESS"
else
write_color "Installation completed with warnings" "$COLOR_WARNING"
fi
write_color "Installation Details:" "$COLOR_INFO"
echo " Mode: $mode"
if [ "$mode" = "Path" ]; then
echo " Local Path: $path"
echo " Global Path: $HOME"
echo " Local Components: agents, commands, output-styles, .codex, .gemini, .qwen"
echo " Global Components: workflows, scripts, python_script, etc."
else
echo " Path: $path"
echo " Global Components: .claude, .codex, .gemini, .qwen"
fi
if [ "$NO_BACKUP" = true ]; then
echo " Backup: Disabled (no backup created)"
elif [ "$BACKUP_ALL" = true ]; then
echo " Backup: Enabled (automatic backup of all existing files)"
else
echo " Backup: Enabled (default behavior)"
fi
echo ""
write_color "Next steps:" "$COLOR_INFO"
echo "1. Review CLAUDE.md - Customize guidelines for your project"
echo "2. Review .codex/Agent.md - Codex agent execution protocol"
echo "3. Review .gemini/CLAUDE.md - Gemini agent execution protocol"
echo "4. Review .qwen/QWEN.md - Qwen agent execution protocol"
echo "5. Configure settings - Edit .claude/settings.local.json as needed"
echo "6. Start using Claude Code with Agent workflow coordination!"
echo "7. Use /workflow commands for task execution"
echo "8. Use /update-memory commands for memory system management"
echo ""
write_color "Documentation: https://github.com/catlog22/Claude-Code-Workflow" "$COLOR_INFO"
write_color "Features: Unified workflow system with comprehensive file output generation" "$COLOR_INFO"
}
function parse_arguments() {
while [[ $# -gt 0 ]]; do
case "$1" in
-InstallMode)
INSTALL_MODE="$2"
shift 2
;;
-TargetPath)
TARGET_PATH="$2"
shift 2
;;
-Force)
FORCE=true
shift
;;
-NonInteractive)
NON_INTERACTIVE=true
shift
;;
-BackupAll)
BACKUP_ALL=true
NO_BACKUP=false
shift
;;
-NoBackup)
NO_BACKUP=true
BACKUP_ALL=false
shift
;;
-SourceVersion)
SOURCE_VERSION="$2"
shift 2
;;
-SourceBranch)
SOURCE_BRANCH="$2"
shift 2
;;
-SourceCommit)
SOURCE_COMMIT="$2"
shift 2
;;
--help|-h)
show_help
exit 0
;;
*)
write_color "Unknown option: $1" "$COLOR_ERROR"
show_help
exit 1
;;
esac
done
}
function show_help() {
cat << EOF
$SCRIPT_NAME v$VERSION
Usage: $0 [OPTIONS]
Options:
-InstallMode <mode> Installation mode: Global or Path
-TargetPath <path> Target path for Path installation mode
-Force Skip confirmation prompts
-NonInteractive Run in non-interactive mode with default options
-BackupAll Automatically backup all existing files (default)
-NoBackup Disable automatic backup functionality
-SourceVersion <ver> Source version (passed from install-remote.sh)
-SourceBranch <name> Source branch (passed from install-remote.sh)
-SourceCommit <sha> Source commit SHA (passed from install-remote.sh)
--help, -h Show this help message
Examples:
# Interactive installation
$0
# Global installation without prompts
$0 -InstallMode Global -Force
# Path installation with custom directory
$0 -InstallMode Path -TargetPath /opt/claude-code-workflow
# Installation without backup
$0 -NoBackup
# With version info (typically called by install-remote.sh)
$0 -InstallMode Global -Force -SourceVersion "3.4.2" -SourceBranch "main" -SourceCommit "abc1234"
EOF
}
function main() {
show_header
# Test prerequisites
write_color "Checking system requirements..." "$COLOR_INFO"
if ! test_prerequisites; then
write_color "Prerequisites check failed!" "$COLOR_ERROR"
return 1
fi
local mode=$(get_installation_mode)
local install_path=""
local success=false
if [ "$mode" = "Global" ]; then
install_path="$HOME"
if install_global; then
success=true
fi
elif [ "$mode" = "Path" ]; then
install_path=$(get_installation_path "$mode")
if install_path "$install_path"; then
success=true
fi
fi
show_summary "$mode" "$install_path" "$success"
# Wait for user confirmation in interactive mode
if [ "$NON_INTERACTIVE" != true ]; then
echo ""
write_color "Installation completed. Press Enter to exit..." "$COLOR_PROMPT"
read -r
fi
if [ "$success" = true ]; then
return 0
else
return 1
fi
}
# Initialize backup behavior - backup is enabled by default unless NoBackup is specified
if [ "$NO_BACKUP" = false ]; then
BACKUP_ALL=true
fi
# Parse command line arguments
parse_arguments "$@"
# Run main function
main
exit $?