Files
ccs/ccs.ps1
T
kaitranntt eac9e39d79 fix(ccs): improve environment variable handling and error resilience
- use proper .NET enum for environment variable targets

- add try-catch blocks for environment variable operations

- provide user-friendly warnings when operations fail

- fixes PATH operations in install and uninstall scripts
2025-11-02 11:40:36 -05:00

187 lines
6.2 KiB
PowerShell

# CCS - Claude Code Switch (Windows PowerShell)
# Cross-platform Claude CLI profile switcher
# https://github.com/kaitranntt/ccs
param(
[Parameter(Position=0)]
[string]$Profile = "default",
[Parameter(ValueFromRemainingArguments=$true)]
[string[]]$RemainingArgs
)
$ErrorActionPreference = "Stop"
# Special case: "default" profile just runs claude directly (no profile switching)
if ($Profile -eq "default") {
try {
if ($RemainingArgs) {
& claude @RemainingArgs
} else {
& claude
}
exit $LASTEXITCODE
} catch {
Write-Host "Error: Failed to execute claude" -ForegroundColor Red
Write-Host $_.Exception.Message
exit 1
}
}
# Config file location (supports environment variable override)
$ConfigFile = if ($env:CCS_CONFIG) {
$env:CCS_CONFIG
} else {
"$env:USERPROFILE\.ccs\config.json"
}
# Check config exists
if (-not (Test-Path $ConfigFile)) {
Write-Host "Error: Config file not found: $ConfigFile" -ForegroundColor Red
Write-Host ""
Write-Host "Create $env:USERPROFILE\.ccs\config.json with your profile mappings."
Write-Host "See .ccs.example.json for template."
exit 1
}
# Validate profile name (alphanumeric, dash, underscore only)
if ($Profile -notmatch '^[a-zA-Z0-9_-]+$') {
Write-Host "Error: Invalid profile name. Use only alphanumeric characters, dash, or underscore." -ForegroundColor Red
exit 1
}
# Read and parse JSON config
try {
$ConfigContent = Get-Content $ConfigFile -Raw -ErrorAction Stop
$Config = $ConfigContent | ConvertFrom-Json -ErrorAction Stop
} catch {
Write-Host "Error: Invalid JSON in $ConfigFile" -ForegroundColor Red
exit 1
}
# Validate config has profiles object
if (-not $Config.profiles) {
Write-Host "Error: Config must have 'profiles' object" -ForegroundColor Red
Write-Host "See .ccs.example.json for correct format"
exit 1
}
# Get settings path for profile
$SettingsPath = $Config.profiles.$Profile
if (-not $SettingsPath) {
Write-Host "Error: Profile '$Profile' not found in $ConfigFile" -ForegroundColor Red
Write-Host ""
Write-Host "Available profiles:"
$Config.profiles.PSObject.Properties.Name | ForEach-Object {
Write-Host " - $_"
}
exit 1
}
# Path expansion and normalization
# 1. Handle Unix-style tilde expansion (~/path -> %USERPROFILE%\path)
if ($SettingsPath -match '^~[/\\]') {
$SettingsPath = $SettingsPath -replace '^~', $env:USERPROFILE
}
# 2. Expand Windows environment variables (%USERPROFILE%, etc.)
$SettingsPath = [System.Environment]::ExpandEnvironmentVariables($SettingsPath)
# 3. Convert forward slashes to backslashes (Unix path compatibility)
$SettingsPath = $SettingsPath -replace '/', '\'
# Validate settings file exists
if (-not (Test-Path $SettingsPath)) {
Write-Host "Error: Settings file not found: $SettingsPath" -ForegroundColor Red
Write-Host ""
Write-Host "Solutions:" -ForegroundColor Yellow
Write-Host " 1. Create the settings file with empty env:"
Write-Host " New-Item -ItemType File -Force -Path '$SettingsPath'"
Write-Host " Set-Content -Path '$SettingsPath' -Value '{`"env`":{}}`'"
Write-Host ""
Write-Host " 2. Or update profile path in $ConfigFile"
Write-Host ""
Write-Host " 3. Or reinstall: irm ccs.kaitran.ca/install.ps1 | iex"
exit 1
}
# Read settings file (contains environment variables for Windows)
try {
$SettingsContent = Get-Content $SettingsPath -Raw -ErrorAction Stop
$Settings = $SettingsContent | ConvertFrom-Json -ErrorAction Stop
} catch {
Write-Host "Error: Invalid JSON in $SettingsPath" -ForegroundColor Red
Write-Host ""
Write-Host "Details: $_" -ForegroundColor Yellow
Write-Host ""
Write-Host "Solutions:" -ForegroundColor Yellow
Write-Host " 1. Validate JSON at https://jsonlint.com"
Write-Host " 2. Or reset to template:"
Write-Host " Set-Content -Path '$SettingsPath' -Value '{`"env`":{}}`'"
Write-Host ""
Write-Host " 3. Or reinstall: irm ccs.kaitran.ca/install.ps1 | iex"
exit 1
}
# Windows Claude CLI uses environment variables instead of --settings flag
# Settings file contains env vars directly or nested in "env" object (Linux compat)
# Check if settings contain "env" wrapper (Linux format) or direct env vars (Windows format)
$EnvVars = if ($Settings.env) {
# Linux format: { "env": { "ANTHROPIC_AUTH_TOKEN": "..." } }
$Settings.env
} else {
# Windows format: { "ANTHROPIC_AUTH_TOKEN": "..." }
$Settings
}
# Save original environment variables to restore after execution
$OriginalEnvVars = @{}
if ($EnvVars.PSObject.Properties.Count -gt 0) {
foreach ($Property in $EnvVars.PSObject.Properties) {
$VarName = $Property.Name
$VarValue = $Property.Value
try {
# Save original value
$OriginalEnvVars[$VarName] = [System.Environment]::GetEnvironmentVariable($VarName, [System.EnvironmentVariableTarget]::Process)
# Set new value for this process only
[System.Environment]::SetEnvironmentVariable($VarName, $VarValue, [System.EnvironmentVariableTarget]::Process)
} catch {
Write-Host "Warning: Could not set environment variable '$VarName'" -ForegroundColor Yellow
Write-Host " Error: $($_.Exception.Message)" -ForegroundColor Yellow
}
}
}
# Execute claude with environment variables set
try {
if ($RemainingArgs) {
& claude @RemainingArgs
} else {
& claude
}
$ExitCode = $LASTEXITCODE
} finally {
# Restore original environment variables
foreach ($VarName in $OriginalEnvVars.Keys) {
$OriginalValue = $OriginalEnvVars[$VarName]
try {
if ($null -eq $OriginalValue) {
# Variable didn't exist before, remove it
[System.Environment]::SetEnvironmentVariable($VarName, $null, [System.EnvironmentVariableTarget]::Process)
} else {
# Restore original value
[System.Environment]::SetEnvironmentVariable($VarName, $OriginalValue, [System.EnvironmentVariableTarget]::Process)
}
} catch {
Write-Host "Warning: Could not restore environment variable '$VarName'" -ForegroundColor Yellow
}
}
}
exit $ExitCode