# Hugging Face CLI Installer for Windows # Usage: powershell -ExecutionPolicy ByPass -c "irm https://hf.co/cli/install.ps1 | iex" # Or: curl -LsSf https://hf.co/cli/install.ps1 | pwsh - <# .SYNOPSIS Installs the Hugging Face CLI on Windows by creating an isolated virtual environment and exposing the `hf` command. .DESCRIPTION Downloads and installs the `huggingface_hub` package into a dedicated virtual environment, then copies the generated `hf.exe` console script to a directory on the user's PATH. .PARAMETER Force Recreates the virtual environment even if it already exists. Off by default. .PARAMETER Verbose Enables verbose output, including detailed pip logs. .PARAMETER NoModifyPath Skips PATH modifications; `hf` must be invoked via its full path unless you add it manually. .PARAMETER WithTransformers Also install the transformers CLI. .PARAMETER ExcludeSkill Skip installing the `hf-cli` skill for AI agents. .EXAMPLE powershell -c "irm https://hf.co/cli/install.ps1 | iex" powershell -c "& ([scriptblock]::Create((irm https://hf.co/cli/install.ps1))) -WithTransformers" #> <# .NOTES Environment variables: HF_HOME Installation base directory; installer uses $env:HF_HOME\cli when set HF_CLI_BIN_DIR Directory for the hf wrapper (default: $env:USERPROFILE\.local\bin) #> param( [switch]$Force = $false, [switch]$Verbose, [switch]$NoModifyPath, [switch]$WithTransformers = $false, [switch]$ExcludeSkill = $false ) $script:LogLevel = if ($Verbose) { 2 } else { 1 } $script:PathUpdated = $false if ($Verbose) { $env:HF_CLI_VERBOSE_PIP = '1' } # Set error action preference $ErrorActionPreference = "Stop" # Colors for output $Colors = @{ Red = 'Red' Green = 'Green' Yellow = 'Yellow' Blue = 'Blue' White = 'White' } function Write-Log { param( [string]$Message, [string]$Level = "INFO" ) $timestamp = Get-Date -Format "HH:mm:ss" switch ($Level) { "DEBUG" { if ($script:LogLevel -lt 2) { return } Write-Host "[$timestamp] [DEBUG] $Message" -ForegroundColor $Colors.Blue } "INFO" { if ($script:LogLevel -lt 1) { return } Write-Host "[$timestamp] [INFO] $Message" -ForegroundColor $Colors.Blue } "SUCCESS" { Write-Host "[$timestamp] [SUCCESS] $Message" -ForegroundColor $Colors.Green } "WARNING" { Write-Host "[$timestamp] [WARNING] $Message" -ForegroundColor $Colors.Yellow } "ERROR" { Write-Host "[$timestamp] [ERROR] $Message" -ForegroundColor $Colors.Red } } } # Normalize user-supplied paths function Resolve-CliPath { param([string]$Path) if (-not $Path) { return $null } $expanded = [Environment]::ExpandEnvironmentVariables($Path) if ($expanded -eq '~') { return $env:USERPROFILE } if ($expanded -like '~\*') { $suffix = $expanded.Substring(2) if ([string]::IsNullOrWhiteSpace($suffix)) { return $env:USERPROFILE } return (Join-Path $env:USERPROFILE $suffix) } if ([System.IO.Path]::IsPathRooted($expanded)) { return [System.IO.Path]::GetFullPath($expanded) } $base = (Get-Location).ProviderPath return [System.IO.Path]::GetFullPath((Join-Path $base $expanded)) } # Compose installer directories using environment variables only if ($env:HF_HOME) { $HF_CLI_DIR = Resolve-CliPath (Join-Path $env:HF_HOME "cli") } else { $HF_CLI_DIR = Resolve-CliPath (Join-Path $env:USERPROFILE ".hf-cli") } $VENV_DIR = Join-Path $HF_CLI_DIR "venv" if ($env:HF_CLI_BIN_DIR) { $BIN_DIR = Resolve-CliPath $env:HF_CLI_BIN_DIR } else { $BIN_DIR = Resolve-CliPath (Join-Path $env:USERPROFILE ".local\bin") } $SCRIPTS_DIR = Join-Path $VENV_DIR "Scripts" $script:VenvPython = $null function Test-Command { param([string]$Command) try { Get-Command $Command -ErrorAction Stop | Out-Null; return $true } catch { return $false } } function Test-PythonVersion { param([string]$PythonCmd) try { $version = & $PythonCmd --version 2>&1 if ($version -match "Python 3\.(\d+)\.") { $minorVersion = [int]$matches[1] return $minorVersion -ge 10 # Python 3.10+ } return $false } catch { return $false } } function Find-Python { Write-Log "Looking for Python 3.10+ installation..." # Try common Python commands $pythonCommands = @("python", "python3", "py") foreach ($cmd in $pythonCommands) { if (Test-Command $cmd) { if (Test-PythonVersion $cmd) { $version = & $cmd --version 2>&1 Write-Log "Found compatible Python: $version using command '$cmd'" return $cmd } } } # Try Python Launcher for Windows if (Test-Command "py") { try { $version = py -3 --version 2>&1 if ($version -match "Python 3\.(\d+)\.") { $minorVersion = [int]$matches[1] if ($minorVersion -ge 10) { Write-Log "Found compatible Python: $version using Python Launcher" return "py -3" } } } catch { } } Write-Log "Python 3.10+ is required but not found." "ERROR" Write-Log "Please install Python from https://python.org or Microsoft Store" "ERROR" Write-Log "Make sure to check 'Add Python to PATH' during installation" "ERROR" throw "Python 3.10+ not found" } function New-Directories { Write-Log "Creating directories..." if (-not (Test-Path $HF_CLI_DIR)) { New-Item -ItemType Directory -Path $HF_CLI_DIR -Force | Out-Null } if (-not (Test-Path $BIN_DIR)) { New-Item -ItemType Directory -Path $BIN_DIR -Force | Out-Null } } function New-VirtualEnvironment { param([string]$PythonCmd) Write-Log "Creating virtual environment..." if (Test-Path $VENV_DIR) { if ($Force) { Write-Log "Virtual environment already exists; removing it since --force was passed" "WARNING" Remove-Item -Path $VENV_DIR -Recurse -Force } else { Write-Log "Virtual environment already exists. Use -Force to recreate." "WARNING" Write-Log "Skipping virtual environment creation..." return } } # Fail fast when venv module is unavailable try { if ($PythonCmd -eq "py -3") { & py -3 -c "import venv" | Out-Null } else { & $PythonCmd -c "import venv" | Out-Null } } catch { Write-Log "Python installation is missing the venv module." "ERROR" Write-Log "Install the optional venv feature or repair Python before retrying." "ERROR" Write-Log "Microsoft Store Python: Repair via Apps settings" "INFO" Write-Log "python.org installer: Choose 'Modify' and enable 'pip/venv'." "INFO" throw "Python venv module unavailable" } # Create virtual environment if ($PythonCmd -eq "py -3") { & py -3 -m venv $VENV_DIR } else { & $PythonCmd -m venv $VENV_DIR } if (-not $?) { throw "Failed to create virtual environment" } # Mark this installation as installer-managed $markerFile = Join-Path $VENV_DIR ".hf_installer_marker" New-Item -Path $markerFile -ItemType File -Force | Out-Null # Use the venv's python -m pip for deterministic upgrades $script:VenvPython = Join-Path $SCRIPTS_DIR "python.exe" Write-Log "Upgrading pip..." & $script:VenvPython -m pip install --upgrade pip if (-not $?) { throw "Failed to upgrade pip" } } function Install-Package { param([string]$PackageSpec) if (-not $script:VenvPython) { $script:VenvPython = Join-Path $SCRIPTS_DIR "python.exe" } $extraArgsRaw = if ($env:HF_CLI_PIP_ARGS) { $env:HF_CLI_PIP_ARGS } else { $env:HF_PIP_ARGS } if ($extraArgsRaw) { Write-Log "Passing extra arguments: $extraArgsRaw" } if ($env:HF_CLI_VERBOSE_PIP -ne '1') { Write-Log "Installation output suppressed; set HF_CLI_VERBOSE_PIP=1 for full logs" } # Check if uv is available and use it for faster installation if (Test-Command "uv") { Write-Log "Using uv for faster installation" $uvArgs = @('pip', 'install', '--python', $script:VenvPython, '--upgrade') if ($env:HF_CLI_VERBOSE_PIP -ne '1') { $uvArgs += '--quiet' } $uvArgs += $PackageSpec if ($extraArgsRaw) { $uvArgs += $extraArgsRaw -split '\s+' } & uv @uvArgs if (-not $?) { throw "Failed to install $PackageSpec with uv" } } else { $pipArgs = @('-m', 'pip', 'install', '--upgrade') if ($env:HF_CLI_VERBOSE_PIP -ne '1') { $pipArgs += @('--quiet', '--progress-bar', 'off', '--disable-pip-version-check') } $pipArgs += $PackageSpec if ($extraArgsRaw) { $pipArgs += $extraArgsRaw -split '\s+' } & $script:VenvPython @pipArgs if (-not $?) { throw "Failed to install $PackageSpec" } } } function Install-HuggingFaceHub { Write-Log "Installing/upgrading Hugging Face CLI (latest)..." Install-Package -PackageSpec 'huggingface_hub' } function Test-TransformersInstalled { # Use importlib.metadata for speed (avoids loading the full transformers module) if (-not $script:VenvPython) { $script:VenvPython = Join-Path $SCRIPTS_DIR "python.exe" } try { & $script:VenvPython -c "import importlib.metadata; importlib.metadata.version('transformers')" 2>&1 | Out-Null return $? } catch { return $false } } function Install-Transformers { # Install if -WithTransformers was passed OR if transformers is already in the venv if (-not $WithTransformers -and -not (Test-TransformersInstalled)) { return } Write-Log "Installing/upgrading transformers CLI (latest)..." Install-Package -PackageSpec 'transformers' } function Publish-HfCommand { Write-Log "Copying hf CLI launcher..." $hfExeSource = Join-Path $SCRIPTS_DIR "hf.exe" if (-not (Test-Path $hfExeSource)) { throw "hf.exe not found in virtual environment. Check that The Hugging Face CLI installed correctly." } $hfExeTarget = Join-Path $BIN_DIR "hf.exe" Copy-Item -Path $hfExeSource -Destination $hfExeTarget -Force $hfScriptSource = Join-Path $SCRIPTS_DIR "hf-script.py" if (Test-Path $hfScriptSource) { Copy-Item -Path $hfScriptSource -Destination (Join-Path $BIN_DIR "hf-script.py") -Force } Write-Log "hf CLI available at $hfExeTarget" Write-Log ('Run without updating PATH: & "{0}" --help' -f $hfExeTarget) } function Install-Skill { # Opt-out with -ExcludeSkill if ($ExcludeSkill) { Write-Log "Skipping the hf-cli skill (-ExcludeSkill)" return } Write-Log "Installing the hf-cli skill for AI agents..." $hfExecutable = Join-Path $BIN_DIR "hf.exe" & $hfExecutable skills add hf-cli --global --force if (-not $?) { Write-Log "Failed to install the hf-cli skill. Install it later with: hf skills add -g" "WARNING" return } Write-Log "The hf-cli skill was installed automatically so AI agents know how to use the hf CLI." Write-Log "Pass -ExcludeSkill to skip it." } function Update-Path { Write-Log "Checking PATH configuration..." # Get current user PATH $currentPath = [Environment]::GetEnvironmentVariable("PATH", "User") if ($currentPath -notlike "*$BIN_DIR*") { Write-Log "Adding $BIN_DIR to user PATH..." try { $newPath = "$BIN_DIR;" + $currentPath [Environment]::SetEnvironmentVariable("PATH", $newPath, "User") # Update PATH for current session $env:PATH = "$BIN_DIR;$env:PATH" Write-Log "Added $BIN_DIR to PATH. Changes will take effect in new terminals." "SUCCESS" Write-Log "Current PowerShell session already includes hf after this update." "INFO" Write-Log "Undo later via Settings ▸ Environment Variables, or: [Environment]::SetEnvironmentVariable(`"PATH`", ($([Environment]::GetEnvironmentVariable('PATH','User')) -replace [regex]::Escape(`"$BIN_DIR;`"), ''), 'User')" "INFO" $script:PathUpdated = $true } catch { Write-Log "Failed to update PATH automatically. Please add manually:" "WARNING" Write-Log @" Run: [Environment]::SetEnvironmentVariable("PATH", "$BIN_DIR;$([Environment]::GetEnvironmentVariable('PATH','User'))", 'User') "@ "WARNING" } } else { Write-Log "PATH already contains $BIN_DIR" } } function Test-Installation { Write-Log "Verifying installation..." $hfExecutable = Join-Path $BIN_DIR "hf.exe" if (Test-Path $hfExecutable) { try { # Test the CLI $output = & $hfExecutable version --format quiet 2>&1 if ($?) { Write-Log "CLI location: $hfExecutable" Write-Log "Installation directory: $HF_CLI_DIR" return $true } else { Write-Log "Installation verification failed. The hf command is not working properly." "ERROR" Write-Log "Error output: $output" "ERROR" return $false } } catch { Write-Log "Installation verification failed: $($_.Exception.Message)" "ERROR" return $false } } else { Write-Log "Installation failed. hf.exe not found in $BIN_DIR." "ERROR" return $false } } function Show-UninstallInfo { Write-Log "" Write-Log "To uninstall the Hugging Face CLI:" Write-Log " Remove-Item -Path '$HF_CLI_DIR' -Recurse -Force" Write-Log " Remove-Item -Path '$BIN_DIR\hf.exe'" Write-Log " Remove-Item -Path '$BIN_DIR\hf-script.py' (if present)" Write-Log "" if ($script:PathUpdated) { Write-Log "Remove '$BIN_DIR' from your user PATH via Settings ▸ Environment Variables," "INFO" Write-Log "or run: [Environment]::SetEnvironmentVariable(`"PATH`", ($([Environment]::GetEnvironmentVariable('PATH','User')) -replace [regex]::Escape(`"$BIN_DIR;`"), ''), 'User')" "INFO" } elseif ($NoModifyPath) { Write-Log 'PATH was not modified (--no-modify-path).' 'INFO' } else { Write-Log "If you added '$BIN_DIR' to PATH manually, remove it when finished." "INFO" } } function Show-Usage { Write-Log '' Write-Log 'Usage examples:' Write-Log ' hf auth login' Write-Log ' hf download deepseek-ai/DeepSeek-R1' Write-Log ' hf jobs run python:3.12 python -c ''print("Hello from HF CLI!")''' Write-Log '' Write-Log "The 'hf' command is now available." 'SUCCESS' Write-Log 'Please **close and reopen** your terminal to use it directly, for example: `hf --help`' 'INFO' Write-Log 'Alternatively, you can test it immediately in this session by using the full path:' 'INFO' $hfExecutable = Join-Path $BIN_DIR 'hf.exe' Write-Log (' & "{0}" --help' -f $hfExecutable) } # Main installation process function Main { try { Write-Log "Installing Hugging Face CLI for Windows..." Write-Log "PowerShell version: $($PSVersionTable.PSVersion)" $pythonCmd = Find-Python New-Directories New-VirtualEnvironment -PythonCmd $pythonCmd Install-HuggingFaceHub Install-Transformers Publish-HfCommand Install-Skill if ($NoModifyPath) { Write-Log 'Skipping PATH modification (--no-modify-path).' } else { Update-Path } if (Test-Installation) { $hfExecutable = Join-Path $BIN_DIR "hf.exe" Show-Usage Show-UninstallInfo Write-Log "hf CLI ready!" "SUCCESS" Write-Log "Binary: $hfExecutable" Write-Log "Virtualenv: $HF_CLI_DIR" } else { throw "Installation verification failed" } } catch { Write-Log "Installation failed: $($_.Exception.Message)" "ERROR" exit 1 } } # Handle Ctrl+C $null = Register-ObjectEvent -InputObject ([Console]) -EventName CancelKeyPress -Action { Write-Log "Installation interrupted" "ERROR" exit 130 } # Run main function Main