Files
wehub-resource-sync 5296d0e97c
CI / Ban suppressions and legacy annotations (push) Has been cancelled
CI / pytest (push) Has been cancelled
CI / ruff-check (push) Has been cancelled
CI / ruff-format (push) Has been cancelled
CI / ty (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:44 +08:00

272 lines
7.4 KiB
PowerShell

param(
[switch] $DryRun,
[switch] $Help,
[Parameter(ValueFromRemainingArguments = $true)]
[object[]] $RemainingArgs = @()
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$PackageName = "free-claude-code"
$FccHomeDirname = ".fcc"
$FccCommands = @(
"fcc-server",
"fcc-claude",
"fcc-codex",
"fcc-pi",
"fcc-init",
"free-claude-code"
)
$script:UvPath = ""
$script:UvToolBin = ""
function Show-Usage {
@"
Usage: uninstall.ps1 [options]
Removes the Free Claude Code uv tool and deletes ~/.fcc/ after removal is verified.
Does not remove uv, Claude Code, Codex, Pi, the uv-managed Python runtime, or shared PATH entries.
Options:
-DryRun Print commands without running them.
-Help Show this help text.
"@
}
function Write-Step {
param([string] $Message)
Write-Host ""
Write-Host "==> $Message"
}
function Format-Argument {
param([string] $Value)
if ($Value -match '^[A-Za-z0-9_./:@%+=,\[\]\\-]+$') {
return $Value
}
return "'" + ($Value -replace "'", "''") + "'"
}
function Format-Command {
param(
[string] $FilePath,
[string[]] $Arguments = @()
)
$parts = @($FilePath) + $Arguments
return ($parts | ForEach-Object { Format-Argument ([string] $_) }) -join " "
}
function Get-ApplicationCommand {
param([string] $Name)
$commands = @(Get-Command $Name -CommandType Application -ErrorAction SilentlyContinue)
if ($commands.Count -eq 0) {
return $null
}
return $commands[0]
}
function Invoke-NativeResult {
param(
[string] $FilePath,
[string[]] $Arguments
)
$previousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
$global:LASTEXITCODE = 0
$output = (& $FilePath @Arguments 2>&1 | Out-String).Trim()
return [pscustomobject] @{
ExitCode = $LASTEXITCODE
Output = $output
}
}
finally {
$ErrorActionPreference = $previousErrorActionPreference
}
}
function Test-MissingUvToolError {
param([string] $Output)
$normalized = $Output.ToLowerInvariant()
return $normalized.Contains($PackageName) -and $normalized.Contains("is not installed")
}
function Add-PathEntry {
param([string] $PathEntry)
if ([string]::IsNullOrWhiteSpace($PathEntry)) {
return
}
$separator = [IO.Path]::PathSeparator
$entries = @()
if (-not [string]::IsNullOrEmpty($env:Path)) {
$entries = $env:Path -split [regex]::Escape([string] $separator)
}
if ($entries -notcontains $PathEntry) {
$env:Path = "$PathEntry$separator$env:Path"
}
}
function Add-KnownUvPaths {
Add-PathEntry (Join-Path $env:USERPROFILE ".local\bin")
Add-PathEntry (Join-Path $env:USERPROFILE ".cargo\bin")
}
function Assert-NoFccProcessesRunning {
$running = @()
foreach ($commandName in $FccCommands) {
$processes = @(Get-Process -Name $commandName -ErrorAction SilentlyContinue)
if ($processes.Count -gt 0) {
$running += $commandName
}
}
if ($running.Count -gt 0) {
throw "Free Claude Code is still running ($($running -join ', ')). Stop those processes, then rerun uninstall."
}
}
function Initialize-UvContext {
Add-KnownUvPaths
if ($DryRun) {
Write-Host "+ uv tool dir --bin"
return
}
$uvCommand = Get-ApplicationCommand "uv"
if (-not $uvCommand) {
throw "uv is required to remove the Free Claude Code tool. Install uv, then rerun this uninstaller; ~/.fcc was not deleted."
}
$script:UvPath = $uvCommand.Source
$commandText = Format-Command -FilePath $script:UvPath -Arguments @("tool", "dir", "--bin")
Write-Host "+ $commandText"
$result = Invoke-NativeResult -FilePath $script:UvPath -Arguments @("tool", "dir", "--bin")
if ($result.ExitCode -ne 0) {
if (-not [string]::IsNullOrWhiteSpace($result.Output)) {
[Console]::Error.WriteLine($result.Output)
}
throw "Could not determine the uv tool bin directory (exit code $($result.ExitCode)); ~/.fcc was not deleted."
}
$script:UvToolBin = $result.Output.Trim()
if ([string]::IsNullOrWhiteSpace($script:UvToolBin)) {
throw "uv returned an empty tool bin directory; ~/.fcc was not deleted."
}
}
function Uninstall-FreeClaudeCode {
Write-Host "+ uv tool uninstall $PackageName"
if ($DryRun) {
return
}
$result = Invoke-NativeResult -FilePath $script:UvPath -Arguments @(
"tool",
"uninstall",
$PackageName
)
if ($result.ExitCode -eq 0) {
if (-not [string]::IsNullOrWhiteSpace($result.Output)) {
Write-Host $result.Output
}
return
}
if (Test-MissingUvToolError -Output $result.Output) {
Write-Host "Free Claude Code uv tool is already absent; verifying its entry points."
return
}
if (-not [string]::IsNullOrWhiteSpace($result.Output)) {
[Console]::Error.WriteLine($result.Output)
}
throw "uv tool uninstall $PackageName failed with exit code $($result.ExitCode); ~/.fcc was not deleted."
}
function Confirm-FccCommandsRemoved {
if ($DryRun) {
Write-Host "+ verify all Free Claude Code entry points are absent from the uv tool bin directory"
return
}
$remaining = @()
$extensions = @("", ".exe", ".cmd", ".bat", ".ps1")
foreach ($commandName in $FccCommands) {
foreach ($extension in $extensions) {
$commandPath = Join-Path $script:UvToolBin "$commandName$extension"
if (Test-Path -LiteralPath $commandPath) {
$remaining += $commandPath
}
}
}
if ($remaining.Count -gt 0) {
throw "Free Claude Code entry points remain after uv uninstall: $($remaining -join ', '); ~/.fcc was not deleted."
}
}
function Purge-FccHome {
$fccHome = Join-Path $env:USERPROFILE $FccHomeDirname
if (-not (Test-Path -LiteralPath $fccHome)) {
Write-Host "No FCC config directory at $fccHome; skipping purge."
return
}
$commandText = @(
"Remove-Item",
"-LiteralPath",
(Format-Argument $fccHome),
"-Recurse",
"-Force"
) -join " "
Write-Host "+ $commandText"
if ($DryRun) {
return
}
Remove-Item -LiteralPath $fccHome -Recurse -Force
if (Test-Path -LiteralPath $fccHome) {
throw "FCC config directory still exists after deletion: $fccHome"
}
}
if ($Help) {
Show-Usage
return
}
if ($RemainingArgs.Count -gt 0) {
Show-Usage
throw "Unknown option: $($RemainingArgs -join ' ')"
}
if ([string]::IsNullOrWhiteSpace($env:USERPROFILE)) {
throw "USERPROFILE is not set; cannot locate Free Claude Code data."
}
Write-Step "Checking for running Free Claude Code processes"
Assert-NoFccProcessesRunning
Write-Step "Locating the uv-managed Free Claude Code installation"
Initialize-UvContext
Write-Step "Removing the Free Claude Code uv tool"
Uninstall-FreeClaudeCode
Write-Step "Verifying Free Claude Code entry points were removed"
Confirm-FccCommandsRemoved
Write-Step "Purging FCC config and data from ~/.fcc"
Purge-FccHome
Write-Host ""
if ($DryRun) {
Write-Host "Dry run complete. No changes were made."
}
else {
Write-Host "Free Claude Code has been removed and verified."
Write-Host "uv, Claude Code, Codex, Pi, the uv-managed Python runtime, and shared PATH entries were left installed."
}