#Requires -RunAsAdministrator <# .SYNOPSIS ClaudeTools Bootstrap / Reinstall Script .DESCRIPTION One-and-done script to restore a complete ClaudeTools development environment after a fresh Windows 11 install. Idempotent - safe to re-run at any time. .NOTES Run from an elevated PowerShell prompt: Set-ExecutionPolicy Bypass -Scope Process -Force .\bootstrap.ps1 Optional flags: -SkipPhase Skip a specific phase (1-9) -OnlyPhase Run only a specific phase (1-9) -Archive Create pre-reinstall archive instead of installing -ArchivePath Custom archive output path (default: D:\ClaudeTools-backup.zip) #> param( [int[]]$SkipPhase = @(), [int]$OnlyPhase = 0, [switch]$Archive, [string]$ArchivePath = "D:\ClaudeTools-backup.zip" ) Set-StrictMode -Version Latest $ErrorActionPreference = "Continue" # --------------------------------------------------------------------------- # Globals # --------------------------------------------------------------------------- $Script:ClaudeToolsRoot = "D:\ClaudeTools" $Script:GiteaRepo = "https://git.azcomputerguru.com/azcomputerguru/claudetools.git" $Script:ClaudeConfigDir = Join-Path $env:USERPROFILE ".claude" $Script:ClaudeCommandsDir = Join-Path $Script:ClaudeConfigDir "commands" $Script:MemoryDir = Join-Path $Script:ClaudeConfigDir "projects\D--ClaudeTools\memory" $Script:Errors = [System.Collections.Generic.List[string]]::new() $Script:Warnings = [System.Collections.Generic.List[string]]::new() $Script:Successes = [System.Collections.Generic.List[string]]::new() $Script:Skipped = [System.Collections.Generic.List[string]]::new() # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- function Write-Status { param( [string]$Prefix, [string]$Message, [ConsoleColor]$Color = [ConsoleColor]::White ) Write-Host "[$Prefix] " -ForegroundColor $Color -NoNewline Write-Host $Message } function Write-OK { param([string]$Msg) Write-Status "OK" $Msg Green; $Script:Successes.Add($Msg) } function Write-Err { param([string]$Msg) Write-Status "ERROR" $Msg Red; $Script:Errors.Add($Msg) } function Write-Warn { param([string]$Msg) Write-Status "WARNING" $Msg Yellow; $Script:Warnings.Add($Msg) } function Write-Skip { param([string]$Msg) Write-Status "SKIP" $Msg Cyan; $Script:Skipped.Add($Msg) } function Write-Info { param([string]$Msg) Write-Status "INFO" $Msg White } function Write-Phase { param([int]$Num, [string]$Title) Write-Host "" Write-Host ("=" * 70) -ForegroundColor Magenta Write-Host " Phase ${Num}: $Title" -ForegroundColor Magenta Write-Host ("=" * 70) -ForegroundColor Magenta Write-Host "" } function Test-CommandExists { param([string]$Command) $null -ne (Get-Command $Command -ErrorAction SilentlyContinue) } function Refresh-PathEnv { # Reload PATH from registry so newly-installed tools are visible $machinePath = [System.Environment]::GetEnvironmentVariable("Path", "Machine") $userPath = [System.Environment]::GetEnvironmentVariable("Path", "User") $env:Path = "$machinePath;$userPath" } function Install-WingetPackage { param( [string]$PackageId, [string]$FriendlyName, [string]$VersionHint = "" ) # Check if already installed via winget list $installed = winget list --id $PackageId 2>$null if ($LASTEXITCODE -eq 0 -and $installed -match $PackageId) { Write-Skip "$FriendlyName is already installed" return $true } Write-Info "Installing $FriendlyName ($PackageId)..." $args = @("install", "--id", $PackageId, "--accept-source-agreements", "--accept-package-agreements", "--silent") if ($VersionHint) { $args += @("--version", $VersionHint) } $result = & winget @args 2>&1 if ($LASTEXITCODE -eq 0) { Write-OK "$FriendlyName installed successfully" return $true } else { # winget sometimes returns non-zero even on success (already installed race) $resultText = $result -join "`n" if ($resultText -match "already installed" -or $resultText -match "No applicable update") { Write-Skip "$FriendlyName is already installed" return $true } Write-Err "Failed to install $FriendlyName : $resultText" return $false } } function ShouldRunPhase { param([int]$PhaseNum) if ($OnlyPhase -gt 0) { return $PhaseNum -eq $OnlyPhase } return $PhaseNum -notin $SkipPhase } # --------------------------------------------------------------------------- # Archive Mode: Create pre-reinstall backup # --------------------------------------------------------------------------- function Invoke-Archive { Write-Phase 0 "Creating Pre-Reinstall Archive" $tempStaging = Join-Path $env:TEMP "claudetools-archive-staging" if (Test-Path $tempStaging) { Remove-Item $tempStaging -Recurse -Force } New-Item -ItemType Directory -Path $tempStaging -Force | Out-Null # Create subdirectories in staging $ctDest = Join-Path $tempStaging "ClaudeTools" $memDest = Join-Path $tempStaging "claude-memory" # Copy ClaudeTools repo (exclude node_modules, __pycache__, venv, .git large objects) Write-Info "Copying ClaudeTools repository..." if (Test-Path $Script:ClaudeToolsRoot) { $robocopyArgs = @( $Script:ClaudeToolsRoot, $ctDest, "/E", "/NFL", "/NDL", "/NJH", "/NJS", "/XD", "node_modules", "__pycache__", "venv", ".venv", ".mypy_cache" ) & robocopy @robocopyArgs | Out-Null Write-OK "ClaudeTools repository copied" } else { Write-Err "ClaudeTools directory not found at $Script:ClaudeToolsRoot" } # Copy Claude memory/config Write-Info "Copying Claude configuration and memory..." if (Test-Path $Script:ClaudeConfigDir) { $memSrc = $Script:ClaudeConfigDir & robocopy $memSrc (Join-Path $tempStaging "claude-config") /E /NFL /NDL /NJH /NJS /XD "node_modules" | Out-Null Write-OK "Claude configuration copied" } else { Write-Warn "Claude config directory not found at $Script:ClaudeConfigDir" } # Compress Write-Info "Compressing archive to $ArchivePath ..." if (Test-Path $ArchivePath) { Remove-Item $ArchivePath -Force } Compress-Archive -Path "$tempStaging\*" -DestinationPath $ArchivePath -CompressionLevel Optimal Write-OK "Archive created: $ArchivePath" # Cleanup staging Remove-Item $tempStaging -Recurse -Force $sizeMB = [math]::Round((Get-Item $ArchivePath).Length / 1MB, 1) Write-Host "" Write-OK "Archive complete: $ArchivePath ($sizeMB MB)" Write-Info "Copy this file to external storage before reinstalling Windows." } # --------------------------------------------------------------------------- # Phase 1: Prerequisites # --------------------------------------------------------------------------- function Invoke-Phase1 { Write-Phase 1 "Prerequisites Installation (winget)" if (-not (Test-CommandExists "winget")) { Write-Err "winget is not available. Please install App Installer from the Microsoft Store." return } Install-WingetPackage "Git.Git" "Git" Install-WingetPackage "OpenJS.NodeJS" "Node.js (Latest LTS)" Install-WingetPackage "Python.Python.3.13" "Python 3.13" Install-WingetPackage "Ollama.Ollama" "Ollama" # Refresh PATH so subsequent phases see the new installs Refresh-PathEnv # Verify critical tools are now on PATH foreach ($tool in @("git", "node", "python", "ollama")) { if (Test-CommandExists $tool) { $ver = & $tool --version 2>&1 | Select-Object -First 1 Write-OK "$tool found: $ver" } else { Write-Warn "$tool not found on PATH after install. You may need to restart your terminal." } } } # --------------------------------------------------------------------------- # Phase 2: Claude Code + Global NPM Packages # --------------------------------------------------------------------------- function Invoke-Phase2 { Write-Phase 2 "Claude Code and Global NPM Packages" if (-not (Test-CommandExists "npm")) { Refresh-PathEnv if (-not (Test-CommandExists "npm")) { Write-Err "npm not found. Node.js installation may have failed." return } } $npmPackages = @( @{ Name = "@anthropic-ai/claude-code"; Cmd = "claude" }, @{ Name = "clawhub"; Cmd = "clawhub" }, @{ Name = "mcporter"; Cmd = "mcporter" }, @{ Name = "openclaw"; Cmd = "openclaw" } ) foreach ($pkg in $npmPackages) { # Check if already installed globally $installed = npm list -g $($pkg.Name) --depth=0 2>$null if ($LASTEXITCODE -eq 0 -and $installed -match $pkg.Name) { Write-Skip "$($pkg.Name) is already installed globally" } else { Write-Info "Installing $($pkg.Name) globally..." npm install -g $($pkg.Name) 2>&1 | Out-Null if ($LASTEXITCODE -eq 0) { Write-OK "$($pkg.Name) installed" } else { Write-Err "Failed to install $($pkg.Name)" } } } } # --------------------------------------------------------------------------- # Phase 3: Clone or Configure ClaudeTools Repository # --------------------------------------------------------------------------- function Invoke-Phase3 { Write-Phase 3 "ClaudeTools Repository" if (-not (Test-CommandExists "git")) { Refresh-PathEnv if (-not (Test-CommandExists "git")) { Write-Err "git not found. Cannot proceed with repository setup." return } } if (Test-Path (Join-Path $Script:ClaudeToolsRoot ".git")) { Write-Skip "ClaudeTools repository already exists at $Script:ClaudeToolsRoot" Write-Info "Configuring git for self-signed certificate..." Push-Location $Script:ClaudeToolsRoot git config http.sslVerify false 2>&1 | Out-Null Pop-Location Write-OK "Git SSL verification disabled for self-signed Gitea cert" # Ensure remote is correct Push-Location $Script:ClaudeToolsRoot $currentRemote = git remote get-url origin 2>$null if ($currentRemote -ne $Script:GiteaRepo) { git remote set-url origin $Script:GiteaRepo 2>&1 | Out-Null Write-OK "Git remote updated to $Script:GiteaRepo" } else { Write-OK "Git remote is correct" } Pop-Location } elseif (Test-Path $Script:ClaudeToolsRoot) { # Directory exists but is not a git repo (restored from archive) Write-Info "Directory exists but is not a git repo. Initializing..." Push-Location $Script:ClaudeToolsRoot git init 2>&1 | Out-Null git remote add origin $Script:GiteaRepo 2>&1 | Out-Null git config http.sslVerify false 2>&1 | Out-Null git fetch origin 2>&1 | Out-Null git checkout -B main origin/main 2>&1 | Out-Null Pop-Location Write-OK "Repository initialized from archive and connected to remote" } else { # Fresh clone Write-Info "Cloning ClaudeTools repository..." # Ensure D:\ exists if (-not (Test-Path "D:\")) { Write-Err "D:\ drive not found. Cannot clone repository." return } git clone -c http.sslVerify=false $Script:GiteaRepo $Script:ClaudeToolsRoot 2>&1 if ($LASTEXITCODE -eq 0) { Push-Location $Script:ClaudeToolsRoot git config http.sslVerify false 2>&1 | Out-Null Pop-Location Write-OK "Repository cloned successfully" } else { Write-Err "Failed to clone repository" } } } # --------------------------------------------------------------------------- # Phase 4: Python Environment # --------------------------------------------------------------------------- function Invoke-Phase4 { Write-Phase 4 "Python Packages (Global pip install)" if (-not (Test-CommandExists "python")) { Refresh-PathEnv if (-not (Test-CommandExists "python")) { Write-Err "python not found. Python installation may have failed." return } } $pyVersion = python --version 2>&1 Write-OK "Python detected: $pyVersion" # Full list of required packages with pinned versions $pipPackages = @( "alembic==1.13.1", "annotated-types==0.7.0", "anyio==4.12.1", "argon2-cffi==25.1.0", "argon2-cffi-bindings==25.1.0", "bcrypt==5.0.0", "beautifulsoup4==4.13.5", "certifi==2026.1.4", "cffi==2.0.0", "claude-agent-sdk==0.1.19", "claude-code-sdk==0.0.25", "click==8.3.1", "colorama==0.4.6", "compressed-rtf==1.0.6", "cryptography==46.0.3", "easygui==0.98.3", "extract-msg==0.55.0", "fastapi==0.128.0", "google-api-core==2.30.0", "google-api-python-client==2.192.0", "google-auth==2.49.0", "google-auth-httplib2==0.3.0", "googleapis-common-protos==1.73.0", "greenlet==3.3.0", "httpcore==1.0.9", "httplib2==0.31.2", "httpx==0.28.1", "httpx-sse==0.4.3", "invoke==2.2.1", "jsonschema==4.26.0", "lark==1.3.1", "Mako==1.3.10", "MarkupSafe==3.0.3", "mcp==1.25.0", "msoffcrypto-tool==5.4.2", "numpy==2.4.2", "olefile==0.47", "oletools==0.60.2", "opencv-python==4.13.0.90", "paramiko==4.0.0", "passlib==1.7.4", "pillow==12.1.0", "proto-plus==1.27.1", "protobuf==6.33.5", "pydantic==2.12.5", "pydantic-settings==2.12.0", "PyJWT==2.10.1", "PyMySQL==1.1.0", "PyNaCl==1.6.2", "python-dotenv==1.2.1", "python-multipart==0.0.21", "pywin32==311", "pyzbar==0.1.9", "requests==2.32.5", "RTFDE==0.1.2.2", "SQLAlchemy==2.0.45", "sse-starlette==3.1.2", "starlette==0.50.0", "tzdata==2025.3", "tzlocal==5.3.1", "uritemplate==4.2.0", "uvicorn==0.40.0", "uv==0.9.9", "websockets==15.0.1" ) # Write a temporary requirements file for batch install $reqFile = Join-Path $env:TEMP "claudetools-requirements.txt" $pipPackages | Out-File -FilePath $reqFile -Encoding UTF8 Write-Info "Installing $($pipPackages.Count) Python packages (this may take several minutes)..." $pipOutput = python -m pip install --upgrade pip 2>&1 $pipOutput = python -m pip install -r $reqFile 2>&1 if ($LASTEXITCODE -eq 0) { Write-OK "All Python packages installed successfully" } else { # Check for partial failures $failLines = ($pipOutput | Select-String "ERROR:" | ForEach-Object { $_.Line }) if ($failLines) { foreach ($line in $failLines) { Write-Err "pip: $line" } } else { Write-Warn "pip exited with warnings but may have succeeded. Review output above." } } Remove-Item $reqFile -Force -ErrorAction SilentlyContinue } # --------------------------------------------------------------------------- # Phase 5: Ollama Models # --------------------------------------------------------------------------- function Invoke-Phase5 { Write-Phase 5 "Ollama Models" if (-not (Test-CommandExists "ollama")) { Refresh-PathEnv if (-not (Test-CommandExists "ollama")) { Write-Err "ollama not found. Ollama installation may have failed." return } } # Ensure Ollama service is running Write-Info "Ensuring Ollama service is running..." $ollamaProcess = Get-Process "ollama" -ErrorAction SilentlyContinue if (-not $ollamaProcess) { Write-Info "Starting Ollama service..." Start-Process "ollama" -ArgumentList "serve" -WindowStyle Hidden Start-Sleep -Seconds 3 } $models = @( "nomic-embed-text", "llama3.1:8b", "qwen2.5-coder:7b" ) foreach ($model in $models) { Write-Info "Pulling model: $model (this may take a while)..." $pullOutput = ollama pull $model 2>&1 if ($LASTEXITCODE -eq 0) { Write-OK "Model $model pulled successfully" } else { $outputText = $pullOutput -join "`n" if ($outputText -match "up to date") { Write-Skip "Model $model is already up to date" } else { Write-Err "Failed to pull model $model : $outputText" } } } } # --------------------------------------------------------------------------- # Phase 6: MCP Server Setup # --------------------------------------------------------------------------- function Invoke-Phase6 { Write-Phase 6 "MCP Server Setup (Ollama Assistant)" $ollamaAssistantDir = Join-Path $Script:ClaudeToolsRoot "mcp-servers\ollama-assistant" $venvDir = Join-Path $ollamaAssistantDir "venv" $venvPython = Join-Path $venvDir "Scripts\python.exe" $requirementsFile = Join-Path $ollamaAssistantDir "requirements.txt" if (-not (Test-Path $ollamaAssistantDir)) { Write-Err "Ollama assistant directory not found at $ollamaAssistantDir" return } # Create venv if it doesn't exist or is broken if (-not (Test-Path $venvPython)) { Write-Info "Creating Python virtual environment..." if (Test-Path $venvDir) { Remove-Item $venvDir -Recurse -Force } python -m venv $venvDir 2>&1 | Out-Null if ($LASTEXITCODE -eq 0) { Write-OK "Virtual environment created" } else { Write-Err "Failed to create virtual environment" return } } else { Write-Skip "Virtual environment already exists" } # Install requirements Write-Info "Installing MCP server dependencies..." if (Test-Path $requirementsFile) { & $venvPython -m pip install --upgrade pip 2>&1 | Out-Null $installOutput = & $venvPython -m pip install -r $requirementsFile 2>&1 if ($LASTEXITCODE -eq 0) { Write-OK "MCP server dependencies installed" } else { Write-Err "Failed to install MCP server dependencies: $installOutput" } } else { # Fallback: install mcp and httpx directly & $venvPython -m pip install --upgrade pip 2>&1 | Out-Null & $venvPython -m pip install "mcp>=0.1.0" "httpx>=0.25.0" 2>&1 | Out-Null if ($LASTEXITCODE -eq 0) { Write-OK "MCP server dependencies installed (fallback)" } else { Write-Err "Failed to install MCP server dependencies" } } # Verify .mcp.json exists in repo $mcpJson = Join-Path $Script:ClaudeToolsRoot ".mcp.json" if (Test-Path $mcpJson) { Write-OK ".mcp.json configuration found in repository" } else { Write-Warn ".mcp.json not found - MCP servers will not be configured" } } # --------------------------------------------------------------------------- # Phase 7: Claude Code Configuration # --------------------------------------------------------------------------- function Invoke-Phase7 { Write-Phase 7 "Claude Code Configuration" # Create directories foreach ($dir in @($Script:ClaudeConfigDir, $Script:ClaudeCommandsDir, $Script:MemoryDir)) { if (-not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null Write-OK "Created directory: $dir" } else { Write-Skip "Directory exists: $dir" } } # Write settings.json $settingsPath = Join-Path $Script:ClaudeConfigDir "settings.json" $settingsContent = @' { "permissions": { "allow": [ "Bash(git:*)", "Bash(gh:*)", "Bash(ssh:*)", "Bash(scp:*)", "Bash(rsync:*)", "Bash(wsl:*)", "Bash(wsl.exe:*)", "Bash(cat:*)", "Bash(ls:*)", "Bash(find:*)", "Bash(grep:*)", "Bash(echo:*)", "Bash(chmod:*)", "Bash(chown:*)", "Bash(mkdir:*)", "Bash(rm:*)", "Bash(cp:*)", "Bash(mv:*)", "Bash(curl:*)", "Bash(wget:*)", "Bash(nslookup:*)", "Bash(dig:*)", "Bash(ping:*)", "Bash(python:*)", "Bash(python3:*)", "Bash(node:*)", "Bash(npm:*)", "Bash(npx:*)", "Bash(cargo:*)", "Bash(rustc:*)", "Bash(rustup:*)", "Bash(powershell:*)", "Bash(powershell.exe:*)", "Bash(pwsh:*)", "Bash(which:*)", "Bash(where:*)", "Bash(whoami:*)", "Bash(date:*)", "Bash(head:*)", "Bash(tail:*)", "Bash(less:*)", "Bash(more:*)", "Bash(diff:*)", "Bash(tar:*)", "Bash(unzip:*)", "Bash(zip:*)", "Bash(docker:*)", "Bash(docker-compose:*)", "Bash(systemctl:*)", "Bash(service:*)", "Bash(journalctl:*)", "Bash(apt:*)", "Bash(apt-get:*)", "Bash(brew:*)", "Bash(code:*)", "Bash(make:*)", "Bash(cmake:*)", "Bash(dir:*)", "Bash(wc:*)", "Bash(winget:*)", "Bash(choco:*)", "Bash(ipconfig:*)", "Bash(net:*)", "Bash(perl:*)", "Bash(xxd:*)", "Bash(timeout:*)", "Bash(claude:*)", "Bash(plink:*)", "WebFetch(domain:*)", "Skill(s)", "Read(//c/Users/$env:USERNAME/.claude/**)" ], "deny": [], "ask": [] }, "statusLine": { "type": "command", "command": "input=$(cat); remaining=$(echo \"$input\" | jq -r '.context_window.remaining_percentage // empty'); [ -n \"$remaining\" ] && printf \"Context: %.0f%% remaining\" \"$remaining\" || echo \"\"" }, "skipDangerousModePermissionPrompt": true } '@ # Replace $env:USERNAME placeholder with actual username $settingsContent = $settingsContent -replace '\$env:USERNAME', $env:USERNAME Set-Content -Path $settingsPath -Value $settingsContent -Encoding UTF8 -Force Write-OK "settings.json written to $settingsPath" # Copy commands from repo to global $repoCommandsDir = Join-Path $Script:ClaudeToolsRoot ".claude\commands" if (Test-Path $repoCommandsDir) { $commandFiles = Get-ChildItem -Path $repoCommandsDir -File if ($commandFiles.Count -gt 0) { Copy-Item -Path "$repoCommandsDir\*" -Destination $Script:ClaudeCommandsDir -Force Write-OK "Copied $($commandFiles.Count) command files to $Script:ClaudeCommandsDir" } else { Write-Warn "No command files found in $repoCommandsDir" } } else { Write-Warn "Repo commands directory not found at $repoCommandsDir (run after Phase 3)" } # Check for archived memory to restore Write-Info "Checking for memory directory..." if (Test-Path (Join-Path $Script:MemoryDir "MEMORY.md")) { Write-Skip "Memory files already present" } else { Write-Warn "Memory directory is empty. If you have an archive, manually restore:" Write-Warn " Copy contents to $Script:MemoryDir" } } # --------------------------------------------------------------------------- # Phase 8: GrepAI Setup # --------------------------------------------------------------------------- function Invoke-Phase8 { Write-Phase 8 "GrepAI Setup" $grepaiExe = Join-Path $Script:ClaudeToolsRoot "grepai.exe" if (-not (Test-Path $grepaiExe)) { Write-Err "grepai.exe not found at $grepaiExe" Write-Warn "GrepAI binary should be in the repository. Ensure Phase 3 completed successfully." return } Write-OK "grepai.exe found at $grepaiExe" # Check if already initialized $grepaiConfig = Join-Path $Script:ClaudeToolsRoot ".grepai" if (Test-Path $grepaiConfig) { Write-Skip "GrepAI appears to already be initialized (.grepai directory exists)" } else { Write-Info "Initializing GrepAI..." Write-Info "This may prompt for configuration. Accept defaults for Ollama + nomic-embed-text." Push-Location $Script:ClaudeToolsRoot & $grepaiExe init 2>&1 Pop-Location if ($LASTEXITCODE -eq 0) { Write-OK "GrepAI initialized" } else { Write-Warn "GrepAI init may require manual interaction. Run manually:" Write-Warn " cd $Script:ClaudeToolsRoot && .\grepai.exe init" } } } # --------------------------------------------------------------------------- # Phase 9: Verification # --------------------------------------------------------------------------- function Invoke-Phase9 { Write-Phase 9 "Verification" $checks = @( @{ Name = "Git"; Cmd = "git --version" }, @{ Name = "Node.js"; Cmd = "node --version" }, @{ Name = "npm"; Cmd = "npm --version" }, @{ Name = "Python"; Cmd = "python --version" }, @{ Name = "pip"; Cmd = "python -m pip --version" }, @{ Name = "Ollama"; Cmd = "ollama --version" }, @{ Name = "Claude Code CLI"; Cmd = "claude --version" } ) Refresh-PathEnv foreach ($check in $checks) { try { $result = Invoke-Expression $check.Cmd 2>&1 | Select-Object -First 1 if ($LASTEXITCODE -eq 0 -or $result) { Write-OK "$($check.Name): $result" } else { Write-Err "$($check.Name): not found or not working" } } catch { Write-Err "$($check.Name): $($_.Exception.Message)" } } # Check directories Write-Host "" Write-Info "Directory checks:" $dirs = @( $Script:ClaudeToolsRoot, $Script:ClaudeConfigDir, $Script:ClaudeCommandsDir, (Join-Path $Script:ClaudeToolsRoot "mcp-servers\ollama-assistant\venv") ) foreach ($dir in $dirs) { if (Test-Path $dir) { Write-OK "EXISTS: $dir" } else { Write-Err "MISSING: $dir" } } # Check key files Write-Host "" Write-Info "File checks:" $files = @( (Join-Path $Script:ClaudeConfigDir "settings.json"), (Join-Path $Script:ClaudeToolsRoot ".mcp.json"), (Join-Path $Script:ClaudeToolsRoot "grepai.exe") ) foreach ($file in $files) { if (Test-Path $file) { Write-OK "EXISTS: $file" } else { Write-Err "MISSING: $file" } } # Check Ollama models Write-Host "" Write-Info "Ollama model checks:" if (Test-CommandExists "ollama") { $modelList = ollama list 2>&1 foreach ($model in @("nomic-embed-text", "llama3.1:8b", "qwen2.5-coder:7b")) { if ($modelList -match [regex]::Escape($model)) { Write-OK "Model loaded: $model" } else { Write-Warn "Model not found: $model" } } } else { Write-Warn "Cannot check Ollama models - ollama not on PATH" } } # --------------------------------------------------------------------------- # Main Execution # --------------------------------------------------------------------------- Write-Host "" Write-Host "================================================================" -ForegroundColor Cyan Write-Host " ClaudeTools Bootstrap / Reinstall Script" -ForegroundColor Cyan Write-Host " $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan Write-Host "" # Archive mode if ($Archive) { Invoke-Archive return } # Check for admin $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $isAdmin) { Write-Err "This script requires administrator privileges for winget installations." Write-Err "Re-run from an elevated PowerShell prompt." exit 1 } # Execute phases $phases = @( @{ Num = 1; Func = { Invoke-Phase1 } }, @{ Num = 2; Func = { Invoke-Phase2 } }, @{ Num = 3; Func = { Invoke-Phase3 } }, @{ Num = 4; Func = { Invoke-Phase4 } }, @{ Num = 5; Func = { Invoke-Phase5 } }, @{ Num = 6; Func = { Invoke-Phase6 } }, @{ Num = 7; Func = { Invoke-Phase7 } }, @{ Num = 8; Func = { Invoke-Phase8 } }, @{ Num = 9; Func = { Invoke-Phase9 } } ) foreach ($phase in $phases) { if (ShouldRunPhase $phase.Num) { try { & $phase.Func } catch { Write-Err "Phase $($phase.Num) failed with exception: $($_.Exception.Message)" } } else { Write-Skip "Phase $($phase.Num) skipped (by request)" } } # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- Write-Host "" Write-Host "================================================================" -ForegroundColor Cyan Write-Host " Bootstrap Summary" -ForegroundColor Cyan Write-Host "================================================================" -ForegroundColor Cyan Write-Host "" if ($Script:Successes.Count -gt 0) { Write-Host " Successes: $($Script:Successes.Count)" -ForegroundColor Green } if ($Script:Skipped.Count -gt 0) { Write-Host " Skipped: $($Script:Skipped.Count)" -ForegroundColor Cyan } if ($Script:Warnings.Count -gt 0) { Write-Host " Warnings: $($Script:Warnings.Count)" -ForegroundColor Yellow foreach ($w in $Script:Warnings) { Write-Host " - $w" -ForegroundColor Yellow } } if ($Script:Errors.Count -gt 0) { Write-Host " Errors: $($Script:Errors.Count)" -ForegroundColor Red foreach ($e in $Script:Errors) { Write-Host " - $e" -ForegroundColor Red } } Write-Host "" Write-Host " Manual steps remaining:" -ForegroundColor Yellow Write-Host " 1. Run 'claude' and authenticate with Anthropic API key" -ForegroundColor Yellow Write-Host " 2. Install Claude-in-Chrome browser extension" -ForegroundColor Yellow Write-Host " 3. Set GITHUB_PERSONAL_ACCESS_TOKEN in .mcp.json" -ForegroundColor Yellow Write-Host " 4. Restore memory files if not already present" -ForegroundColor Yellow Write-Host "" if ($Script:Errors.Count -eq 0) { Write-Host " [OK] Bootstrap completed successfully!" -ForegroundColor Green } else { Write-Host " [WARNING] Bootstrap completed with $($Script:Errors.Count) error(s). Review above." -ForegroundColor Yellow } Write-Host ""