mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Enhanced installer with ASCII art banner and arrow key navigation
Major improvements: - Add colorful ASCII art banner (CLAUDE/CODE/WORKFLOW in Cyan/Green/Yellow) - Implement arrow key navigation (↑/↓) for installation mode selection - Add new Path installation mode (hybrid: local agents/commands/output-styles + global workflows/scripts) - Fix parameter type conversion error for $success variable - Improve console capability detection with graceful fallback to numbered menu - Use single-quoted strings to properly escape $ symbols in ASCII art Technical enhancements: - New Get-UserChoiceWithArrows function with keyboard input handling - New Install-Path function for hybrid installation - Enhanced Show-Banner with three-section colored ASCII art - Better error handling and stack trace output 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -48,15 +48,17 @@
|
|||||||
#>
|
#>
|
||||||
|
|
||||||
param(
|
param(
|
||||||
[ValidateSet("Global")]
|
[ValidateSet("Global", "Path")]
|
||||||
[string]$InstallMode = "Global",
|
[string]$InstallMode = "",
|
||||||
|
|
||||||
|
[string]$TargetPath = "",
|
||||||
|
|
||||||
[switch]$Force,
|
[switch]$Force,
|
||||||
|
|
||||||
[switch]$NonInteractive,
|
[switch]$NonInteractive,
|
||||||
|
|
||||||
[switch]$BackupAll,
|
[switch]$BackupAll,
|
||||||
|
|
||||||
[switch]$NoBackup
|
[switch]$NoBackup
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,13 +97,54 @@ function Write-ColorOutput {
|
|||||||
Write-Host $Message -ForegroundColor $Color
|
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 {
|
function Show-Header {
|
||||||
Write-ColorOutput "==== $ScriptName v$Version ====" $ColorInfo
|
Show-Banner
|
||||||
Write-ColorOutput "========================================================" $ColorInfo
|
Write-ColorOutput " $ScriptName v$Version" $ColorInfo
|
||||||
|
Write-ColorOutput " Unified workflow system with comprehensive coordination" $ColorInfo
|
||||||
|
Write-ColorOutput "========================================================================" $ColorInfo
|
||||||
if ($NoBackup) {
|
if ($NoBackup) {
|
||||||
Write-ColorOutput "WARNING: Backup disabled - existing files will be overwritten without backup!" $ColorWarning
|
Write-ColorOutput "WARNING: Backup disabled - existing files will be overwritten!" $ColorWarning
|
||||||
} else {
|
} else {
|
||||||
Write-ColorOutput "Auto-backup enabled - existing files will be backed up before replacement" $ColorSuccess
|
Write-ColorOutput "Auto-backup enabled - existing files will be backed up" $ColorSuccess
|
||||||
}
|
}
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
}
|
}
|
||||||
@@ -133,18 +176,130 @@ function Test-Prerequisites {
|
|||||||
return $true
|
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 {
|
function Get-UserChoice {
|
||||||
param(
|
param(
|
||||||
[string]$Prompt,
|
[string]$Prompt,
|
||||||
[string[]]$Options,
|
[string[]]$Options,
|
||||||
[string]$Default = $null
|
[string]$Default = $null
|
||||||
)
|
)
|
||||||
|
|
||||||
if ($NonInteractive -and $Default) {
|
if ($NonInteractive -and $Default) {
|
||||||
Write-ColorOutput "Non-interactive mode: Using default '$Default'" $ColorInfo
|
Write-ColorOutput "Non-interactive mode: Using default '$Default'" $ColorInfo
|
||||||
return $Default
|
return $Default
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-ColorOutput $Prompt $ColorPrompt
|
Write-ColorOutput $Prompt $ColorPrompt
|
||||||
for ($i = 0; $i -lt $Options.Count; $i++) {
|
for ($i = 0; $i -lt $Options.Count; $i++) {
|
||||||
if ($Default -and $Options[$i] -eq $Default) {
|
if ($Default -and $Options[$i] -eq $Default) {
|
||||||
@@ -154,18 +309,18 @@ function Get-UserChoice {
|
|||||||
}
|
}
|
||||||
Write-Host " $($i + 1). $($Options[$i])$marker"
|
Write-Host " $($i + 1). $($Options[$i])$marker"
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$input = Read-Host "Please select (1-$($Options.Count))"
|
$input = Read-Host "Please select (1-$($Options.Count))"
|
||||||
if ([string]::IsNullOrWhiteSpace($input) -and $Default) {
|
if ([string]::IsNullOrWhiteSpace($input) -and $Default) {
|
||||||
return $Default
|
return $Default
|
||||||
}
|
}
|
||||||
|
|
||||||
$index = $null
|
$index = $null
|
||||||
if ([int]::TryParse($input, [ref]$index) -and $index -ge 1 -and $index -le $Options.Count) {
|
if ([int]::TryParse($input, [ref]$index) -and $index -ge 1 -and $index -le $Options.Count) {
|
||||||
return $Options[$index - 1]
|
return $Options[$index - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-ColorOutput "Invalid selection. Please enter a number between 1 and $($Options.Count)" $ColorWarning
|
Write-ColorOutput "Invalid selection. Please enter a number between 1 and $($Options.Count)" $ColorWarning
|
||||||
} while ($true)
|
} while ($true)
|
||||||
}
|
}
|
||||||
@@ -457,19 +612,19 @@ function Merge-DirectoryContents {
|
|||||||
|
|
||||||
function Install-Global {
|
function Install-Global {
|
||||||
Write-ColorOutput "Installing Claude Code Workflow System globally..." $ColorInfo
|
Write-ColorOutput "Installing Claude Code Workflow System globally..." $ColorInfo
|
||||||
|
|
||||||
# Determine user profile directory
|
# Determine user profile directory
|
||||||
$userProfile = [Environment]::GetFolderPath("UserProfile")
|
$userProfile = [Environment]::GetFolderPath("UserProfile")
|
||||||
$globalClaudeDir = Join-Path $userProfile ".claude"
|
$globalClaudeDir = Join-Path $userProfile ".claude"
|
||||||
$globalClaudeMd = Join-Path $globalClaudeDir "CLAUDE.md"
|
$globalClaudeMd = Join-Path $globalClaudeDir "CLAUDE.md"
|
||||||
|
|
||||||
Write-ColorOutput "Global installation path: $userProfile" $ColorInfo
|
Write-ColorOutput "Global installation path: $userProfile" $ColorInfo
|
||||||
|
|
||||||
# Source paths
|
# Source paths
|
||||||
$sourceDir = $PSScriptRoot
|
$sourceDir = $PSScriptRoot
|
||||||
$sourceClaudeDir = Join-Path $sourceDir ".claude"
|
$sourceClaudeDir = Join-Path $sourceDir ".claude"
|
||||||
$sourceClaudeMd = Join-Path $sourceDir "CLAUDE.md"
|
$sourceClaudeMd = Join-Path $sourceDir "CLAUDE.md"
|
||||||
|
|
||||||
# Create backup folder if needed (default behavior unless NoBackup is specified)
|
# Create backup folder if needed (default behavior unless NoBackup is specified)
|
||||||
$backupFolder = $null
|
$backupFolder = $null
|
||||||
if (-not $NoBackup) {
|
if (-not $NoBackup) {
|
||||||
@@ -485,15 +640,15 @@ function Install-Global {
|
|||||||
Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo
|
Write-ColorOutput "Backup folder created: $backupFolder" $ColorInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Merge .claude directory contents (don't replace entire directory)
|
# Merge .claude directory contents (don't replace entire directory)
|
||||||
Write-ColorOutput "Merging .claude directory contents..." $ColorInfo
|
Write-ColorOutput "Merging .claude directory contents..." $ColorInfo
|
||||||
$claudeMerged = Merge-DirectoryContents -Source $sourceClaudeDir -Destination $globalClaudeDir -Description ".claude directory contents" -BackupFolder $backupFolder
|
$claudeMerged = Merge-DirectoryContents -Source $sourceClaudeDir -Destination $globalClaudeDir -Description ".claude directory contents" -BackupFolder $backupFolder
|
||||||
|
|
||||||
# Handle CLAUDE.md file in .claude directory
|
# Handle CLAUDE.md file in .claude directory
|
||||||
Write-ColorOutput "Installing CLAUDE.md to global .claude directory..." $ColorInfo
|
Write-ColorOutput "Installing CLAUDE.md to global .claude directory..." $ColorInfo
|
||||||
$claudeMdInstalled = Copy-FileToDestination -Source $sourceClaudeMd -Destination $globalClaudeMd -Description "CLAUDE.md" -BackupFolder $backupFolder
|
$claudeMdInstalled = Copy-FileToDestination -Source $sourceClaudeMd -Destination $globalClaudeMd -Description "CLAUDE.md" -BackupFolder $backupFolder
|
||||||
|
|
||||||
if ($backupFolder -and (Test-Path $backupFolder)) {
|
if ($backupFolder -and (Test-Path $backupFolder)) {
|
||||||
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
$backupFiles = Get-ChildItem $backupFolder -Recurse -File -ErrorAction SilentlyContinue
|
||||||
if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) {
|
if (-not $backupFiles -or ($backupFiles | Measure-Object).Count -eq 0) {
|
||||||
@@ -502,16 +657,207 @@ function Install-Global {
|
|||||||
Write-ColorOutput "Removed empty backup folder" $ColorInfo
|
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"
|
||||||
|
|
||||||
|
# Local paths - only for agents, commands, output-styles
|
||||||
|
$localClaudeDir = Join-Path $TargetDirectory ".claude"
|
||||||
|
|
||||||
|
# Create backup folder if needed
|
||||||
|
$backupFolder = $null
|
||||||
|
if (-not $NoBackup) {
|
||||||
|
if ((Test-Path $localClaudeDir) -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) {
|
||||||
|
if (Test-Path $destFolderPath) {
|
||||||
|
if ($backupFolder) {
|
||||||
|
Backup-DirectoryToFolder -DirectoryPath $destFolderPath -BackupFolder $backupFolder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Copy-DirectoryRecursive -Source $sourceFolderPath -Destination $destFolderPath
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
return $true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Get-InstallationMode {
|
function Get-InstallationMode {
|
||||||
Write-ColorOutput "Installation mode: Global (installing to user profile ~/.claude/)" $ColorInfo
|
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"
|
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 {
|
function Show-Summary {
|
||||||
param(
|
param(
|
||||||
@@ -519,17 +865,26 @@ function Show-Summary {
|
|||||||
[string]$Path,
|
[string]$Path,
|
||||||
[bool]$Success
|
[bool]$Success
|
||||||
)
|
)
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
if ($Success) {
|
if ($Success) {
|
||||||
Write-ColorOutput "Installation completed successfully!" $ColorSuccess
|
Write-ColorOutput "Installation completed successfully!" $ColorSuccess
|
||||||
} else {
|
} else {
|
||||||
Write-ColorOutput "Installation completed with warnings" $ColorWarning
|
Write-ColorOutput "Installation completed with warnings" $ColorWarning
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-ColorOutput "Installation Details:" $ColorInfo
|
Write-ColorOutput "Installation Details:" $ColorInfo
|
||||||
Write-Host " Mode: $Mode"
|
Write-Host " Mode: $Mode"
|
||||||
Write-Host " Path: $Path"
|
|
||||||
|
if ($Mode -eq "Path") {
|
||||||
|
Write-Host " Local Path: $Path"
|
||||||
|
Write-Host " Global Path: $([Environment]::GetFolderPath('UserProfile'))"
|
||||||
|
Write-Host " Local Components: agents, commands, output-styles"
|
||||||
|
Write-Host " Global Components: workflows, scripts, python_script, etc."
|
||||||
|
} else {
|
||||||
|
Write-Host " Path: $Path"
|
||||||
|
}
|
||||||
|
|
||||||
if ($NoBackup) {
|
if ($NoBackup) {
|
||||||
Write-Host " Backup: Disabled (no backup created)"
|
Write-Host " Backup: Disabled (no backup created)"
|
||||||
} elseif ($BackupAll) {
|
} elseif ($BackupAll) {
|
||||||
@@ -537,7 +892,7 @@ function Show-Summary {
|
|||||||
} else {
|
} else {
|
||||||
Write-Host " Backup: Enabled (default behavior)"
|
Write-Host " Backup: Enabled (default behavior)"
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-ColorOutput "Next steps:" $ColorInfo
|
Write-ColorOutput "Next steps:" $ColorInfo
|
||||||
Write-Host "1. Review CLAUDE.md - Customize guidelines for your project"
|
Write-Host "1. Review CLAUDE.md - Customize guidelines for your project"
|
||||||
@@ -545,7 +900,7 @@ function Show-Summary {
|
|||||||
Write-Host "3. Start using Claude Code with Agent workflow coordination!"
|
Write-Host "3. Start using Claude Code with Agent workflow coordination!"
|
||||||
Write-Host "4. Use /workflow commands for task execution"
|
Write-Host "4. Use /workflow commands for task execution"
|
||||||
Write-Host "5. Use /update-memory commands for memory system management"
|
Write-Host "5. Use /update-memory commands for memory system management"
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-ColorOutput "Documentation: https://github.com/catlog22/Claude-CCW" $ColorInfo
|
Write-ColorOutput "Documentation: https://github.com/catlog22/Claude-CCW" $ColorInfo
|
||||||
Write-ColorOutput "Features: Unified workflow system with comprehensive file output generation" $ColorInfo
|
Write-ColorOutput "Features: Unified workflow system with comprehensive file output generation" $ColorInfo
|
||||||
@@ -553,48 +908,57 @@ function Show-Summary {
|
|||||||
|
|
||||||
function Main {
|
function Main {
|
||||||
Show-Header
|
Show-Header
|
||||||
|
|
||||||
# Test prerequisites
|
# Test prerequisites
|
||||||
Write-ColorOutput "Checking system requirements..." $ColorInfo
|
Write-ColorOutput "Checking system requirements..." $ColorInfo
|
||||||
if (-not (Test-Prerequisites)) {
|
if (-not (Test-Prerequisites)) {
|
||||||
Write-ColorOutput "Prerequisites check failed!" $ColorError
|
Write-ColorOutput "Prerequisites check failed!" $ColorError
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
# Get installation mode
|
# Get installation mode
|
||||||
$mode = Get-InstallationMode
|
$mode = Get-InstallationMode
|
||||||
$installPath = ""
|
$installPath = ""
|
||||||
$success = $false
|
$success = $false
|
||||||
|
|
||||||
$installPath = [Environment]::GetFolderPath("UserProfile")
|
if ($mode -eq "Global") {
|
||||||
$success = Install-Global
|
$installPath = [Environment]::GetFolderPath("UserProfile")
|
||||||
|
$result = Install-Global
|
||||||
Show-Summary -Mode $mode -Path $installPath -Success $success
|
$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
|
# Wait for user confirmation before exit in interactive mode
|
||||||
if (-not $NonInteractive) {
|
if (-not $NonInteractive) {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-ColorOutput "Installation completed. Press any key to exit..." $ColorPrompt
|
Write-ColorOutput "Installation completed. Press any key to exit..." $ColorPrompt
|
||||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($success) {
|
if ($success) {
|
||||||
return 0
|
return 0
|
||||||
} else {
|
} else {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
Write-ColorOutput "CRITICAL ERROR: $($_.Exception.Message)" $ColorError
|
Write-ColorOutput "CRITICAL ERROR: $($_.Exception.Message)" $ColorError
|
||||||
|
Write-ColorOutput "Stack trace: $($_.ScriptStackTrace)" $ColorError
|
||||||
|
|
||||||
# Wait for user confirmation before exit in interactive mode
|
# Wait for user confirmation before exit in interactive mode
|
||||||
if (-not $NonInteractive) {
|
if (-not $NonInteractive) {
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
Write-ColorOutput "An error occurred. Press any key to exit..." $ColorPrompt
|
Write-ColorOutput "An error occurred. Press any key to exit..." $ColorPrompt
|
||||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user