mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
**Issue:**
Previous logic cleared entire destination directory, removing all existing files including user-created content.
**Fix:**
Modified `Backup-AndReplaceDirectory` to:
1. Backup entire destination directory (as before)
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
**New Behavior:**
- Removes: ~/.claude/commands (if exists in source)
- Removes: ~/.claude/workflows (if exists in source)
- Preserves: ~/.claude/custom-file.md (user-created)
- Preserves: ~/.claude/.workflow/ (session data)
**Example:**
Source: .claude/{commands, workflows, scripts}
Destination before: .claude/{commands, workflows, custom-config.json, .workflow/}
Destination after: .claude/{commands, workflows, scripts, custom-config.json, .workflow/}
**Benefits:**
- Protects user-created files and session data
- Only updates files that come from installation source
- Safer incremental updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
1158 lines
44 KiB
PowerShell
1158 lines
44 KiB
PowerShell
#Requires -Version 5.1
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Claude Code Workflow System Interactive Installer
|
|
|
|
.DESCRIPTION
|
|
Installation script for Claude Code Workflow System with Agent coordination and distributed memory system.
|
|
Installs globally to user profile directory (~/.claude) by default.
|
|
|
|
.PARAMETER InstallMode
|
|
Installation mode: "Global" (default and only supported mode)
|
|
|
|
.PARAMETER TargetPath
|
|
Target path for Custom installation mode
|
|
|
|
.PARAMETER Force
|
|
Skip confirmation prompts
|
|
|
|
.PARAMETER NonInteractive
|
|
Run in non-interactive mode with default options
|
|
|
|
.PARAMETER BackupAll
|
|
Automatically backup all existing files without confirmation prompts (enabled by default)
|
|
|
|
.PARAMETER NoBackup
|
|
Disable automatic backup functionality
|
|
|
|
.EXAMPLE
|
|
.\Install-Claude.ps1
|
|
Interactive installation with mode selection
|
|
|
|
.EXAMPLE
|
|
.\Install-Claude.ps1 -InstallMode Global -Force
|
|
Global installation without prompts
|
|
|
|
.EXAMPLE
|
|
.\Install-Claude.ps1 -Force -NonInteractive
|
|
Global installation without prompts
|
|
|
|
.EXAMPLE
|
|
.\Install-Claude.ps1 -BackupAll
|
|
Global installation with automatic backup of all existing files
|
|
|
|
.EXAMPLE
|
|
.\Install-Claude.ps1 -NoBackup
|
|
Installation without any backup (overwrite existing files)
|
|
#>
|
|
|
|
param(
|
|
[ValidateSet("Global", "Path")]
|
|
[string]$InstallMode = "",
|
|
|
|
[string]$TargetPath = "",
|
|
|
|
[switch]$Force,
|
|
|
|
[switch]$NonInteractive,
|
|
|
|
[switch]$BackupAll,
|
|
|
|
[switch]$NoBackup,
|
|
|
|
[string]$SourceVersion = "",
|
|
|
|
[string]$SourceBranch = "",
|
|
|
|
[string]$SourceCommit = ""
|
|
)
|
|
|
|
# Set encoding for proper Unicode support
|
|
if ($PSVersionTable.PSVersion.Major -ge 6) {
|
|
$OutputEncoding = [System.Text.Encoding]::UTF8
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
|
} else {
|
|
# For Windows PowerShell 5.1
|
|
chcp 65001 | Out-Null
|
|
}
|
|
|
|
# Script metadata
|
|
$ScriptName = "Claude Code Workflow System Installer"
|
|
$ScriptVersion = "2.2.0" # Installer script version
|
|
|
|
# Default version (will be overridden by -SourceVersion from install-remote.ps1)
|
|
$DefaultVersion = "unknown"
|
|
|
|
# Initialize backup behavior - backup is enabled by default unless NoBackup is specified
|
|
if (-not $BackupAll -and -not $NoBackup) {
|
|
$BackupAll = $true
|
|
Write-Verbose "Auto-backup enabled by default. Use -NoBackup to disable."
|
|
}
|
|
|
|
# Colors for output
|
|
$ColorSuccess = "Green"
|
|
$ColorInfo = "Cyan"
|
|
$ColorWarning = "Yellow"
|
|
$ColorError = "Red"
|
|
$ColorPrompt = "Magenta"
|
|
|
|
function Write-ColorOutput {
|
|
param(
|
|
[string]$Message,
|
|
[string]$Color = "White"
|
|
)
|
|
Write-Host $Message -ForegroundColor $Color
|
|
}
|
|
|
|
function Show-Banner {
|
|
Write-Host ""
|
|
# CLAUDE - Cyan color
|
|
Write-Host ' ______ __ __ ' -ForegroundColor Cyan
|
|
Write-Host ' / \ | \ | \ ' -ForegroundColor Cyan
|
|
Write-Host '| $$$$$$\| $$ ______ __ __ ____| $$ ______ ' -ForegroundColor Cyan
|
|
Write-Host '| $$ \$$| $$ | \ | \ | \ / $$ / \ ' -ForegroundColor Cyan
|
|
Write-Host '| $$ | $$ \$$$$$$\| $$ | $$| $$$$$$$| $$$$$$\ ' -ForegroundColor Cyan
|
|
Write-Host '| $$ __ | $$ / $$| $$ | $$| $$ | $$| $$ $$ ' -ForegroundColor Cyan
|
|
Write-Host '| $$__/ \| $$| $$$$$$$| $$__/ $$| $$__| $$| $$$$$$$$ ' -ForegroundColor Cyan
|
|
Write-Host ' \$$ $$| $$ \$$ $$ \$$ $$ \$$ $$ \$$ \ ' -ForegroundColor Cyan
|
|
Write-Host ' \$$$$$$ \$$ \$$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$$ ' -ForegroundColor Cyan
|
|
Write-Host ""
|
|
|
|
# CODE - Green color
|
|
Write-Host ' ______ __ ' -ForegroundColor Green
|
|
Write-Host '/ \ | \ ' -ForegroundColor Green
|
|
Write-Host '| $$$$$$\ ______ ____| $$ ______ ' -ForegroundColor Green
|
|
Write-Host '| $$ \$$ / \ / $$ / \ ' -ForegroundColor Green
|
|
Write-Host '| $$ | $$$$$$\| $$$$$$$| $$$$$$\ ' -ForegroundColor Green
|
|
Write-Host '| $$ __ | $$ | $$| $$ | $$| $$ $$ ' -ForegroundColor Green
|
|
Write-Host '| $$__/ \| $$__/ $$| $$__| $$| $$$$$$$$ ' -ForegroundColor Green
|
|
Write-Host ' \$$ $$ \$$ $$ \$$ $$ \$$ \ ' -ForegroundColor Green
|
|
Write-Host ' \$$$$$$ \$$$$$$ \$$$$$$$ \$$$$$$$ ' -ForegroundColor Green
|
|
Write-Host ""
|
|
|
|
# WORKFLOW - Yellow color
|
|
Write-Host '__ __ __ ______ __ ' -ForegroundColor Yellow
|
|
Write-Host '| \ _ | \ | \ / \ | \ ' -ForegroundColor Yellow
|
|
Write-Host '| $$ / \ | $$ ______ ______ | $$ __ | $$$$$$\| $$ ______ __ __ __ ' -ForegroundColor Yellow
|
|
Write-Host '| $$/ $\| $$ / \ / \ | $$ / \| $$_ \$$| $$ / \ | \ | \ | \' -ForegroundColor Yellow
|
|
Write-Host '| $$ $$$\ $$| $$$$$$\| $$$$$$\| $$_/ $$| $$ \ | $$| $$$$$$\| $$ | $$ | $$' -ForegroundColor Yellow
|
|
Write-Host '| $$ $$\$$\$$| $$ | $$| $$ \$$| $$ $$ | $$$$ | $$| $$ | $$| $$ | $$ | $$' -ForegroundColor Yellow
|
|
Write-Host '| $$$$ \$$$$| $$__/ $$| $$ | $$$$$$\ | $$ | $$| $$__/ $$| $$_/ $$_/ $$' -ForegroundColor Yellow
|
|
Write-Host '| $$$ \$$$ \$$ $$| $$ | $$ \$$\| $$ | $$ \$$ $$ \$$ $$ $$' -ForegroundColor Yellow
|
|
Write-Host ' \$$ \$$ \$$$$$$ \$$ \$$ \$$ \$$ \$$ \$$$$$$ \$$$$$\$$$$' -ForegroundColor Yellow
|
|
Write-Host ""
|
|
}
|
|
|
|
function Show-Header {
|
|
param(
|
|
[string]$InstallVersion = $DefaultVersion
|
|
)
|
|
|
|
Show-Banner
|
|
Write-ColorOutput " $ScriptName v$ScriptVersion" $ColorInfo
|
|
if ($InstallVersion -ne "unknown") {
|
|
Write-ColorOutput " Installing Claude Code Workflow v$InstallVersion" $ColorInfo
|
|
}
|
|
Write-ColorOutput " Unified workflow system with comprehensive coordination" $ColorInfo
|
|
Write-ColorOutput "========================================================================" $ColorInfo
|
|
if ($NoBackup) {
|
|
Write-ColorOutput "WARNING: Backup disabled - existing files will be overwritten!" $ColorWarning
|
|
} else {
|
|
Write-ColorOutput "Auto-backup enabled - existing files will be backed up" $ColorSuccess
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
function Test-Prerequisites {
|
|
# Test PowerShell version
|
|
if ($PSVersionTable.PSVersion.Major -lt 5) {
|
|
Write-ColorOutput "ERROR: PowerShell 5.1 or higher is required" $ColorError
|
|
Write-ColorOutput "Current version: $($PSVersionTable.PSVersion)" $ColorError
|
|
return $false
|
|
}
|
|
|
|
# Test source files exist
|
|
$sourceDir = $PSScriptRoot
|
|
$claudeDir = Join-Path $sourceDir ".claude"
|
|
$claudeMd = Join-Path $sourceDir "CLAUDE.md"
|
|
$codexDir = Join-Path $sourceDir ".codex"
|
|
$geminiDir = Join-Path $sourceDir ".gemini"
|
|
$qwenDir = Join-Path $sourceDir ".qwen"
|
|
|
|
if (-not (Test-Path $claudeDir)) {
|
|
Write-ColorOutput "ERROR: .claude directory not found in $sourceDir" $ColorError
|
|
return $false
|
|
}
|
|
|
|
if (-not (Test-Path $claudeMd)) {
|
|
Write-ColorOutput "ERROR: CLAUDE.md file not found in $sourceDir" $ColorError
|
|
return $false
|
|
}
|
|
|
|
if (-not (Test-Path $codexDir)) {
|
|
Write-ColorOutput "ERROR: .codex directory not found in $sourceDir" $ColorError
|
|
return $false
|
|
}
|
|
|
|
if (-not (Test-Path $geminiDir)) {
|
|
Write-ColorOutput "ERROR: .gemini directory not found in $sourceDir" $ColorError
|
|
return $false
|
|
}
|
|
|
|
if (-not (Test-Path $qwenDir)) {
|
|
Write-ColorOutput "ERROR: .qwen directory not found in $sourceDir" $ColorError
|
|
return $false
|
|
}
|
|
|
|
Write-ColorOutput "Prerequisites check passed" $ColorSuccess
|
|
return $true
|
|
}
|
|
|
|
function Get-UserChoiceWithArrows {
|
|
param(
|
|
[string]$Prompt,
|
|
[string[]]$Options,
|
|
[int]$DefaultIndex = 0
|
|
)
|
|
|
|
if ($NonInteractive) {
|
|
Write-ColorOutput "Non-interactive mode: Using default '$($Options[$DefaultIndex])'" $ColorInfo
|
|
return $Options[$DefaultIndex]
|
|
}
|
|
|
|
# Test if we can use console features (interactive terminal)
|
|
$canUseConsole = $true
|
|
try {
|
|
$null = [Console]::CursorVisible
|
|
$null = $Host.UI.RawUI.ReadKey
|
|
}
|
|
catch {
|
|
$canUseConsole = $false
|
|
}
|
|
|
|
# Fallback to simple numbered menu if console not available
|
|
if (-not $canUseConsole) {
|
|
Write-ColorOutput "Arrow navigation not available in this environment. Using numbered menu." $ColorWarning
|
|
return Get-UserChoice -Prompt $Prompt -Options $Options -Default $Options[$DefaultIndex]
|
|
}
|
|
|
|
$selectedIndex = $DefaultIndex
|
|
$cursorVisible = $true
|
|
|
|
try {
|
|
$cursorVisible = [Console]::CursorVisible
|
|
[Console]::CursorVisible = $false
|
|
}
|
|
catch {
|
|
# Silently continue if cursor control fails
|
|
}
|
|
|
|
try {
|
|
Write-Host ""
|
|
Write-ColorOutput $Prompt $ColorPrompt
|
|
Write-Host ""
|
|
|
|
while ($true) {
|
|
# Display options
|
|
for ($i = 0; $i -lt $Options.Count; $i++) {
|
|
$prefix = if ($i -eq $selectedIndex) { " > " } else { " " }
|
|
$color = if ($i -eq $selectedIndex) { $ColorSuccess } else { "White" }
|
|
|
|
# Clear line and write option
|
|
Write-Host "`r$prefix$($Options[$i])".PadRight(80) -ForegroundColor $color
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host " Use " -NoNewline -ForegroundColor DarkGray
|
|
Write-Host "UP/DOWN" -NoNewline -ForegroundColor Yellow
|
|
Write-Host " arrows to navigate, " -NoNewline -ForegroundColor DarkGray
|
|
Write-Host "ENTER" -NoNewline -ForegroundColor Yellow
|
|
Write-Host " to select, or type " -NoNewline -ForegroundColor DarkGray
|
|
Write-Host "1-$($Options.Count)" -NoNewline -ForegroundColor Yellow
|
|
Write-Host "" -ForegroundColor DarkGray
|
|
|
|
# Read key
|
|
$key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
|
|
|
# Handle arrow keys
|
|
if ($key.VirtualKeyCode -eq 38) {
|
|
# Up arrow
|
|
$selectedIndex = if ($selectedIndex -gt 0) { $selectedIndex - 1 } else { $Options.Count - 1 }
|
|
}
|
|
elseif ($key.VirtualKeyCode -eq 40) {
|
|
# Down arrow
|
|
$selectedIndex = if ($selectedIndex -lt ($Options.Count - 1)) { $selectedIndex + 1 } else { 0 }
|
|
}
|
|
elseif ($key.VirtualKeyCode -eq 13) {
|
|
# Enter key
|
|
Write-Host ""
|
|
return $Options[$selectedIndex]
|
|
}
|
|
elseif ($key.Character -match '^\d$') {
|
|
# Number key
|
|
$num = [int]::Parse($key.Character)
|
|
if ($num -ge 1 -and $num -le $Options.Count) {
|
|
Write-Host ""
|
|
return $Options[$num - 1]
|
|
}
|
|
}
|
|
|
|
# Move cursor back up to redraw menu
|
|
$linesToMove = $Options.Count + 2
|
|
try {
|
|
for ($i = 0; $i -lt $linesToMove; $i++) {
|
|
[Console]::SetCursorPosition(0, [Console]::CursorTop - 1)
|
|
}
|
|
}
|
|
catch {
|
|
# If cursor positioning fails, just continue
|
|
break
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
try {
|
|
[Console]::CursorVisible = $cursorVisible
|
|
}
|
|
catch {
|
|
# Silently continue if cursor control fails
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-UserChoice {
|
|
param(
|
|
[string]$Prompt,
|
|
[string[]]$Options,
|
|
[string]$Default = $null
|
|
)
|
|
|
|
if ($NonInteractive -and $Default) {
|
|
Write-ColorOutput "Non-interactive mode: Using default '$Default'" $ColorInfo
|
|
return $Default
|
|
}
|
|
|
|
Write-ColorOutput $Prompt $ColorPrompt
|
|
for ($i = 0; $i -lt $Options.Count; $i++) {
|
|
if ($Default -and $Options[$i] -eq $Default) {
|
|
$marker = " (default)"
|
|
} else {
|
|
$marker = ""
|
|
}
|
|
Write-Host " $($i + 1). $($Options[$i])$marker"
|
|
}
|
|
|
|
do {
|
|
$input = Read-Host "Please select (1-$($Options.Count))"
|
|
if ([string]::IsNullOrWhiteSpace($input) -and $Default) {
|
|
return $Default
|
|
}
|
|
|
|
$index = $null
|
|
if ([int]::TryParse($input, [ref]$index) -and $index -ge 1 -and $index -le $Options.Count) {
|
|
return $Options[$index - 1]
|
|
}
|
|
|
|
Write-ColorOutput "Invalid selection. Please enter a number between 1 and $($Options.Count)" $ColorWarning
|
|
} while ($true)
|
|
}
|
|
|
|
function Confirm-Action {
|
|
param(
|
|
[string]$Message,
|
|
[switch]$DefaultYes
|
|
)
|
|
|
|
if ($Force) {
|
|
Write-ColorOutput "Force mode: Proceeding with '$Message'" $ColorInfo
|
|
return $true
|
|
}
|
|
|
|
if ($NonInteractive) {
|
|
if ($DefaultYes) {
|
|
$result = $true
|
|
} else {
|
|
$result = $false
|
|
}
|
|
if ($result) {
|
|
$resultText = 'Yes'
|
|
} else {
|
|
$resultText = 'No'
|
|
}
|
|
Write-ColorOutput "Non-interactive mode: $Message - $resultText" $ColorInfo
|
|
return $result
|
|
}
|
|
|
|
if ($DefaultYes) {
|
|
$defaultChar = "Y"
|
|
$prompt = "(Y/n)"
|
|
} else {
|
|
$defaultChar = "N"
|
|
$prompt = "(y/N)"
|
|
}
|
|
|
|
do {
|
|
$response = Read-Host "$Message $prompt"
|
|
if ([string]::IsNullOrWhiteSpace($response)) {
|
|
return $DefaultYes
|
|
}
|
|
|
|
switch ($response.ToLower()) {
|
|
{ $_ -in @('y', 'yes') } { return $true }
|
|
{ $_ -in @('n', 'no') } { return $false }
|
|
default {
|
|
Write-ColorOutput "Please answer 'y' or 'n'" $ColorWarning
|
|
}
|
|
}
|
|
} while ($true)
|
|
}
|
|
|
|
function Get-BackupDirectory {
|
|
param(
|
|
[string]$TargetDirectory
|
|
)
|
|
|
|
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
$backupDirName = "claude-backup-$timestamp"
|
|
$backupPath = Join-Path $TargetDirectory $backupDirName
|
|
|
|
# Ensure backup directory exists
|
|
if (-not (Test-Path $backupPath)) {
|
|
New-Item -ItemType Directory -Path $backupPath -Force | Out-Null
|
|
}
|
|
|
|
return $backupPath
|
|
}
|
|
|
|
function Backup-FileToFolder {
|
|
param(
|
|
[string]$FilePath,
|
|
[string]$BackupFolder
|
|
)
|
|
|
|
if (-not (Test-Path $FilePath)) {
|
|
return $false
|
|
}
|
|
|
|
try {
|
|
$fileName = Split-Path $FilePath -Leaf
|
|
$relativePath = ""
|
|
|
|
# Try to determine relative path structure for better organization
|
|
$fileDir = Split-Path $FilePath -Parent
|
|
if ($fileDir -match '\.claude') {
|
|
# Extract path relative to .claude directory
|
|
$claudeIndex = $fileDir.LastIndexOf('.claude')
|
|
if ($claudeIndex -ge 0) {
|
|
$relativePath = $fileDir.Substring($claudeIndex + 7) # +7 for ".claude\"
|
|
if ($relativePath.StartsWith('\')) {
|
|
$relativePath = $relativePath.Substring(1)
|
|
}
|
|
}
|
|
}
|
|
|
|
# Create subdirectory structure in backup if needed
|
|
$backupSubDir = $BackupFolder
|
|
if (-not [string]::IsNullOrEmpty($relativePath)) {
|
|
$backupSubDir = Join-Path $BackupFolder $relativePath
|
|
if (-not (Test-Path $backupSubDir)) {
|
|
New-Item -ItemType Directory -Path $backupSubDir -Force | Out-Null
|
|
}
|
|
}
|
|
|
|
$backupFilePath = Join-Path $backupSubDir $fileName
|
|
Copy-Item -Path $FilePath -Destination $backupFilePath -Force
|
|
|
|
Write-ColorOutput "Backed up: $fileName" $ColorInfo
|
|
return $true
|
|
} catch {
|
|
Write-ColorOutput "WARNING: Failed to backup file $FilePath`: $($_.Exception.Message)" $ColorWarning
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Backup-DirectoryToFolder {
|
|
param(
|
|
[string]$DirectoryPath,
|
|
[string]$BackupFolder
|
|
)
|
|
|
|
if (-not (Test-Path $DirectoryPath)) {
|
|
return $false
|
|
}
|
|
|
|
try {
|
|
$dirName = Split-Path $DirectoryPath -Leaf
|
|
$backupDirPath = Join-Path $BackupFolder $dirName
|
|
|
|
Copy-Item -Path $DirectoryPath -Destination $backupDirPath -Recurse -Force
|
|
Write-ColorOutput "Backed up directory: $dirName" $ColorInfo
|
|
return $true
|
|
} catch {
|
|
Write-ColorOutput "WARNING: Failed to backup directory $DirectoryPath`: $($_.Exception.Message)" $ColorWarning
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Copy-DirectoryRecursive {
|
|
param(
|
|
[string]$Source,
|
|
[string]$Destination
|
|
)
|
|
|
|
if (-not (Test-Path $Source)) {
|
|
throw "Source directory does not exist: $Source"
|
|
}
|
|
|
|
# Create destination directory if it doesn't exist
|
|
if (-not (Test-Path $Destination)) {
|
|
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
|
|
}
|
|
|
|
try {
|
|
# Copy all items recursively
|
|
Copy-Item -Path "$Source\*" -Destination $Destination -Recurse -Force
|
|
Write-ColorOutput "Directory copied: $Source -> $Destination" $ColorSuccess
|
|
} catch {
|
|
throw "Failed to copy directory: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Copy-FileToDestination {
|
|
param(
|
|
[string]$Source,
|
|
[string]$Destination,
|
|
[string]$Description = "file",
|
|
[string]$BackupFolder = $null
|
|
)
|
|
|
|
if (Test-Path $Destination) {
|
|
# Use BackupAll mode for automatic backup without confirmation (default behavior)
|
|
if ($BackupAll -and -not $NoBackup) {
|
|
if ($BackupFolder -and (Backup-FileToFolder -FilePath $Destination -BackupFolder $BackupFolder)) {
|
|
Write-ColorOutput "Auto-backed up: $Description" $ColorSuccess
|
|
}
|
|
Copy-Item -Path $Source -Destination $Destination -Force
|
|
Write-ColorOutput "$Description updated (with backup)" $ColorSuccess
|
|
return $true
|
|
} elseif ($NoBackup) {
|
|
# No backup mode - ask for confirmation
|
|
if (Confirm-Action "$Description already exists. Replace it? (NO BACKUP)" -DefaultYes:$false) {
|
|
Copy-Item -Path $Source -Destination $Destination -Force
|
|
Write-ColorOutput "$Description updated (no backup)" $ColorWarning
|
|
return $true
|
|
} else {
|
|
Write-ColorOutput "Skipping $Description installation" $ColorWarning
|
|
return $false
|
|
}
|
|
} elseif (Confirm-Action "$Description already exists. Replace it?" -DefaultYes:$false) {
|
|
if ($BackupFolder -and (Backup-FileToFolder -FilePath $Destination -BackupFolder $BackupFolder)) {
|
|
Write-ColorOutput "Existing $Description backed up" $ColorSuccess
|
|
}
|
|
Copy-Item -Path $Source -Destination $Destination -Force
|
|
Write-ColorOutput "$Description updated" $ColorSuccess
|
|
return $true
|
|
} else {
|
|
Write-ColorOutput "Skipping $Description installation" $ColorWarning
|
|
return $false
|
|
}
|
|
} else {
|
|
# Ensure destination directory exists
|
|
$destinationDir = Split-Path $Destination -Parent
|
|
if (-not (Test-Path $destinationDir)) {
|
|
New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
|
|
}
|
|
Copy-Item -Path $Source -Destination $Destination -Force
|
|
Write-ColorOutput "$Description installed" $ColorSuccess
|
|
return $true
|
|
}
|
|
}
|
|
|
|
function Backup-AndReplaceDirectory {
|
|
param(
|
|
[string]$Source,
|
|
[string]$Destination,
|
|
[string]$Description = "directory",
|
|
[string]$BackupFolder = $null
|
|
)
|
|
|
|
if (-not (Test-Path $Source)) {
|
|
Write-ColorOutput "WARNING: Source $Description not found: $Source" $ColorWarning
|
|
return $false
|
|
}
|
|
|
|
# Backup destination if it exists
|
|
if (Test-Path $Destination) {
|
|
Write-ColorOutput "Found existing $Description at: $Destination" $ColorInfo
|
|
|
|
# Backup entire directory if backup is enabled
|
|
if (-not $NoBackup -and $BackupFolder) {
|
|
Write-ColorOutput "Backing up entire $Description..." $ColorInfo
|
|
if (Backup-DirectoryToFolder -DirectoryPath $Destination -BackupFolder $BackupFolder) {
|
|
Write-ColorOutput "Backed up $Description to: $BackupFolder" $ColorSuccess
|
|
}
|
|
} elseif ($NoBackup) {
|
|
if (-not (Confirm-Action "Replace existing $Description without backup?" -DefaultYes:$false)) {
|
|
Write-ColorOutput "Skipping $Description installation" $ColorWarning
|
|
return $false
|
|
}
|
|
}
|
|
|
|
# Get all items from source to determine what to clear in destination
|
|
Write-ColorOutput "Clearing conflicting items in destination $Description..." $ColorInfo
|
|
$sourceItems = Get-ChildItem -Path $Source -Force
|
|
|
|
foreach ($sourceItem in $sourceItems) {
|
|
$destItemPath = Join-Path $Destination $sourceItem.Name
|
|
if (Test-Path $destItemPath) {
|
|
Write-ColorOutput "Removing existing: $($sourceItem.Name)" $ColorInfo
|
|
Remove-Item -Path $destItemPath -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
Write-ColorOutput "Cleared conflicting items in destination" $ColorSuccess
|
|
} else {
|
|
# Create destination directory if it doesn't exist
|
|
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
|
|
Write-ColorOutput "Created destination directory: $Destination" $ColorInfo
|
|
}
|
|
|
|
# Copy all items from source to destination
|
|
Write-ColorOutput "Copying $Description from $Source to $Destination..." $ColorInfo
|
|
$sourceItems = Get-ChildItem -Path $Source -Force
|
|
foreach ($item in $sourceItems) {
|
|
$destPath = Join-Path $Destination $item.Name
|
|
Copy-Item -Path $item.FullName -Destination $destPath -Recurse -Force
|
|
}
|
|
Write-ColorOutput "$Description installed successfully" $ColorSuccess
|
|
|
|
return $true
|
|
}
|
|
|
|
function Merge-DirectoryContents {
|
|
param(
|
|
[string]$Source,
|
|
[string]$Destination,
|
|
[string]$Description = "directory contents",
|
|
[string]$BackupFolder = $null
|
|
)
|
|
|
|
if (-not (Test-Path $Source)) {
|
|
Write-ColorOutput "WARNING: Source $Description not found: $Source" $ColorWarning
|
|
return $false
|
|
}
|
|
|
|
# Create destination directory if it doesn't exist
|
|
if (-not (Test-Path $Destination)) {
|
|
New-Item -ItemType Directory -Path $Destination -Force | Out-Null
|
|
Write-ColorOutput "Created destination directory: $Destination" $ColorInfo
|
|
}
|
|
|
|
# Get all items in source directory
|
|
$sourceItems = Get-ChildItem -Path $Source -Recurse -File
|
|
$mergedCount = 0
|
|
$skippedCount = 0
|
|
|
|
foreach ($item in $sourceItems) {
|
|
# Calculate relative path from source
|
|
$relativePath = $item.FullName.Substring($Source.Length + 1)
|
|
$destinationPath = Join-Path $Destination $relativePath
|
|
|
|
# Ensure destination directory exists
|
|
$destinationDir = Split-Path $destinationPath -Parent
|
|
if (-not (Test-Path $destinationDir)) {
|
|
New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
|
|
}
|
|
|
|
# Handle file merging
|
|
if (Test-Path $destinationPath) {
|
|
$fileName = Split-Path $relativePath -Leaf
|
|
# Use BackupAll mode for automatic backup without confirmation (default behavior)
|
|
if ($BackupAll -and -not $NoBackup) {
|
|
if ($BackupFolder -and (Backup-FileToFolder -FilePath $destinationPath -BackupFolder $BackupFolder)) {
|
|
Write-ColorOutput "Auto-backed up: $fileName" $ColorInfo
|
|
}
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
} elseif ($NoBackup) {
|
|
# No backup mode - ask for confirmation
|
|
if (Confirm-Action "File '$relativePath' already exists. Replace it? (NO BACKUP)" -DefaultYes:$false) {
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
} else {
|
|
Write-ColorOutput "Skipped $fileName (no backup)" $ColorWarning
|
|
$skippedCount++
|
|
}
|
|
} elseif (Confirm-Action "File '$relativePath' already exists. Replace it?" -DefaultYes:$false) {
|
|
if ($BackupFolder -and (Backup-FileToFolder -FilePath $destinationPath -BackupFolder $BackupFolder)) {
|
|
Write-ColorOutput "Backed up existing $fileName" $ColorInfo
|
|
}
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
} else {
|
|
Write-ColorOutput "Skipped $fileName" $ColorWarning
|
|
$skippedCount++
|
|
}
|
|
} else {
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
}
|
|
}
|
|
|
|
Write-ColorOutput "Merged $mergedCount files, skipped $skippedCount files" $ColorSuccess
|
|
return $true
|
|
}
|
|
|
|
function Create-VersionJson {
|
|
param(
|
|
[string]$TargetClaudeDir,
|
|
[string]$InstallationMode
|
|
)
|
|
|
|
# Determine version from source parameter (passed from install-remote.ps1)
|
|
$versionNumber = if ($SourceVersion) { $SourceVersion } else { $DefaultVersion }
|
|
$sourceBranch = if ($SourceBranch) { $SourceBranch } else { "unknown" }
|
|
$commitSha = if ($SourceCommit) { $SourceCommit } else { "unknown" }
|
|
|
|
# Create version.json content
|
|
$versionInfo = @{
|
|
version = $versionNumber
|
|
commit_sha = $commitSha
|
|
installation_mode = $InstallationMode
|
|
installation_path = $TargetClaudeDir
|
|
installation_date_utc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
source_branch = $sourceBranch
|
|
installer_version = $ScriptVersion
|
|
}
|
|
|
|
$versionJsonPath = Join-Path $TargetClaudeDir "version.json"
|
|
|
|
try {
|
|
$versionInfo | ConvertTo-Json | Out-File -FilePath $versionJsonPath -Encoding utf8 -Force
|
|
Write-ColorOutput "Created version.json: $versionNumber ($commitSha) - $InstallationMode" $ColorSuccess
|
|
return $true
|
|
} catch {
|
|
Write-ColorOutput "WARNING: Failed to create version.json: $($_.Exception.Message)" $ColorWarning
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Install-Global {
|
|
Write-ColorOutput "Installing Claude Code Workflow System globally..." $ColorInfo
|
|
|
|
# Determine user profile directory
|
|
$userProfile = [Environment]::GetFolderPath("UserProfile")
|
|
$globalClaudeDir = Join-Path $userProfile ".claude"
|
|
$globalClaudeMd = Join-Path $globalClaudeDir "CLAUDE.md"
|
|
$globalCodexDir = Join-Path $userProfile ".codex"
|
|
$globalGeminiDir = Join-Path $userProfile ".gemini"
|
|
$globalQwenDir = Join-Path $userProfile ".qwen"
|
|
|
|
Write-ColorOutput "Global installation path: $userProfile" $ColorInfo
|
|
|
|
# Source paths
|
|
$sourceDir = $PSScriptRoot
|
|
$sourceClaudeDir = Join-Path $sourceDir ".claude"
|
|
$sourceClaudeMd = Join-Path $sourceDir "CLAUDE.md"
|
|
$sourceCodexDir = Join-Path $sourceDir ".codex"
|
|
$sourceGeminiDir = Join-Path $sourceDir ".gemini"
|
|
$sourceQwenDir = Join-Path $sourceDir ".qwen"
|
|
|
|
# Create backup folder if needed (default behavior unless NoBackup is specified)
|
|
$backupFolder = $null
|
|
if (-not $NoBackup) {
|
|
if ((Test-Path $globalClaudeDir) -or (Test-Path $globalCodexDir) -or (Test-Path $globalGeminiDir) -or (Test-Path $globalQwenDir)) {
|
|
$existingFiles = @()
|
|
if (Test-Path $globalClaudeDir) {
|
|
$existingFiles += Get-ChildItem $globalClaudeDir -Recurse -File -ErrorAction SilentlyContinue
|
|
}
|
|
if (Test-Path $globalCodexDir) {
|
|
$existingFiles += Get-ChildItem $globalCodexDir -Recurse -File -ErrorAction SilentlyContinue
|
|
}
|
|
if (Test-Path $globalGeminiDir) {
|
|
$existingFiles += Get-ChildItem $globalGeminiDir -Recurse -File -ErrorAction SilentlyContinue
|
|
}
|
|
if (Test-Path $globalQwenDir) {
|
|
$existingFiles += Get-ChildItem $globalQwenDir -Recurse -File -ErrorAction SilentlyContinue
|
|
}
|
|
if (($existingFiles -and ($existingFiles | Measure-Object).Count -gt 0)) {
|
|
$backupFolder = Get-BackupDirectory -TargetDirectory $userProfile
|
|
Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo
|
|
}
|
|
} elseif (Test-Path $globalClaudeMd) {
|
|
# Create backup folder even if .claude directory doesn't exist but CLAUDE.md does
|
|
$backupFolder = Get-BackupDirectory -TargetDirectory $userProfile
|
|
Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo
|
|
}
|
|
}
|
|
|
|
# Replace .claude directory (backup → clear → copy entire folder)
|
|
Write-ColorOutput "Installing .claude directory..." $ColorInfo
|
|
$claudeInstalled = Backup-AndReplaceDirectory -Source $sourceClaudeDir -Destination $globalClaudeDir -Description ".claude directory" -BackupFolder $backupFolder
|
|
|
|
# Handle CLAUDE.md file in .claude directory
|
|
Write-ColorOutput "Installing CLAUDE.md to global .claude directory..." $ColorInfo
|
|
$claudeMdInstalled = Copy-FileToDestination -Source $sourceClaudeMd -Destination $globalClaudeMd -Description "CLAUDE.md" -BackupFolder $backupFolder
|
|
|
|
# Replace .codex directory (backup → clear → copy entire folder)
|
|
Write-ColorOutput "Installing .codex directory..." $ColorInfo
|
|
$codexInstalled = Backup-AndReplaceDirectory -Source $sourceCodexDir -Destination $globalCodexDir -Description ".codex directory" -BackupFolder $backupFolder
|
|
|
|
# Replace .gemini directory (backup → clear → copy entire folder)
|
|
Write-ColorOutput "Installing .gemini directory..." $ColorInfo
|
|
$geminiInstalled = Backup-AndReplaceDirectory -Source $sourceGeminiDir -Destination $globalGeminiDir -Description ".gemini directory" -BackupFolder $backupFolder
|
|
|
|
# Replace .qwen directory (backup → clear → copy entire folder)
|
|
Write-ColorOutput "Installing .qwen directory..." $ColorInfo
|
|
$qwenInstalled = Backup-AndReplaceDirectory -Source $sourceQwenDir -Destination $globalQwenDir -Description ".qwen directory" -BackupFolder $backupFolder
|
|
|
|
# Create version.json in global .claude directory
|
|
Write-ColorOutput "Creating version.json..." $ColorInfo
|
|
Create-VersionJson -TargetClaudeDir $globalClaudeDir -InstallationMode "Global"
|
|
|
|
if ($backupFolder -and (Test-Path $backupFolder)) {
|
|
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
|
if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) {
|
|
# Remove empty backup folder
|
|
Remove-Item -Path $backupFolder -Force
|
|
Write-ColorOutput "Removed empty backup folder" $ColorInfo
|
|
}
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
function Install-Path {
|
|
param(
|
|
[string]$TargetDirectory
|
|
)
|
|
|
|
Write-ColorOutput "Installing Claude Code Workflow System in hybrid mode..." $ColorInfo
|
|
Write-ColorOutput "Local path: $TargetDirectory" $ColorInfo
|
|
|
|
# Determine user profile directory for global files
|
|
$userProfile = [Environment]::GetFolderPath("UserProfile")
|
|
$globalClaudeDir = Join-Path $userProfile ".claude"
|
|
|
|
Write-ColorOutput "Global path: $userProfile" $ColorInfo
|
|
|
|
# Source paths
|
|
$sourceDir = $PSScriptRoot
|
|
$sourceClaudeDir = Join-Path $sourceDir ".claude"
|
|
$sourceClaudeMd = Join-Path $sourceDir "CLAUDE.md"
|
|
$sourceCodexDir = Join-Path $sourceDir ".codex"
|
|
$sourceGeminiDir = Join-Path $sourceDir ".gemini"
|
|
$sourceQwenDir = Join-Path $sourceDir ".qwen"
|
|
|
|
# Local paths - for agents, commands, output-styles, .codex, .gemini, .qwen
|
|
$localClaudeDir = Join-Path $TargetDirectory ".claude"
|
|
$localCodexDir = Join-Path $TargetDirectory ".codex"
|
|
$localGeminiDir = Join-Path $TargetDirectory ".gemini"
|
|
$localQwenDir = Join-Path $TargetDirectory ".qwen"
|
|
|
|
# Create backup folder if needed
|
|
$backupFolder = $null
|
|
if (-not $NoBackup) {
|
|
if ((Test-Path $localClaudeDir) -or (Test-Path $localCodexDir) -or (Test-Path $localGeminiDir) -or (Test-Path $localQwenDir) -or (Test-Path $globalClaudeDir)) {
|
|
$backupFolder = Get-BackupDirectory -TargetDirectory $TargetDirectory
|
|
Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo
|
|
}
|
|
}
|
|
|
|
# Create local .claude directory
|
|
if (-not (Test-Path $localClaudeDir)) {
|
|
New-Item -ItemType Directory -Path $localClaudeDir -Force | Out-Null
|
|
Write-ColorOutput "Created local .claude directory" $ColorSuccess
|
|
}
|
|
|
|
# Local folders to install (agents, commands, output-styles)
|
|
$localFolders = @("agents", "commands", "output-styles")
|
|
|
|
Write-ColorOutput "Installing local components (agents, commands, output-styles)..." $ColorInfo
|
|
foreach ($folder in $localFolders) {
|
|
$sourceFolderPath = Join-Path $sourceClaudeDir $folder
|
|
$destFolderPath = Join-Path $localClaudeDir $folder
|
|
|
|
if (Test-Path $sourceFolderPath) {
|
|
# Use new backup and replace logic for local folders
|
|
Write-ColorOutput "Installing local folder: $folder..." $ColorInfo
|
|
Backup-AndReplaceDirectory -Source $sourceFolderPath -Destination $destFolderPath -Description "$folder folder" -BackupFolder $backupFolder
|
|
Write-ColorOutput "Installed local folder: $folder" $ColorSuccess
|
|
} else {
|
|
Write-ColorOutput "WARNING: Source folder not found: $folder" $ColorWarning
|
|
}
|
|
}
|
|
|
|
# Global components - exclude local folders
|
|
Write-ColorOutput "Installing global components to $globalClaudeDir..." $ColorInfo
|
|
|
|
# Get all items from source, excluding local folders
|
|
$sourceItems = Get-ChildItem -Path $sourceClaudeDir -Recurse -File | Where-Object {
|
|
$relativePath = $_.FullName.Substring($sourceClaudeDir.Length + 1)
|
|
$topFolder = $relativePath.Split([System.IO.Path]::DirectorySeparatorChar)[0]
|
|
$topFolder -notin $localFolders
|
|
}
|
|
|
|
$mergedCount = 0
|
|
foreach ($item in $sourceItems) {
|
|
$relativePath = $item.FullName.Substring($sourceClaudeDir.Length + 1)
|
|
$destinationPath = Join-Path $globalClaudeDir $relativePath
|
|
|
|
# Ensure destination directory exists
|
|
$destinationDir = Split-Path $destinationPath -Parent
|
|
if (-not (Test-Path $destinationDir)) {
|
|
New-Item -ItemType Directory -Path $destinationDir -Force | Out-Null
|
|
}
|
|
|
|
# Handle file merging
|
|
if (Test-Path $destinationPath) {
|
|
if ($BackupAll -and -not $NoBackup) {
|
|
if ($backupFolder) {
|
|
Backup-FileToFolder -FilePath $destinationPath -BackupFolder $backupFolder
|
|
}
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
} elseif ($NoBackup) {
|
|
if (Confirm-Action "File '$relativePath' already exists in global location. Replace it? (NO BACKUP)" -DefaultYes:$false) {
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
}
|
|
} elseif (Confirm-Action "File '$relativePath' already exists in global location. Replace it?" -DefaultYes:$false) {
|
|
if ($backupFolder) {
|
|
Backup-FileToFolder -FilePath $destinationPath -BackupFolder $backupFolder
|
|
}
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
}
|
|
} else {
|
|
Copy-Item -Path $item.FullName -Destination $destinationPath -Force
|
|
$mergedCount++
|
|
}
|
|
}
|
|
|
|
Write-ColorOutput "Merged $mergedCount files to global location" $ColorSuccess
|
|
|
|
# Handle CLAUDE.md file in global .claude directory
|
|
$globalClaudeMd = Join-Path $globalClaudeDir "CLAUDE.md"
|
|
Write-ColorOutput "Installing CLAUDE.md to global .claude directory..." $ColorInfo
|
|
Copy-FileToDestination -Source $sourceClaudeMd -Destination $globalClaudeMd -Description "CLAUDE.md" -BackupFolder $backupFolder
|
|
|
|
# Replace .codex directory to local location (backup → clear → copy entire folder)
|
|
Write-ColorOutput "Installing .codex directory to local location..." $ColorInfo
|
|
$codexInstalled = Backup-AndReplaceDirectory -Source $sourceCodexDir -Destination $localCodexDir -Description ".codex directory" -BackupFolder $backupFolder
|
|
|
|
# Replace .gemini directory to local location (backup → clear → copy entire folder)
|
|
Write-ColorOutput "Installing .gemini directory to local location..." $ColorInfo
|
|
$geminiInstalled = Backup-AndReplaceDirectory -Source $sourceGeminiDir -Destination $localGeminiDir -Description ".gemini directory" -BackupFolder $backupFolder
|
|
|
|
# Replace .qwen directory to local location (backup → clear → copy entire folder)
|
|
Write-ColorOutput "Installing .qwen directory to local location..." $ColorInfo
|
|
$qwenInstalled = Backup-AndReplaceDirectory -Source $sourceQwenDir -Destination $localQwenDir -Description ".qwen directory" -BackupFolder $backupFolder
|
|
|
|
# Create version.json in local .claude directory
|
|
Write-ColorOutput "Creating version.json in local directory..." $ColorInfo
|
|
Create-VersionJson -TargetClaudeDir $localClaudeDir -InstallationMode "Path"
|
|
|
|
# Also create version.json in global .claude directory
|
|
Write-ColorOutput "Creating version.json in global directory..." $ColorInfo
|
|
Create-VersionJson -TargetClaudeDir $globalClaudeDir -InstallationMode "Global"
|
|
|
|
if ($backupFolder -and (Test-Path $backupFolder)) {
|
|
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
|
if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) {
|
|
Remove-Item -Path $backupFolder -Force
|
|
Write-ColorOutput "Removed empty backup folder" $ColorInfo
|
|
}
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
|
|
function Get-InstallationMode {
|
|
if ($InstallMode) {
|
|
Write-ColorOutput "Installation mode: $InstallMode" $ColorInfo
|
|
return $InstallMode
|
|
}
|
|
|
|
$modes = @(
|
|
"Global - Install to user profile (~/.claude/)",
|
|
"Path - Install to custom directory (partial local + global)"
|
|
)
|
|
|
|
Write-Host ""
|
|
$selection = Get-UserChoiceWithArrows -Prompt "Choose installation mode:" -Options $modes -DefaultIndex 0
|
|
|
|
if ($selection -like "Global*") {
|
|
return "Global"
|
|
} elseif ($selection -like "Path*") {
|
|
return "Path"
|
|
}
|
|
|
|
return "Global"
|
|
}
|
|
|
|
function Get-InstallationPath {
|
|
param(
|
|
[string]$Mode
|
|
)
|
|
|
|
if ($Mode -eq "Global") {
|
|
return [Environment]::GetFolderPath("UserProfile")
|
|
}
|
|
|
|
if ($TargetPath) {
|
|
if (Test-Path $TargetPath) {
|
|
return $TargetPath
|
|
}
|
|
Write-ColorOutput "WARNING: Specified target path does not exist: $TargetPath" $ColorWarning
|
|
}
|
|
|
|
# Interactive path selection
|
|
do {
|
|
Write-Host ""
|
|
Write-ColorOutput "Enter the target directory path for installation:" $ColorPrompt
|
|
Write-ColorOutput "(This will install agents, commands, output-styles locally, other files globally)" $ColorInfo
|
|
$path = Read-Host "Path"
|
|
|
|
if ([string]::IsNullOrWhiteSpace($path)) {
|
|
Write-ColorOutput "Path cannot be empty" $ColorWarning
|
|
continue
|
|
}
|
|
|
|
# Expand environment variables and relative paths
|
|
$expandedPath = [System.Environment]::ExpandEnvironmentVariables($path)
|
|
$expandedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($expandedPath)
|
|
|
|
if (Test-Path $expandedPath) {
|
|
return $expandedPath
|
|
}
|
|
|
|
Write-ColorOutput "Path does not exist: $expandedPath" $ColorWarning
|
|
if (Confirm-Action "Create this directory?" -DefaultYes) {
|
|
try {
|
|
New-Item -ItemType Directory -Path $expandedPath -Force | Out-Null
|
|
Write-ColorOutput "Directory created successfully" $ColorSuccess
|
|
return $expandedPath
|
|
} catch {
|
|
Write-ColorOutput "Failed to create directory: $($_.Exception.Message)" $ColorError
|
|
}
|
|
}
|
|
} while ($true)
|
|
}
|
|
|
|
|
|
function Show-Summary {
|
|
param(
|
|
[string]$Mode,
|
|
[string]$Path,
|
|
[bool]$Success
|
|
)
|
|
|
|
Write-Host ""
|
|
if ($Success) {
|
|
Write-ColorOutput "Installation completed successfully!" $ColorSuccess
|
|
} else {
|
|
Write-ColorOutput "Installation completed with warnings" $ColorWarning
|
|
}
|
|
|
|
Write-ColorOutput "Installation Details:" $ColorInfo
|
|
Write-Host " Mode: $Mode"
|
|
|
|
if ($Mode -eq "Path") {
|
|
Write-Host " Local Path: $Path"
|
|
Write-Host " Global Path: $([Environment]::GetFolderPath('UserProfile'))"
|
|
Write-Host " Local Components: agents, commands, output-styles, .codex, .gemini, .qwen"
|
|
Write-Host " Global Components: workflows, scripts, python_script, etc."
|
|
} else {
|
|
Write-Host " Path: $Path"
|
|
Write-Host " Global Components: .claude, .codex, .gemini, .qwen"
|
|
}
|
|
|
|
if ($NoBackup) {
|
|
Write-Host " Backup: Disabled (no backup created)"
|
|
} elseif ($BackupAll) {
|
|
Write-Host " Backup: Enabled (automatic backup of all existing files)"
|
|
} else {
|
|
Write-Host " Backup: Enabled (default behavior)"
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-ColorOutput "Next steps:" $ColorInfo
|
|
Write-Host "1. Review CLAUDE.md - Customize guidelines for your project"
|
|
Write-Host "2. Review .codex/Agent.md - Codex agent execution protocol"
|
|
Write-Host "3. Review .gemini/CLAUDE.md - Gemini agent execution protocol"
|
|
Write-Host "4. Review .qwen/QWEN.md - Qwen agent execution protocol"
|
|
Write-Host "5. Configure settings - Edit .claude/settings.local.json as needed"
|
|
Write-Host "6. Start using Claude Code with Agent workflow coordination!"
|
|
Write-Host "7. Use /workflow commands for task execution"
|
|
Write-Host "8. Use /update-memory commands for memory system management"
|
|
|
|
Write-Host ""
|
|
Write-ColorOutput "Documentation: https://github.com/catlog22/Claude-CCW" $ColorInfo
|
|
Write-ColorOutput "Features: Unified workflow system with comprehensive file output generation" $ColorInfo
|
|
}
|
|
|
|
function Main {
|
|
# Use SourceVersion parameter if provided, otherwise use default
|
|
$installVersion = if ($SourceVersion) { $SourceVersion } else { $DefaultVersion }
|
|
|
|
Show-Header -InstallVersion $installVersion
|
|
|
|
# Test prerequisites
|
|
Write-ColorOutput "Checking system requirements..." $ColorInfo
|
|
if (-not (Test-Prerequisites)) {
|
|
Write-ColorOutput "Prerequisites check failed!" $ColorError
|
|
return 1
|
|
}
|
|
|
|
try {
|
|
# Get installation mode
|
|
$mode = Get-InstallationMode
|
|
$installPath = ""
|
|
$success = $false
|
|
|
|
if ($mode -eq "Global") {
|
|
$installPath = [Environment]::GetFolderPath("UserProfile")
|
|
$result = Install-Global
|
|
$success = $result -eq $true
|
|
}
|
|
elseif ($mode -eq "Path") {
|
|
$installPath = Get-InstallationPath -Mode $mode
|
|
$result = Install-Path -TargetDirectory $installPath
|
|
$success = $result -eq $true
|
|
}
|
|
|
|
Show-Summary -Mode $mode -Path $installPath -Success ([bool]$success)
|
|
|
|
# Wait for user confirmation before exit in interactive mode
|
|
if (-not $NonInteractive) {
|
|
Write-Host ""
|
|
Write-ColorOutput "Installation completed. Press any key to exit..." $ColorPrompt
|
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
|
}
|
|
|
|
if ($success) {
|
|
return 0
|
|
} else {
|
|
return 1
|
|
}
|
|
|
|
} catch {
|
|
Write-ColorOutput "CRITICAL ERROR: $($_.Exception.Message)" $ColorError
|
|
Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" $ColorError
|
|
|
|
# Wait for user confirmation before exit in interactive mode
|
|
if (-not $NonInteractive) {
|
|
Write-Host ""
|
|
Write-ColorOutput "An error occurred. Press any key to exit..." $ColorPrompt
|
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
|
}
|
|
|
|
return 1
|
|
}
|
|
}
|
|
|
|
# Run main function
|
|
exit (Main) |