From c28095cb700fe49222d321a7161a2d72e5388695 Mon Sep 17 00:00:00 2001 From: TachiKuma Date: Thu, 8 Jan 2026 23:17:49 +0800 Subject: [PATCH] feat: Add Windows native installation support with install.ps1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive Windows installation script with automated setup, PATH configuration, and skills/commands deployment. Enhanced cca.ps1 with improved hook installation and error handling. New Features: - Windows automated installation script (install.ps1) * Support for install/uninstall/help commands * Automatic PATH configuration (user-level environment variable) * Auto-install 10 skills and 10 commands to ~/.claude/ * Version injection (git/env/GitHub API fallback) * Wrapper files creation (cca.bat, cca.cmd) * roles.json initialization * UTF-8 encoding support for Chinese/emoji output * ASCII-only output ([OK], [SKIP]) to avoid encoding issues Enhanced cca.ps1: - Improved hook installation with duplicate detection - Enhanced error handling in Write-Installations - Better hook script presence detection - More informative error messages Documentation Updates: - README.md: Added Windows installation instructions (English) * Method 1: Automated installation (recommended) * Method 2: Manual installation * Uninstallation guide * Troubleshooting section - README_zh.md: Added Windows installation instructions (Chinese) * Complete Chinese installation guide * Consistent structure with English version Bug Fixes: - Fixed Unicode character encoding issues (√ → [OK], × → [SKIP]) - Fixed PowerShell encoding problems in Windows environments - Fixed empty installations file handling - Fixed hook script copy detection Installation: powershell -ExecutionPolicy Bypass -File .\install.ps1 install Uninstallation: .\install.ps1 uninstall Files Changed: - New: install.ps1 (458 lines) - Modified: README.md (+50, -12) - Modified: README_zh.md (+61, -2) - Modified: cca.ps1 (+178, -13) Total: +747 lines, -27 lines --- README.md | 62 +++++-- README_zh.md | 63 ++++++- cca.ps1 | 191 +++++++++++++++++++-- install.ps1 | 458 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 747 insertions(+), 27 deletions(-) create mode 100644 install.ps1 diff --git a/README.md b/README.md index ff910f2..3d57807 100644 --- a/README.md +++ b/README.md @@ -81,23 +81,61 @@ cd claude_code_autoflow ### Windows (PowerShell) -**Prerequisites**: PowerShell 5.1+ (Windows 10+) +**Method 1: Automated Installation (Recommended)** -**Installation**: -```powershell -git clone https://github.com/bfly123/claude_code_autoflow.git -cd claude_code_autoflow -# Copy cca.ps1 to your PATH or run directly -Copy-Item cca.ps1 $env:LOCALAPPDATA\Microsoft\WindowsApps\cca.ps1 -``` +1. Clone the repository: + ```powershell + git clone https://github.com/bfly123/claude_code_autoflow.git + cd claude_code_autoflow + ``` + +2. Run the installation script: + ```powershell + powershell -ExecutionPolicy Bypass -File .\install.ps1 install + ``` + + Or simply: + ```powershell + .\install.ps1 install + ``` + +3. Restart your terminal or refresh PATH: + ```powershell + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","User") + ``` + +4. Verify installation: + ```powershell + cca --version + ``` + +**Method 2: Manual Installation** + +1. Copy `cca.ps1` to a directory in your PATH: + ```powershell + Copy-Item cca.ps1 $env:LOCALAPPDATA\Microsoft\WindowsApps\cca.ps1 + ``` + +2. Manually install skills and commands to `~\.claude\` + +**Uninstallation** -**Usage**: ```powershell -cca.ps1 [options] -# Or if in PATH: -cca [options] +powershell -ExecutionPolicy Bypass -File .\install.ps1 uninstall ``` +**Troubleshooting** + +- If you encounter "execution policy" errors, run: + ```powershell + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + ``` + +- If `cca` command is not found after installation, restart your terminal or manually refresh PATH: + ```powershell + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","User") + ";" + [System.Environment]::GetEnvironmentVariable("Path","Machine") + ``` + ## 📖 Usage ### Prerequisites: Start Codex Session diff --git a/README_zh.md b/README_zh.md index 8b91b0a..d77a814 100644 --- a/README_zh.md +++ b/README_zh.md @@ -105,12 +105,71 @@ cd claude_code_bridge ``` ### 3. 安装 cca (AutoFlow) + +**Linux/macOS:** ```bash -git clone https://github.com/bfly123/claude-autoflow.git -cd claude-autoflow +git clone https://github.com/bfly123/claude_code_autoflow.git +cd claude_code_autoflow ./install.sh install ``` +**Windows:** + +**方法 1:自动安装(推荐)** + +1. 克隆仓库: + ```powershell + git clone https://github.com/bfly123/claude_code_autoflow.git + cd claude_code_autoflow + ``` + +2. 运行安装脚本: + ```powershell + powershell -ExecutionPolicy Bypass -File .\install.ps1 install + ``` + + 或者直接运行: + ```powershell + .\install.ps1 install + ``` + +3. 重启终端或刷新 PATH: + ```powershell + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","User") + ``` + +4. 验证安装: + ```powershell + cca --version + ``` + +**方法 2:手动安装** + +1. 将 `cca.ps1` 复制到 PATH 中的目录: + ```powershell + Copy-Item cca.ps1 $env:LOCALAPPDATA\Microsoft\WindowsApps\cca.ps1 + ``` + +2. 手动安装 skills 和 commands 到 `~\.claude\` + +**卸载** + +```powershell +powershell -ExecutionPolicy Bypass -File .\install.ps1 uninstall +``` + +**故障排除** + +- 如果遇到"执行策略"错误,运行: + ```powershell + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + ``` + +- 如果安装后找不到 `cca` 命令,重启终端或手动刷新 PATH: + ```powershell + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","User") + ";" + [System.Environment]::GetEnvironmentVariable("Path","Machine") + ``` + ## 📖 使用指南 ### CLI 管理 diff --git a/cca.ps1 b/cca.ps1 index 047a60c..9dc6200 100644 --- a/cca.ps1 +++ b/cca.ps1 @@ -261,9 +261,10 @@ function Read-Installations { } function Write-Installations { - param([Parameter(Mandatory = $true)][object[]]$Records) + param([Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]]$Records) $normalized = @($Records | Select-Object Path, Type, InstallDate) $lines = $normalized | ConvertTo-Csv -NoTypeInformation + if (-not $lines -or $lines.Count -eq 0) { $lines = @('Path,Type,InstallDate') } Write-AllLinesUtf8NoBom -Path $INSTALLATIONS_FILE -Lines $lines } @@ -668,14 +669,25 @@ function Ensure-HookInstalled { } try { New-Item -ItemType Directory -Path $binDir -Force | Out-Null } catch { } + $copyNeeded = $true try { - Copy-Item -LiteralPath $srcPs1 -Destination $dstPs1 -Force + if ((Test-Path -LiteralPath $dstPs1) -and ((Resolve-Path -LiteralPath $srcPs1).ProviderPath -eq (Resolve-Path -LiteralPath $dstPs1).ProviderPath)) { + $copyNeeded = $false + } + } catch { } + + try { + if ($copyNeeded) { + Copy-Item -LiteralPath $srcPs1 -Destination $dstPs1 -Force + } else { + Write-Info ("Hook script already present: {0}" -f $dstPs1) + } $cmd = "@echo off`r`n" + "powershell -NoProfile -ExecutionPolicy Bypass -File `"%~dp0cca-roles-hook.ps1`" %*`r`n" Write-AllTextUtf8NoBom -Path $dstCmd -Text $cmd Write-Info ("Installed hook: {0}" -f $dstCmd) } catch { - Write-Warn ("Failed to install hook to: {0}" -f $binDir) + Write-Warn ("Failed to install hook to: {0} ({1})" -f $binDir, $_.Exception.Message) } } @@ -685,32 +697,185 @@ function Ensure-ClaudeSettingsHook { $settingsPath = Join-PathSafe $claudeDir 'settings.json' try { New-Item -ItemType Directory -Path $claudeDir -Force | Out-Null } catch { } - $hookObj = @{ matcher = '.*'; commands = @('cca-roles-hook') } + $hookCommand = 'cca-roles-hook' $data = @{} if (Test-Path -LiteralPath $settingsPath) { try { $raw = Get-Content -LiteralPath $settingsPath -Raw -ErrorAction Stop $parsed = $raw | ConvertFrom-Json -ErrorAction Stop - if ($parsed) { $data = $parsed } + if ($parsed -and ($parsed -is [System.Collections.IDictionary] -or $parsed -is [PSCustomObject])) { + $data = $parsed + } else { + throw 'settings.json must be a JSON object' + } } catch { Write-Warn ("Invalid JSON (skipping): {0}" -f $settingsPath) return } } - if (-not $data.hooks) { $data | Add-Member -NotePropertyName hooks -NotePropertyValue (@{}) -Force } - if (-not $data.hooks.PreToolUse) { $data.hooks.PreToolUse = @() } - $exists = $false - foreach ($e in $data.hooks.PreToolUse) { + $getProp = { + param($Obj, [string]$Name) + if ($null -eq $Obj -or -not $Name) { return $null } + if ($Obj -is [System.Collections.IDictionary]) { + try { + if ($Obj.Contains($Name)) { return $Obj[$Name] } + } catch { + try { + if ($Obj.ContainsKey($Name)) { return $Obj[$Name] } + } catch { } + } + return $null + } try { - if ($e.commands -and ($e.commands -contains 'cca-roles-hook')) { $exists = $true; break } + $prop = $Obj.PSObject.Properties[$Name] + if ($prop) { return $prop.Value } } catch { } + return $null + } + + $setProp = { + param($Obj, [string]$Name, $Value) + if ($null -eq $Obj -or -not $Name) { return } + if ($Obj -is [System.Collections.IDictionary]) { + $Obj[$Name] = $Value + return + } + try { + $Obj | Add-Member -NotePropertyName $Name -NotePropertyValue $Value -Force + } catch { + try { $Obj.$Name = $Value } catch { } + } + } + + $hasProp = { + param($Obj, [string]$Name) + if ($null -eq $Obj -or -not $Name) { return $false } + if ($Obj -is [System.Collections.IDictionary]) { + try { if ($Obj.Contains($Name)) { return $true } } catch { + try { if ($Obj.ContainsKey($Name)) { return $true } } catch { } + } + return $false + } + try { + $prop = $Obj.PSObject.Properties[$Name] + return $null -ne $prop + } catch { return $false } + } + + $matcherToString = { + param($Matcher) + if ($Matcher -is [string] -and $Matcher.Trim()) { return $Matcher.Trim() } + $tools = & $getProp $Matcher 'tools' + if ($tools -is [System.Collections.IEnumerable]) { + foreach ($t in $tools) { + if ($t -is [string] -and $t.Trim()) { return $t.Trim() } + } + } + $tool = & $getProp $Matcher 'tool' + if ($tool -is [string] -and $tool.Trim()) { return $tool.Trim() } + return '.*' + } + + $migrateLegacy = { + param($Entry) + $matcherValue = & $matcherToString (& $getProp $Entry 'matcher') + $cmds = & $getProp $Entry 'commands' + $hooksArr = New-Object System.Collections.ArrayList + if ($cmds -is [System.Collections.IEnumerable]) { + foreach ($cmd in $cmds) { + if ($cmd -is [string] -and $cmd.Trim()) { + [void]$hooksArr.Add([ordered]@{ type = 'command'; command = $cmd.Trim() }) + } + } + } + if ($hooksArr.Count -eq 0) { + [void]$hooksArr.Add([ordered]@{ type = 'command'; command = $hookCommand }) + } + $entryObj = [ordered]@{ + matcher = $matcherValue + hooks = $hooksArr + } + return $entryObj + } + + $containsHook = { + param($Entry) + $hooks = & $getProp $Entry 'hooks' + if (-not $hooks) { return $false } + foreach ($h in $hooks) { + $cmd = & $getProp $h 'command' + $type = & $getProp $h 'type' + if ([string]::Equals($type, 'command', [System.StringComparison]::OrdinalIgnoreCase) -and + [string]::Equals($cmd, $hookCommand, [System.StringComparison]::OrdinalIgnoreCase)) { + return $true + } + } + return $false + } + + $changed = $false + $hooksContainer = & $getProp $data 'hooks' + if (-not ($hooksContainer -is [System.Collections.IDictionary] -or $hooksContainer -is [PSCustomObject])) { + $hooksContainer = @{} + & $setProp $data 'hooks' $hooksContainer + $changed = $true + } + + $preHooks = & $getProp $hooksContainer 'PreToolUse' + $preList = $null + if ($preHooks -is [System.Collections.ArrayList]) { + $preList = $preHooks + } elseif ($preHooks -is [System.Collections.IEnumerable]) { + $preList = New-Object System.Collections.ArrayList + foreach ($item in $preHooks) { [void]$preList.Add($item) } + } else { + $preList = New-Object System.Collections.ArrayList + } + if ($preHooks -ne $preList) { + & $setProp $hooksContainer 'PreToolUse' $preList + if (-not $preHooks) { $changed = $true } + } + + $hookExists = $false + for ($i = 0; $i -lt $preList.Count; $i++) { + $entry = $preList[$i] + $isObject = $entry -is [System.Collections.IDictionary] -or $entry -is [PSCustomObject] + if (-not $isObject) { continue } + + if (& $hasProp $entry 'commands') { + $preList[$i] = & $migrateLegacy $entry + $entry = $preList[$i] + $changed = $true + } + + if (& $containsHook $entry) { + $hookExists = $true + $matcherValue = & $getProp $entry 'matcher' + $normalizedMatcher = & $matcherToString $matcherValue + if ($normalizedMatcher -ne $matcherValue) { + & $setProp $entry 'matcher' $normalizedMatcher + $changed = $true + } + } + } + + if (-not $hookExists) { + $newEntry = [ordered]@{ + matcher = '.*' + hooks = @( + [ordered]@{ type = 'command'; command = $hookCommand } + ) + } + [void]$preList.Add($newEntry) + $changed = $true } - if (-not $exists) { $data.hooks.PreToolUse += $hookObj } try { - $json = $data | ConvertTo-Json -Depth 16 - Write-AllTextUtf8NoBom -Path $settingsPath -Text ($json + "`n") + if ($changed -or -not (Test-Path -LiteralPath $settingsPath)) { + $json = $data | ConvertTo-Json -Depth 32 + Write-AllTextUtf8NoBom -Path $settingsPath -Text ($json + "`n") + } Write-Info ("Configured PreToolUse hook: {0}" -f $settingsPath) } catch { Write-Warn ("Failed to update .claude/settings.json: {0}" -f $settingsPath) diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..c656630 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,458 @@ +#Requires -Version 5.1 + +[CmdletBinding()] +param( + [Parameter(Position=0)] + [ValidateSet('install', 'uninstall', 'help')] + [string]$Action = 'help' +) + +# UTF-8 encoding initialization (PowerShell 5.1 compatibility) +try { + $script:utf8NoBom = [System.Text.UTF8Encoding]::new($false) +} catch { + $script:utf8NoBom = [System.Text.Encoding]::UTF8 +} +try { $OutputEncoding = $script:utf8NoBom } catch {} +try { [Console]::OutputEncoding = $script:utf8NoBom } catch {} +try { [Console]::InputEncoding = $script:utf8NoBom } catch {} +try { chcp 65001 | Out-Null } catch {} +$ErrorActionPreference = 'Stop' + +# install.ps1 - CCA Windows Installation Script +# Automated installation for Claude Code AutoFlow on Windows + +# Global Variables +$INSTALL_DIR = "$env:LOCALAPPDATA\cca" +$CLAUDE_DIR = "$env:USERPROFILE\.claude" +$CONFIG_DIR = "$env:APPDATA\cca" +$SCRIPT_DIR = $PSScriptRoot + +$AUTOFLOW_SKILLS = @('tr', 'tp', 'dual-design', 'file-op', 'ask-codex', 'ask-gemini', 'roles', 'review', 'mode-switch', 'docs') +$AUTOFLOW_COMMANDS = @('tr.md', 'tp.md', 'dual-design.md', 'file-op.md', 'ask-codex.md', 'ask-gemini.md', 'roles.md', 'review.md', 'mode-switch.md', 'auto.md') + +# Helper Functions +function Write-Info { param([string]$Message) Write-Host "[+] $Message" -ForegroundColor Green } +function Write-Warn { param([string]$Message) Write-Host "[!] $Message" -ForegroundColor Yellow } +function Write-Err { param([string]$Message) Write-Host "[-] $Message" -ForegroundColor Red } +function Write-Blue { param([string]$Message) Write-Host "[*] $Message" -ForegroundColor Cyan } + +function Write-AllTextUtf8NoBom { + param([Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$Text) + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path, $Text, $utf8NoBom) +} + +function Show-Help { +@" +CCA Windows Installation Script + +Usage: + .\install.ps1 install Install or update CCA + .\install.ps1 uninstall Uninstall CCA + .\install.ps1 help Show this help + +Installation Directories: + Install Dir: $INSTALL_DIR + Config Dir: $CONFIG_DIR + Claude Dir: $CLAUDE_DIR + +Examples: + # Install CCA + powershell -ExecutionPolicy Bypass -File .\install.ps1 install + + # Or simply + .\install.ps1 install + + # Uninstall CCA + .\install.ps1 uninstall +"@ | Write-Host +} + +function Add-ToPath { + param([string]$Directory) + + # Read current user PATH + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + + # Check if already exists + $paths = $userPath -split ';' | Where-Object { $_ } + if ($paths -contains $Directory) { + Write-Warn "Directory already in PATH: $Directory" + return + } + + # Add to PATH + $newPath = if ($userPath) { "$userPath;$Directory" } else { $Directory } + [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') + + # Update current session + $env:Path = "$env:Path;$Directory" + + Write-Info "Added to PATH: $Directory" +} + +function Remove-FromPath { + param([string]$Directory) + + # Read current user PATH + $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') + + # Remove target directory + $paths = $userPath -split ';' | Where-Object { $_ -and $_ -ne $Directory } + $newPath = $paths -join ';' + + [Environment]::SetEnvironmentVariable('Path', $newPath, 'User') + + # Update current session + $env:Path = ($env:Path -split ';' | Where-Object { $_ -ne $Directory }) -join ';' + + Write-Info "Removed from PATH: $Directory" +} + +function Get-VersionInfo { + $version = @{ + Version = "1.7.0" + Commit = "unknown" + Date = (Get-Date -Format "yyyy-MM-dd") + } + + # Method 1: From git + if (Get-Command git -ErrorAction SilentlyContinue) { + try { + Push-Location $SCRIPT_DIR + $gitCommit = git rev-parse --short HEAD 2>$null + $gitDate = git log -1 --format=%cd --date=short 2>$null + + if ($gitCommit) { + $version.Commit = $gitCommit + if ($gitDate) { $version.Date = $gitDate } + } + Pop-Location + } catch { + Pop-Location + } + } + + # Method 2: From environment variables + if ($env:CCA_VERSION) { $version.Version = $env:CCA_VERSION } + if ($env:CCA_COMMIT) { $version.Commit = $env:CCA_COMMIT } + if ($env:CCA_DATE) { $version.Date = $env:CCA_DATE } + + # Method 3: From GitHub API (fallback) + if ($version.Commit -eq "unknown") { + try { + $apiUrl = "https://api.github.com/repos/TachiKuma/claude_code_autoflow/commits/main" + $ProgressPreference = 'SilentlyContinue' + $response = Invoke-RestMethod -Uri $apiUrl -Headers @{ 'User-Agent' = 'cca-installer' } -ErrorAction Stop + if ($response.sha) { + $version.Commit = $response.sha.Substring(0, 7) + if ($response.commit.committer.date) { + $version.Date = $response.commit.committer.date.Substring(0, 10) + } + } + } catch { + # Silent fail, use defaults + } + } + + return $version +} + +function Inject-Version { + param( + [string]$SourceFile, + [string]$TargetFile + ) + + if (-not (Test-Path $SourceFile)) { + Write-Warn "Source file not found: $SourceFile" + return + } + + # Get version info + $versionInfo = Get-VersionInfo + + # Read source file + $content = Get-Content $SourceFile -Raw -Encoding UTF8 + + # Replace version placeholders + $content = $content -replace '\$VERSION\s*=\s*''[^'']*''', "`$VERSION = '$($versionInfo.Version)'" + $content = $content -replace '\$GIT_COMMIT\s*=\s*''[^'']*''', "`$GIT_COMMIT = '$($versionInfo.Commit)'" + $content = $content -replace '\$GIT_DATE\s*=\s*''[^'']*''', "`$GIT_DATE = '$($versionInfo.Date)'" + + # Write to target file + Write-AllTextUtf8NoBom -Path $TargetFile -Text $content + + Write-Info "Version injected: $($versionInfo.Version) ($($versionInfo.Commit))" +} + +function Create-WrapperFiles { + $batContent = @" +@echo off +setlocal enabledelayedexpansion + +REM Get script directory +set "SCRIPT_DIR=%~dp0" + +REM Call PowerShell to execute cca.ps1 +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%SCRIPT_DIR%cca.ps1" %* + +REM Pass exit code +exit /b %ERRORLEVEL% +"@ + + $batPath = Join-Path $INSTALL_DIR "cca.bat" + $cmdPath = Join-Path $INSTALL_DIR "cca.cmd" + + Write-AllTextUtf8NoBom -Path $batPath -Text $batContent + Write-AllTextUtf8NoBom -Path $cmdPath -Text $batContent + + Write-Info "Created wrapper: cca.bat" + Write-Info "Created wrapper: cca.cmd" +} + +function Install-Skills { + $skillsSource = Join-Path $SCRIPT_DIR "claude_source\skills" + $skillsTarget = Join-Path $CLAUDE_DIR "skills" + + if (-not (Test-Path $skillsSource)) { + Write-Warn "Skills source directory not found: $skillsSource" + return + } + + # Create target directory + New-Item -ItemType Directory -Path $skillsTarget -Force | Out-Null + + $installedCount = 0 + foreach ($skill in $AUTOFLOW_SKILLS) { + $sourcePath = Join-Path $skillsSource $skill + $targetPath = Join-Path $skillsTarget $skill + + if (Test-Path $sourcePath) { + # Remove existing and copy + if (Test-Path $targetPath) { + Remove-Item -Path $targetPath -Recurse -Force -ErrorAction SilentlyContinue + } + Copy-Item -Path $sourcePath -Destination $targetPath -Recurse -Force + $installedCount++ + Write-Host " [OK] $skill" -ForegroundColor Green + } else { + Write-Warn " [SKIP] $skill (source not found)" + } + } + + Write-Blue "Installed $installedCount skills" +} + +function Install-Commands { + $commandsSource = Join-Path $SCRIPT_DIR "claude_source\commands" + $commandsTarget = Join-Path $CLAUDE_DIR "commands" + + if (-not (Test-Path $commandsSource)) { + Write-Warn "Commands source directory not found: $commandsSource" + return + } + + # Create target directory + New-Item -ItemType Directory -Path $commandsTarget -Force | Out-Null + + $installedCount = 0 + foreach ($command in $AUTOFLOW_COMMANDS) { + $sourcePath = Join-Path $commandsSource $command + $targetPath = Join-Path $commandsTarget $command + + if (Test-Path $sourcePath) { + Copy-Item -Path $sourcePath -Destination $targetPath -Force + $installedCount++ + Write-Host " [OK] $command" -ForegroundColor Green + } else { + Write-Warn " [SKIP] $command (source not found)" + } + } + + Write-Blue "Installed $installedCount commands" +} + +function Initialize-RolesConfig { + $configPath = Join-Path $CONFIG_DIR "roles.json" + $templatePath = Join-Path $SCRIPT_DIR "claude_source\templates\roles.json" + + # Create config directory + New-Item -ItemType Directory -Path $CONFIG_DIR -Force | Out-Null + + # Check if config already exists + if (Test-Path $configPath) { + Write-Warn "System roles.json already exists: $configPath" + $overwrite = Read-Host "Overwrite? (y/N)" + if ($overwrite -ne 'y' -and $overwrite -ne 'Y') { + Write-Blue "Keeping existing configuration" + return + } + } + + # Copy template or create default + if (Test-Path $templatePath) { + Copy-Item -Path $templatePath -Destination $configPath -Force + Write-Info "Initialized system roles.json: $configPath" + } else { + # Create default configuration + $defaultRoles = @{ + executor = "codex" + searcher = "codex" + git_manager = "codex" + } + $defaultRoles | ConvertTo-Json -Depth 10 | Out-File -FilePath $configPath -Encoding UTF8 + Write-Info "Created default roles.json: $configPath" + } +} + +function Install-CCA { + Write-Host "`n=== CCA Windows Installation ===" -ForegroundColor Cyan + Write-Host "" + + # Check if already installed + $update = $false + if (Test-Path $INSTALL_DIR) { + Write-Warn "CCA is already installed, will update..." + $update = $true + } else { + Write-Info "Installing CCA..." + } + + # Step 1: Create installation directory + Write-Host "`n[1/9] Creating installation directory..." -ForegroundColor Cyan + try { + New-Item -ItemType Directory -Path $INSTALL_DIR -Force -ErrorAction Stop | Out-Null + Write-Info "Install directory: $INSTALL_DIR" + } catch { + Write-Err "Failed to create installation directory: $_" + exit 1 + } + + # Step 2: Copy core files + Write-Host "`n[2/9] Copying core files..." -ForegroundColor Cyan + $coreFiles = @('cca.ps1', 'cca-roles-hook.ps1') + foreach ($file in $coreFiles) { + $sourcePath = Join-Path $SCRIPT_DIR $file + $targetPath = Join-Path $INSTALL_DIR $file + + if (Test-Path $sourcePath) { + Copy-Item -Path $sourcePath -Destination $targetPath -Force + Write-Info "Copied: $file" + } else { + Write-Warn "Source file not found: $file" + } + } + + # Step 3: Create wrapper files + Write-Host "`n[3/9] Creating wrapper files..." -ForegroundColor Cyan + Create-WrapperFiles + + # Step 4: Inject version info + Write-Host "`n[4/9] Injecting version information..." -ForegroundColor Cyan + $ccaSource = Join-Path $SCRIPT_DIR "cca.ps1" + $ccaTarget = Join-Path $INSTALL_DIR "cca.ps1" + Inject-Version -SourceFile $ccaSource -TargetFile $ccaTarget + + # Step 5: Install skills + Write-Host "`n[5/9] Installing skills..." -ForegroundColor Cyan + Install-Skills + + # Step 6: Install commands + Write-Host "`n[6/9] Installing commands..." -ForegroundColor Cyan + Install-Commands + + # Step 7: Configure PATH + Write-Host "`n[7/9] Configuring environment variables..." -ForegroundColor Cyan + Add-ToPath -Directory $INSTALL_DIR + + # Step 8: Initialize roles config + Write-Host "`n[8/9] Initializing roles configuration..." -ForegroundColor Cyan + Initialize-RolesConfig + + # Step 9: Done + Write-Host "`n[9/9] Installation complete!" -ForegroundColor Green + Write-Host "" + Write-Info "CCA has been successfully installed!" + Write-Host "" + Write-Host "Next steps:" -ForegroundColor Cyan + Write-Host " 1. Restart your terminal or run:" -ForegroundColor Yellow + Write-Host " `$env:Path = [System.Environment]::GetEnvironmentVariable('Path','User')" -ForegroundColor Gray + Write-Host "" + Write-Host " 2. Verify installation:" -ForegroundColor Yellow + Write-Host " cca --version" -ForegroundColor Gray + Write-Host "" + Write-Host " 3. Configure a project:" -ForegroundColor Yellow + Write-Host " cd your-project" -ForegroundColor Gray + Write-Host " cca add ." -ForegroundColor Gray + Write-Host "" +} + +function Uninstall-CCA { + Write-Host "`n=== CCA Windows Uninstallation ===" -ForegroundColor Cyan + Write-Host "" + + # Check if installed + if (-not (Test-Path $INSTALL_DIR)) { + Write-Warn "CCA is not installed at: $INSTALL_DIR" + return + } + + Write-Warn "This will remove CCA from your system." + Write-Host "" + + # Step 1: Remove from PATH + Write-Host "[1/3] Removing from PATH..." -ForegroundColor Cyan + Remove-FromPath -Directory $INSTALL_DIR + + # Step 2: Ask about config deletion + Write-Host "`n[2/3] Configuration cleanup..." -ForegroundColor Cyan + Write-Host "Do you want to delete the following?" -ForegroundColor Yellow + Write-Host " - $CLAUDE_DIR (skills and commands)" + Write-Host " - $CONFIG_DIR (system configuration)" + $deleteConfig = Read-Host "Delete configuration? (y/N)" + + if ($deleteConfig -eq 'y' -or $deleteConfig -eq 'Y') { + if (Test-Path $CLAUDE_DIR) { + Remove-Item -Path $CLAUDE_DIR -Recurse -Force -ErrorAction SilentlyContinue + Write-Info "Deleted: $CLAUDE_DIR" + } + if (Test-Path $CONFIG_DIR) { + Remove-Item -Path $CONFIG_DIR -Recurse -Force -ErrorAction SilentlyContinue + Write-Info "Deleted: $CONFIG_DIR" + } + } else { + Write-Blue "Configuration preserved" + } + + # Step 3: Remove installation directory + Write-Host "`n[3/3] Removing installation directory..." -ForegroundColor Cyan + try { + Remove-Item -Path $INSTALL_DIR -Recurse -Force -ErrorAction Stop + Write-Info "Deleted: $INSTALL_DIR" + } catch { + Write-Err "Failed to remove installation directory: $_" + } + + Write-Host "" + Write-Info "CCA has been uninstalled. Goodbye!" + Write-Host "" +} + +# Main Entry Point +try { + switch ($Action) { + 'install' { Install-CCA } + 'uninstall' { Uninstall-CCA } + 'help' { Show-Help } + default { + Write-Err "Unknown action: $Action" + Show-Help + exit 1 + } + } +} catch { + Write-Err "Error: $($_.Exception.Message)" + exit 1 +}