diff --git a/Install-Claude.sh b/Install-Claude.sh deleted file mode 100644 index 61a375d4..00000000 --- a/Install-Claude.sh +++ /dev/null @@ -1,1848 +0,0 @@ -#!/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 -UNINSTALL=false # Uninstall mode -SOURCE_VERSION="" # Version from remote installer -SOURCE_BRANCH="" # Branch from remote installer -SOURCE_COMMIT="" # Commit SHA from remote installer - -# Global manifest directory location -MANIFEST_DIR="${HOME}/.claude-manifests" - -# 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" - local quiet="${3:-}" # Optional quiet mode - - 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 - # Only output if not in quiet mode - if [ "$quiet" != "quiet" ]; then - write_color "Backed up: $file_name" "$COLOR_INFO" - fi - return 0 - else - # Always show warnings - if [ "$quiet" != "quiet" ]; then - write_color "WARNING: Failed to backup file $file_path" "$COLOR_WARNING" - fi - 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_critical_config_files() { - local target_directory="$1" - local backup_folder="$2" - shift 2 - local file_names=("$@") - - if [ "$NO_BACKUP" = true ] || [ -z "$backup_folder" ]; then - return 0 - fi - - if [ ! -d "$target_directory" ]; then - return 0 - fi - - local backed_up_count=0 - for file_name in "${file_names[@]}"; do - local file_path="${target_directory}/${file_name}" - if [ -f "$file_path" ]; then - if backup_file_to_folder "$file_path" "$backup_folder"; then - write_color "Critical config backed up: $file_name" "$COLOR_SUCCESS" - ((backed_up_count++)) - fi - fi - done - - if [ "$backed_up_count" -gt 0 ]; then - write_color "Backed up $backed_up_count critical configuration file(s)" "$COLOR_INFO" - 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 - - # Create destination directory if it doesn't exist - if [ ! -d "$destination" ]; then - mkdir -p "$destination" - write_color "Created destination directory: $destination" "$COLOR_INFO" - fi - - # Count total files first - local total_files=$(find "$source" -type f | wc -l) - local merged_count=0 - local skipped_count=0 - local backed_up_count=0 - local processed_count=0 - - write_color "Processing $total_files files in $description..." "$COLOR_INFO" - - # Find all files recursively - while IFS= read -r -d '' file; do - ((processed_count++)) - - 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 - # Use BackupAll mode for automatic backup without confirmation (default behavior) - if [ "$BACKUP_ALL" = true ] && [ "$NO_BACKUP" = false ]; then - if [ -n "$backup_folder" ]; then - # Quiet backup - no individual file output - if backup_file_to_folder "$dest_path" "$backup_folder" "quiet"; then - ((backed_up_count++)) - fi - fi - cp "$file" "$dest_path" - ((merged_count++)) - elif [ "$NO_BACKUP" = true ]; then - # No backup mode - ask for confirmation - if confirm_action "File '$relative_path' already exists. Replace it? (NO BACKUP)" false; then - cp "$file" "$dest_path" - ((merged_count++)) - else - ((skipped_count++)) - fi - elif confirm_action "File '$relative_path' already exists. Replace it?" false; then - if [ -n "$backup_folder" ]; then - # Quiet backup - no individual file output - if backup_file_to_folder "$dest_path" "$backup_folder" "quiet"; then - ((backed_up_count++)) - fi - fi - cp "$file" "$dest_path" - ((merged_count++)) - else - ((skipped_count++)) - fi - else - cp "$file" "$dest_path" - ((merged_count++)) - fi - - # Show progress every 100 files (optimized for performance) - if [ $((processed_count % 100)) -eq 0 ] || [ "$processed_count" -eq "$total_files" ]; then - local percent=$((processed_count * 100 / total_files)) - echo -ne "\rMerging $description: $processed_count/$total_files files ($percent%)..." - fi - done < <(find "$source" -type f -print0) - - # Clear progress line - echo -ne "\r\033[K" - - # Show summary - if [ "$backed_up_count" -gt 0 ]; then - write_color "✓ Merged $merged_count files ($backed_up_count backed up), skipped $skipped_count files" "$COLOR_SUCCESS" - else - write_color "✓ Merged $merged_count files, skipped $skipped_count files" "$COLOR_SUCCESS" - fi - - 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" - - # Clean up old installation before proceeding (fast move operation) - echo "" - move_old_installation "$user_home" "Global" - - # Initialize manifest - local manifest_file=$(new_install_manifest "Global" "$user_home") - - # 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 - - # Merge .claude directory (incremental overlay - preserves user files) - write_color "Installing .claude directory (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_claude_dir" "$global_claude_dir" ".claude directory" "$backup_folder"; then - # Track .claude directory in manifest - add_manifest_entry "$manifest_file" "$global_claude_dir" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_claude_dir" "$global_claude_dir" "File" - fi - - # Handle CLAUDE.md file - write_color "Installing CLAUDE.md to global .claude directory..." "$COLOR_INFO" - if copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"; then - # Track CLAUDE.md in manifest - add_manifest_entry "$manifest_file" "$global_claude_md" "File" - fi - - # Backup critical config files in .codex directory before installation - backup_critical_config_files "$global_codex_dir" "$backup_folder" "AGENTS.md" - - # Merge .codex directory (incremental overlay - preserves user files) - write_color "Installing .codex directory (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_codex_dir" "$global_codex_dir" ".codex directory" "$backup_folder"; then - # Track .codex directory in manifest - add_manifest_entry "$manifest_file" "$global_codex_dir" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_codex_dir" "$global_codex_dir" "File" - fi - - # Backup critical config files in .gemini directory before installation - backup_critical_config_files "$global_gemini_dir" "$backup_folder" "GEMINI.md" "CLAUDE.md" - - # Merge .gemini directory (incremental overlay - preserves user files) - write_color "Installing .gemini directory (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_gemini_dir" "$global_gemini_dir" ".gemini directory" "$backup_folder"; then - # Track .gemini directory in manifest - add_manifest_entry "$manifest_file" "$global_gemini_dir" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_gemini_dir" "$global_gemini_dir" "File" - fi - - # Backup critical config files in .qwen directory before installation - backup_critical_config_files "$global_qwen_dir" "$backup_folder" "QWEN.md" - - # Merge .qwen directory (incremental overlay - preserves user files) - write_color "Installing .qwen directory (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_qwen_dir" "$global_qwen_dir" ".qwen directory" "$backup_folder"; then - # Track .qwen directory in manifest - add_manifest_entry "$manifest_file" "$global_qwen_dir" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_qwen_dir" "$global_qwen_dir" "File" - fi - - # 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" - - # Save installation manifest - save_install_manifest "$manifest_file" "$user_home" "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" - - # Clean up old installation before proceeding (fast move operation) - echo "" - move_old_installation "$target_dir" "Path" - - # Initialize manifest - local manifest_file=$(new_install_manifest "Path" "$target_dir") - - # 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 incremental merge for local folders (preserves user customizations) - write_color "Installing local folder: $folder (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_folder" "$dest_folder" "$folder folder" "$backup_folder"; then - # Track local folder in manifest - add_manifest_entry "$manifest_file" "$dest_folder" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_folder" "$dest_folder" "File" - fi - 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 (use same efficient method as Global mode) - write_color "Installing global components to $global_claude_dir..." "$COLOR_INFO" - - # Create temporary directory for global files only - local temp_global_dir="/tmp/claude-global-$$" - mkdir -p "$temp_global_dir" - - # Copy global files to temp directory (excluding local folders) - write_color "Preparing global components..." "$COLOR_INFO" - 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 temp_dest_path="${temp_global_dir}/${relative_path}" - local temp_dest_dir=$(dirname "$temp_dest_path") - - mkdir -p "$temp_dest_dir" - cp "$file" "$temp_dest_path" - done < <(find "$source_claude_dir" -type f -print0) - - # Use bulk merge method (same as Global mode - fast!) - if merge_directory_contents "$temp_global_dir" "$global_claude_dir" "global components" "$backup_folder"; then - # Track global files in manifest using bulk method (fast!) - add_manifest_entry "$manifest_file" "$global_claude_dir" "Directory" - - # Track files from TEMP directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$temp_global_dir" "$global_claude_dir" "File" - fi - - # Clean up temp directory - rm -rf "$temp_global_dir" - - # 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" - if copy_file_to_destination "$source_claude_md" "$global_claude_md" "CLAUDE.md" "$backup_folder"; then - # Track CLAUDE.md in manifest - add_manifest_entry "$manifest_file" "$global_claude_md" "File" - fi - - # Backup critical config files in .codex directory before installation - backup_critical_config_files "$local_codex_dir" "$backup_folder" "AGENTS.md" - - # Merge .codex directory to local location (incremental overlay - preserves user files) - write_color "Installing .codex directory to local location (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_codex_dir" "$local_codex_dir" ".codex directory" "$backup_folder"; then - # Track .codex directory in manifest - add_manifest_entry "$manifest_file" "$local_codex_dir" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_codex_dir" "$local_codex_dir" "File" - fi - - # Backup critical config files in .gemini directory before installation - backup_critical_config_files "$local_gemini_dir" "$backup_folder" "GEMINI.md" "CLAUDE.md" - - # Merge .gemini directory to local location (incremental overlay - preserves user files) - write_color "Installing .gemini directory to local location (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_gemini_dir" "$local_gemini_dir" ".gemini directory" "$backup_folder"; then - # Track .gemini directory in manifest - add_manifest_entry "$manifest_file" "$local_gemini_dir" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_gemini_dir" "$local_gemini_dir" "File" - fi - - # Backup critical config files in .qwen directory before installation - backup_critical_config_files "$local_qwen_dir" "$backup_folder" "QWEN.md" - - # Merge .qwen directory to local location (incremental overlay - preserves user files) - write_color "Installing .qwen directory to local location (incremental merge)..." "$COLOR_INFO" - if merge_directory_contents "$source_qwen_dir" "$local_qwen_dir" ".qwen directory" "$backup_folder"; then - # Track .qwen directory in manifest - add_manifest_entry "$manifest_file" "$local_qwen_dir" "Directory" - - # Track files from SOURCE directory using bulk operation - add_manifest_entries_bulk "$manifest_file" "$source_qwen_dir" "$local_qwen_dir" "File" - fi - - # 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" - - # Save installation manifest - save_install_manifest "$manifest_file" "$target_dir" "Path" - - 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 -} - -# ============================================================================ -# INSTALLATION MANIFEST MANAGEMENT -# ============================================================================ - -function new_install_manifest() { - local installation_mode="$1" - local installation_path="$2" - - # Create manifest directory if it doesn't exist - mkdir -p "$MANIFEST_DIR" - - # Generate unique manifest ID based on timestamp and mode - # Distinguish between Global and Path installations with clear naming - local timestamp=$(date +"%Y%m%d-%H%M%S") - local mode_prefix - if [ "$installation_mode" = "Global" ]; then - mode_prefix="manifest-global" - else - mode_prefix="manifest-path" - fi - local manifest_id="${mode_prefix}-${timestamp}" - - # Create manifest file path - local manifest_file="${MANIFEST_DIR}/${manifest_id}.json" - - # Get current UTC timestamp - local installation_date_utc=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - - # Create manifest JSON - cat > "$manifest_file" << EOF -{ - "manifest_id": "$manifest_id", - "version": "1.0", - "installation_mode": "$installation_mode", - "installation_path": "$installation_path", - "installation_date": "$installation_date_utc", - "installer_version": "$VERSION", - "files": [], - "directories": [] -} -EOF - - echo "$manifest_file" -} - -function add_manifest_entry() { - local manifest_file="$1" - local entry_path="$2" - local entry_type="$3" - - if [ ! -f "$manifest_file" ]; then - write_color "WARNING: Manifest file not found: $manifest_file" "$COLOR_WARNING" - return 1 - fi - - local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - - # Escape path for JSON - local escaped_path=$(echo "$entry_path" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g') - - # Create entry JSON - local entry_json=$(cat << EOF -{ - "path": "$escaped_path", - "type": "$entry_type", - "timestamp": "$timestamp" -} -EOF -) - - # Read manifest, add entry, write back - local temp_file="${manifest_file}.tmp" - - if [ "$entry_type" = "File" ]; then - jq --argjson entry "$entry_json" '.files += [$entry]' "$manifest_file" > "$temp_file" - else - jq --argjson entry "$entry_json" '.directories += [$entry]' "$manifest_file" > "$temp_file" - fi - - # Only replace manifest if jq succeeded - if [ -s "$temp_file" ]; then - mv "$temp_file" "$manifest_file" - else - write_color "WARNING: Failed to add manifest entry (jq error)" "$COLOR_WARNING" - rm -f "$temp_file" - fi -} - -function add_manifest_entries_bulk() { - local manifest_file="$1" - local source_dir="$2" - local target_base="$3" - local entry_type="$4" - - if [ ! -f "$manifest_file" ]; then - write_color "WARNING: Manifest file not found: $manifest_file" "$COLOR_WARNING" - return 1 - fi - - if [ ! -d "$source_dir" ]; then - return 0 - fi - - local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - local temp_file="${manifest_file}.tmp" - local paths_file=$(mktemp) - local entries_file=$(mktemp) - - # Collect all file paths and compute target paths using bash string operations - # This mimics the original while loop logic - while IFS= read -r -d '' source_file; do - local relative_path="${source_file#$source_dir}" - local target_path="${target_base}${relative_path}" - echo "$target_path" - done < <(find "$source_dir" -type f -print0) > "$paths_file" - - # Check if paths_file has content - if [ ! -s "$paths_file" ]; then - rm -f "$paths_file" "$entries_file" - return 0 - fi - - # Generate JSON entries from paths (filter empty lines) - grep -v '^$' "$paths_file" | jq -R --arg date "$timestamp" --arg type "$entry_type" ' - { - "path": ., - "type": $type, - "timestamp": $date - } - ' | jq -s '.' > "$entries_file" - - # Check if entries_file has valid content - if [ ! -s "$entries_file" ]; then - rm -f "$paths_file" "$entries_file" - return 0 - fi - - # Add all entries to manifest using --slurpfile to avoid argument length limit - if [ "$entry_type" = "File" ]; then - jq --slurpfile entries "$entries_file" '.files += $entries[0]' "$manifest_file" > "$temp_file" - else - jq --slurpfile entries "$entries_file" '.directories += $entries[0]' "$manifest_file" > "$temp_file" - fi - - # Only replace manifest if jq succeeded and temp_file has content - if [ -s "$temp_file" ]; then - mv "$temp_file" "$manifest_file" - else - write_color "WARNING: Failed to update manifest (jq error), keeping original" "$COLOR_WARNING" - rm -f "$temp_file" - fi - - rm -f "$paths_file" "$entries_file" - - return 0 -} - -function remove_old_manifests_for_path() { - local installation_path="$1" - local installation_mode="$2" - local current_manifest_file="$3" # Optional: exclude this file from deletion - - if [ ! -d "$MANIFEST_DIR" ]; then - return 0 - fi - - # Normalize paths for comparison (lowercase, remove trailing slashes) - local target_path=$(echo "$installation_path" | sed 's:/*$::' | tr '[:upper:]' '[:lower:]') - local removed_count=0 - - # Find and remove old manifests for the same installation path and mode - # Support both new (manifest-*) and old (install-*) format - while IFS= read -r -d '' file; do - # Skip the current manifest file if specified - if [ -n "$current_manifest_file" ] && [ "$file" = "$current_manifest_file" ]; then - continue - fi - - local manifest_path=$(jq -r '.installation_path // ""' "$file" 2>/dev/null) - local manifest_mode=$(jq -r '.installation_mode // "Global"' "$file" 2>/dev/null) - - if [ -n "$manifest_path" ]; then - # Normalize manifest path - local normalized_manifest_path=$(echo "$manifest_path" | sed 's:/*$::' | tr '[:upper:]' '[:lower:]') - - # Only remove if BOTH path and mode match - if [ "$normalized_manifest_path" = "$target_path" ] && [ "$manifest_mode" = "$installation_mode" ]; then - rm -f "$file" - write_color "Removed old manifest: $(basename "$file")" "$COLOR_INFO" - ((removed_count++)) - fi - fi - done < <(find "$MANIFEST_DIR" \( -name "manifest-*.json" -o -name "install-*.json" \) -type f -print0 2>/dev/null) - - if [ "$removed_count" -gt 0 ]; then - write_color "Removed $removed_count old manifest(s) for installation path: $installation_path" "$COLOR_SUCCESS" - fi - - return 0 -} - -function save_install_manifest() { - local manifest_file="$1" - local installation_path="$2" - local installation_mode="$3" - - # Remove old manifests for the same installation path and mode (excluding current one) - if [ -n "$installation_path" ] && [ -n "$installation_mode" ]; then - remove_old_manifests_for_path "$installation_path" "$installation_mode" "$manifest_file" - fi - - if [ -f "$manifest_file" ]; then - write_color "Installation manifest saved: $manifest_file" "$COLOR_SUCCESS" - return 0 - else - write_color "WARNING: Failed to save installation manifest" "$COLOR_WARNING" - return 1 - fi -} - -function migrate_legacy_manifest() { - local legacy_manifest="${HOME}/.claude-install-manifest.json" - - if [ ! -f "$legacy_manifest" ]; then - return 0 - fi - - write_color "Found legacy manifest file, migrating to new system..." "$COLOR_INFO" - - # Create manifest directory if it doesn't exist - mkdir -p "$MANIFEST_DIR" - - # Read legacy manifest and generate new manifest ID with new naming convention - local mode=$(jq -r '.installation_mode // "Global"' "$legacy_manifest") - local timestamp=$(date +"%Y%m%d-%H%M%S") - local mode_prefix - if [ "$mode" = "Global" ]; then - mode_prefix="manifest-global" - else - mode_prefix="manifest-path" - fi - local manifest_id="${mode_prefix}-${timestamp}-migrated" - - # Create new manifest file - local new_manifest="${MANIFEST_DIR}/${manifest_id}.json" - - # Copy with new manifest_id field - jq --arg id "$manifest_id" '. + {manifest_id: $id}' "$legacy_manifest" > "$new_manifest" - - # Rename old manifest (don't delete, keep as backup) - mv "$legacy_manifest" "${legacy_manifest}.migrated" - - write_color "Legacy manifest migrated successfully" "$COLOR_SUCCESS" - write_color "Old manifest backed up to: ${legacy_manifest}.migrated" "$COLOR_INFO" -} - -function get_all_install_manifests() { - # Migrate legacy manifest if exists - migrate_legacy_manifest - - if [ ! -d "$MANIFEST_DIR" ]; then - echo "[]" - return - fi - - # Check if any manifest files exist (both new and old formats) - local manifest_count=$(find "$MANIFEST_DIR" \( -name "manifest-*.json" -o -name "install-*.json" \) -type f 2>/dev/null | wc -l) - - if [ "$manifest_count" -eq 0 ]; then - echo "[]" - return - fi - - # Collect all manifests into JSON array - local all_manifests="[" - local first=true - - while IFS= read -r -d '' file; do - if [ "$first" = true ]; then - first=false - else - all_manifests+="," - fi - - # Add manifest_file field - local manifest_content=$(jq --arg file "$file" '. + {manifest_file: $file}' "$file") - - # Count files and directories safely - local files_count=$(echo "$manifest_content" | jq '.files | length') - local dirs_count=$(echo "$manifest_content" | jq '.directories | length') - - # Add counts to manifest - manifest_content=$(echo "$manifest_content" | jq --argjson fc "$files_count" --argjson dc "$dirs_count" '. + {files_count: $fc, directories_count: $dc}') - - all_manifests+="$manifest_content" - done < <(find "$MANIFEST_DIR" \( -name "manifest-*.json" -o -name "install-*.json" \) -type f -print0 | sort -z) - - all_manifests+="]" - - # Group by installation_path (normalized) and keep only the latest per path - # Use jq to process the array and keep only the latest manifest for each unique path - local latest_manifests=$(echo "$all_manifests" | jq ' - # Normalize paths and add normalized_path field - map(. + { - normalized_path: (.installation_path // "" | ascii_downcase | sub("/+$"; "")) - }) - # Group by normalized_path - | group_by(.normalized_path) - # For each group, sort by installation_date descending and take the first (latest) - | map( - sort_by(.installation_date) | reverse | .[0] - ) - # Remove the temporary normalized_path field - | map(del(.normalized_path)) - # Sort final results by installation_date descending - | sort_by(.installation_date) | reverse - ') - - echo "$latest_manifests" -} - -function move_old_installation() { - local installation_path="$1" - local installation_mode="$2" - - write_color "Checking for previous installation..." "$COLOR_INFO" - - # Find existing manifest for this installation path and mode - local manifests_json=$(get_all_install_manifests) - local target_path=$(echo "$installation_path" | sed 's:/*$::' | tr '[:upper:]' '[:lower:]') - - local old_manifest=$(echo "$manifests_json" | jq --arg path "$target_path" --arg mode "$installation_mode" ' - .[] | select( - (.installation_path | ascii_downcase | sub("/+$"; "")) == $path and - .installation_mode == $mode - ) - ') - - if [ -z "$old_manifest" ] || [ "$old_manifest" = "null" ]; then - write_color "No previous $installation_mode installation found at this path" "$COLOR_INFO" - return 0 - fi - - local install_date=$(echo "$old_manifest" | jq -r '.installation_date') - local files_count=$(echo "$old_manifest" | jq -r '.files_count') - local dirs_count=$(echo "$old_manifest" | jq -r '.directories_count') - - write_color "Found previous installation from $install_date" "$COLOR_INFO" - write_color "Files: $files_count, Directories: $dirs_count" "$COLOR_INFO" - - # Create backup folder - local timestamp=$(date +"%Y%m%d-%H%M%S") - local backup_dir="${installation_path}/claude-backup-old-${timestamp}" - mkdir -p "$backup_dir" - write_color "Created backup folder: $backup_dir" "$COLOR_SUCCESS" - - local moved_files=0 - local removed_dirs=0 - local failed_items=() - - # Move files first (from manifest) - write_color "Moving old installation files to backup..." "$COLOR_INFO" - while IFS= read -r file_path; do - if [ -z "$file_path" ] || [ "$file_path" = "null" ]; then - continue - fi - - if [ -f "$file_path" ]; then - # Calculate relative path from installation root - local relative_path="${file_path#$installation_path}" - relative_path="${relative_path#/}" - - if [ -z "$relative_path" ]; then - relative_path=$(basename "$file_path") - fi - - local backup_dest_dir=$(dirname "${backup_dir}/${relative_path}") - - mkdir -p "$backup_dest_dir" - if mv "$file_path" "${backup_dest_dir}/" 2>/dev/null; then - ((moved_files++)) - else - write_color " WARNING: Failed to move file: $file_path" "$COLOR_WARNING" - failed_items+=("$file_path") - fi - fi - done <<< "$(echo "$old_manifest" | jq -r '.files[].path')" - - # Remove empty directories (in reverse order to handle nested dirs) - write_color "Cleaning up empty directories..." "$COLOR_INFO" - while IFS= read -r dir_path; do - if [ -z "$dir_path" ] || [ "$dir_path" = "null" ]; then - continue - fi - - if [ -d "$dir_path" ]; then - # Check if directory is empty - if [ -z "$(ls -A "$dir_path" 2>/dev/null)" ]; then - if rmdir "$dir_path" 2>/dev/null; then - write_color " Removed empty directory: $dir_path" "$COLOR_INFO" - ((removed_dirs++)) - fi - else - write_color " Directory not empty (preserved): $dir_path" "$COLOR_INFO" - fi - fi - done <<< "$(echo "$old_manifest" | jq -r '.directories[].path' | awk '{ print length, $0 }' | sort -rn | cut -d' ' -f2-)" - - # Note: Old manifest will be automatically removed by save_install_manifest - # via remove_old_manifests_for_path to ensure robust cleanup - - echo "" - write_color "Old installation cleanup summary:" "$COLOR_INFO" - echo " Files moved: $moved_files" - echo " Directories removed: $removed_dirs" - echo " Backup location: $backup_dir" - - if [ ${#failed_items[@]} -gt 0 ]; then - write_color " Failed items: ${#failed_items[@]}" "$COLOR_WARNING" - fi - - echo "" - - # Return backup path for reference - return 0 -} - -# ============================================================================ -# UNINSTALLATION FUNCTIONS -# ============================================================================ - -function uninstall_claude_workflow() { - write_color "Claude Code Workflow System Uninstaller" "$COLOR_INFO" - write_color "========================================" "$COLOR_INFO" - echo "" - - # Load all manifests - local manifests_json=$(get_all_install_manifests) - local manifests_count=$(echo "$manifests_json" | jq 'length') - - if [ "$manifests_count" -eq 0 ]; then - write_color "ERROR: No installation manifests found in: $MANIFEST_DIR" "$COLOR_ERROR" - write_color "Cannot proceed with uninstallation without manifest." "$COLOR_ERROR" - echo "" - write_color "Manual uninstallation instructions:" "$COLOR_INFO" - echo "For Global installation, remove these directories:" - echo " - ~/.claude/agents" - echo " - ~/.claude/commands" - echo " - ~/.claude/output-styles" - echo " - ~/.claude/workflows" - echo " - ~/.claude/scripts" - echo " - ~/.claude/prompt-templates" - echo " - ~/.claude/python_script" - echo " - ~/.claude/skills" - echo " - ~/.claude/version.json" - echo " - ~/.claude/CLAUDE.md" - echo " - ~/.codex" - echo " - ~/.gemini" - echo " - ~/.qwen" - return 1 - fi - - # Display available installations - write_color "Found $manifests_count installation(s):" "$COLOR_INFO" - echo "" - - # If only one manifest, use it directly - local selected_index=0 - local selected_manifest="" - - if [ "$manifests_count" -eq 1 ]; then - selected_manifest=$(echo "$manifests_json" | jq '.[0]') - - # Read version from version.json - local install_path=$(echo "$selected_manifest" | jq -r '.installation_path // ""') - local install_mode=$(echo "$selected_manifest" | jq -r '.installation_mode // "Unknown"') - local version_str="Version Unknown" - - # Determine version.json path - local version_json_path="${install_path}/.claude/version.json" - - if [ -f "$version_json_path" ]; then - local ver=$(jq -r '.version // ""' "$version_json_path" 2>/dev/null) - if [ -n "$ver" ] && [ "$ver" != "unknown" ]; then - version_str="v$ver" - fi - fi - - write_color "Found installation: $version_str - $install_path" "$COLOR_INFO" - else - # Multiple manifests - let user choose (simplified: only version and path) - local options=() - - for i in $(seq 0 $((manifests_count - 1))); do - local m=$(echo "$manifests_json" | jq ".[$i]") - - local path_info=$(echo "$m" | jq -r '.installation_path // ""') - local install_mode=$(echo "$m" | jq -r '.installation_mode // "Unknown"') - local version_str="Version Unknown" - - # Read version from version.json - local version_json_path="${path_info}/.claude/version.json" - - if [ -f "$version_json_path" ]; then - local ver=$(jq -r '.version // ""' "$version_json_path" 2>/dev/null) - if [ -n "$ver" ] && [ "$ver" != "unknown" ]; then - version_str="v$ver" - fi - fi - - local path_str="Path Unknown" - if [ -n "$path_info" ]; then - path_str="$path_info" - fi - - options+=("$((i + 1)). $version_str - $path_str") - done - - options+=("Cancel - Don't uninstall anything") - - echo "" - local selection=$(get_user_choice "Select installation to uninstall:" "${options[@]}") - - if [[ "$selection" == Cancel* ]]; then - write_color "Uninstallation cancelled." "$COLOR_WARNING" - return 1 - fi - - # Parse selection to get index - selected_index=$((${selection%%.*} - 1)) - selected_manifest=$(echo "$manifests_json" | jq ".[$selected_index]") - fi - - # Display selected installation info (simplified: only version and path) - local final_path=$(echo "$selected_manifest" | jq -r '.installation_path // ""') - local final_mode=$(echo "$selected_manifest" | jq -r '.installation_mode // "Unknown"') - local final_version="Version Unknown" - - # Read version from version.json - local final_version_path="${final_path}/.claude/version.json" - if [ -f "$final_version_path" ]; then - local ver=$(jq -r '.version // ""' "$final_version_path" 2>/dev/null) - if [ -n "$ver" ] && [ "$ver" != "unknown" ]; then - final_version="v$ver" - fi - fi - - echo "" - write_color "Uninstallation Target:" "$COLOR_INFO" - echo " $final_version" - echo " Path: $final_path" - echo "" - - # Confirm uninstallation - if ! confirm_action "Do you want to uninstall this installation?" false; then - write_color "Uninstallation cancelled." "$COLOR_WARNING" - return 1 - fi - - local removed_files=0 - local failed_items=() - local skipped_files=0 - - # Check if this is a Path mode uninstallation and if Global installation exists - local is_path_mode=false - local has_global_installation=false - - if [ "$final_mode" = "Path" ]; then - is_path_mode=true - - # Check if any Global installation manifest exists - if [ -d "$MANIFEST_DIR" ]; then - local global_manifest_count=$(find "$MANIFEST_DIR" -name "manifest-global-*.json" -type f 2>/dev/null | wc -l) - if [ "$global_manifest_count" -gt 0 ]; then - has_global_installation=true - write_color "Found Global installation, global files will be preserved" "$COLOR_WARNING" - echo "" - fi - fi - fi - - # Only remove files listed in manifest - do NOT remove directories - write_color "Removing installed files..." "$COLOR_INFO" - - local files_array=$(echo "$selected_manifest" | jq -c '.files[]' 2>/dev/null) - - if [ -n "$files_array" ]; then - while IFS= read -r file_entry; do - local file_path=$(echo "$file_entry" | jq -r '.path') - - # For Path mode uninstallation, skip global files if Global installation exists - if [ "$is_path_mode" = true ] && [ "$has_global_installation" = true ]; then - local global_claude_dir="${HOME}/.claude" - - # Skip files under global .claude directory - if [[ "$file_path" == "$global_claude_dir"* ]]; then - ((skipped_files++)) - continue - fi - fi - - if [ -f "$file_path" ]; then - if rm -f "$file_path" 2>/dev/null; then - ((removed_files++)) - else - write_color " WARNING: Failed to remove: $file_path" "$COLOR_WARNING" - failed_items+=("$file_path") - fi - fi - done <<< "$files_array" - fi - - # Display removal summary - if [ "$skipped_files" -gt 0 ]; then - write_color "Removed $removed_files files, skipped $skipped_files global files" "$COLOR_SUCCESS" - else - write_color "Removed $removed_files files" "$COLOR_SUCCESS" - fi - - # Remove manifest file - local manifest_file=$(echo "$selected_manifest" | jq -r '.manifest_file') - - if [ -f "$manifest_file" ]; then - if rm -f "$manifest_file" 2>/dev/null; then - write_color "Removed installation manifest: $(basename "$manifest_file")" "$COLOR_SUCCESS" - else - write_color "WARNING: Failed to remove manifest file" "$COLOR_WARNING" - fi - fi - - # Show summary - echo "" - write_color "========================================" "$COLOR_INFO" - write_color "Uninstallation Summary:" "$COLOR_INFO" - echo " Files removed: $removed_files" - - if [ "$skipped_files" -gt 0 ]; then - echo " Files skipped (global files preserved): $skipped_files" - echo "" - write_color "Note: $skipped_files global files were preserved due to existing Global installation" "$COLOR_INFO" - fi - - if [ ${#failed_items[@]} -gt 0 ]; then - echo "" - write_color "Failed to remove the following items:" "$COLOR_WARNING" - for item in "${failed_items[@]}"; do - echo " - $item" - done - fi - - echo "" - if [ ${#failed_items[@]} -eq 0 ]; then - if [ "$skipped_files" -gt 0 ]; then - write_color "✓ Uninstallation complete! Removed $removed_files files, preserved $skipped_files global files." "$COLOR_SUCCESS" - else - write_color "✓ Claude Code Workflow has been successfully uninstalled!" "$COLOR_SUCCESS" - fi - else - write_color "Uninstallation completed with warnings." "$COLOR_WARNING" - write_color "Please manually remove the failed items listed above." "$COLOR_INFO" - fi - - return 0 -} - -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 - ;; - -Uninstall) - UNINSTALL=true - 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 Installation mode: Global or Path - -TargetPath 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 - -Uninstall Uninstall Claude Code Workflow System based on installation manifest - -SourceVersion Source version (passed from install-remote.sh) - -SourceBranch Source branch (passed from install-remote.sh) - -SourceCommit 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 - - # Uninstall Claude Code Workflow System - $0 -Uninstall - - # Uninstall without confirmation prompts - $0 -Uninstall -Force - - # 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 banner first - show_banner - - # Check for uninstall mode from parameter or ask user interactively - local operation_mode="Install" - - if [ "$UNINSTALL" = true ]; then - operation_mode="Uninstall" - elif [ "$NON_INTERACTIVE" != true ] && [ -z "$INSTALL_MODE" ]; then - # Interactive mode selection - echo "" - local operations=( - "Install - Install Claude Code Workflow System" - "Uninstall - Remove Claude Code Workflow System" - ) - local selection=$(get_user_choice "Choose operation:" "${operations[@]}") - - if [[ "$selection" == Uninstall* ]]; then - operation_mode="Uninstall" - fi - fi - - # Handle uninstall mode - if [ "$operation_mode" = "Uninstall" ]; then - if uninstall_claude_workflow; then - local result=0 - else - local result=1 - fi - - if [ "$NON_INTERACTIVE" != true ]; then - echo "" - write_color "Press Enter to exit..." "$COLOR_PROMPT" - read -r - fi - - return $result - fi - - # Continue with installation - 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 $? diff --git a/ccw/src/core/routes/codexlens/config-handlers.ts b/ccw/src/core/routes/codexlens/config-handlers.ts index 60a7178d..247eaa90 100644 --- a/ccw/src/core/routes/codexlens/config-handlers.ts +++ b/ccw/src/core/routes/codexlens/config-handlers.ts @@ -170,9 +170,13 @@ export async function handleCodexLensConfigRoutes(ctx: RouteContext): Promise 0 ? 100 : 0; diff --git a/ccw/src/tools/cli-executor-core.ts b/ccw/src/tools/cli-executor-core.ts index b4138043..e077ff7d 100644 --- a/ccw/src/tools/cli-executor-core.ts +++ b/ccw/src/tools/cli-executor-core.ts @@ -1184,21 +1184,42 @@ export { /** * Get status of all CLI tools * Dynamically reads tools from config file + * Handles different tool types: + * - builtin: Check system PATH availability + * - cli-wrapper: Check CLI Settings configuration exists + * - api-endpoint: Check LiteLLM endpoint configuration exists */ export async function getCliToolsStatus(): Promise> { // Default built-in tools const builtInTools = ['gemini', 'qwen', 'codex', 'claude', 'opencode']; - // Try to get tools from config - let tools = builtInTools; + // Try to get tools from config with their types + interface ToolInfo { + name: string; + type?: 'builtin' | 'cli-wrapper' | 'api-endpoint'; + enabled?: boolean; + id?: string; // For api-endpoint type + } + let toolsInfo: ToolInfo[] = builtInTools.map(name => ({ name, type: 'builtin' })); + try { // Dynamic import to avoid circular dependencies const { loadClaudeCliTools } = await import('./claude-cli-tools.js'); const config = loadClaudeCliTools(configBaseDir); if (config.tools && typeof config.tools === 'object') { - // Merge built-in tools with config tools to ensure all are checked - const configTools = Object.keys(config.tools); - tools = [...new Set([...builtInTools, ...configTools])]; + // Build complete tool info list from config + const configToolsInfo: ToolInfo[] = Object.entries(config.tools).map(([name, toolConfig]) => ({ + name, + type: toolConfig.type || 'builtin', + enabled: toolConfig.enabled !== false, + id: toolConfig.id + })); + + // Merge: config tools take precedence over built-in defaults + const toolsMap = new Map(); + toolsInfo.forEach(t => toolsMap.set(t.name, t)); + configToolsInfo.forEach(t => toolsMap.set(t.name, t)); + toolsInfo = Array.from(toolsMap.values()); } } catch (e) { // Fallback to built-in tools if config load fails @@ -1207,8 +1228,49 @@ export async function getCliToolsStatus(): Promise = {}; - await Promise.all(tools.map(async (tool) => { - results[tool] = await checkToolAvailability(tool); + await Promise.all(toolsInfo.map(async (toolInfo) => { + const { name, type, enabled, id } = toolInfo; + + // Check availability based on tool type + if (type === 'cli-wrapper') { + // For cli-wrapper: check if CLI Settings configuration exists + try { + const { findEndpoint } = await import('../config/cli-settings-manager.js'); + const endpoint = findEndpoint(name); + if (endpoint && endpoint.enabled) { + results[name] = { + available: true, + path: `cli-settings:${endpoint.id}` // Virtual path indicating CLI Settings source + }; + } else { + results[name] = { available: false, path: null }; + } + } catch (e) { + debugLog('cli-executor', `Failed to check cli-wrapper ${name}: ${(e as Error).message}`); + results[name] = { available: false, path: null }; + } + } else if (type === 'api-endpoint') { + // For api-endpoint: check if LiteLLM endpoint configuration exists + try { + const { findEndpointById } = await import('../config/litellm-api-config-manager.js'); + const endpointId = id || name; + const endpoint = findEndpointById(configBaseDir, endpointId); + if (endpoint && enabled !== false) { + results[name] = { + available: true, + path: `litellm:${endpointId}` // Virtual path indicating LiteLLM source + }; + } else { + results[name] = { available: false, path: null }; + } + } catch (e) { + debugLog('cli-executor', `Failed to check api-endpoint ${name}: ${(e as Error).message}`); + results[name] = { available: false, path: null }; + } + } else { + // For builtin: check system PATH availability + results[name] = await checkToolAvailability(name); + } })); return results; diff --git a/cli-executor/setup.test.ts b/cli-executor/setup.test.ts deleted file mode 100644 index 8e0dbfbd..00000000 --- a/cli-executor/setup.test.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '../ccw/tests/integration/cli-executor/setup.test.ts'; - diff --git a/test_index_dir/README.md b/test_index_dir/README.md deleted file mode 100644 index 89e85e15..00000000 --- a/test_index_dir/README.md +++ /dev/null @@ -1 +0,0 @@ -# Test README\n\nThis is a test file for vector indexing. diff --git a/test_index_dir/test.js b/test_index_dir/test.js deleted file mode 100644 index c8a3b2f6..00000000 --- a/test_index_dir/test.js +++ /dev/null @@ -1 +0,0 @@ -// Test file for embedding generation\nfunction testEmbedding() {\n console.log('Hello world');\n}\n\nexport default testEmbedding;