<# .SYNOPSIS Installs the open-source Grok CLI with Bun and stores your xAI API key as a user environment variable. .DESCRIPTION 1. Checks for Bun and installs it with the official installer if it's missing (or uses npm with -UseNpm). 2. Installs or updates the Grok CLI package globally (grok-dev by default; older guides call it @vibe-kit/grok-cli). 3. Prompts for your xAI API key without echoing it, and saves it as the GROK_API_KEY user environment variable. It's never taken as a parameter, so it doesn't end up in your console history. 4. Checks that the grok command runs and returns a summary object. Like any environment variable, the key is stored in plain text in your user profile (HKCU\Environment). Anything running as you can read it. Supports -WhatIf. .PARAMETER PackageName The npm package to install. Default: grok-dev. .PARAMETER UseNpm Install with npm instead of Bun. Needs Node.js already installed. .PARAMETER SkipApiKey Install the CLI only and leave GROK_API_KEY alone. .PARAMETER ReplaceApiKey Prompt for a new key even if GROK_API_KEY is already set. .EXAMPLE .\Install-GrokCli.ps1 .EXAMPLE .\Install-GrokCli.ps1 -ReplaceApiKey .EXAMPLE .\Install-GrokCli.ps1 -UseNpm -SkipApiKey -WhatIf #> [CmdletBinding(SupportsShouldProcess)] param( [ValidatePattern('^(@[a-z0-9._-]+/)?[a-z0-9._-]+(@[\w.^~-]+)?$')][string]$PackageName = 'grok-dev', [switch]$UseNpm, [switch]$SkipApiKey, [switch]$ReplaceApiKey ) $ErrorActionPreference = 'Stop' $summary = [ordered]@{ Installer = $null; InstallerVersion = $null; Package = $PackageName; PackageStatus = 'Skipped'; GrokPath = $null; GrokVersion = $null; ApiKey = 'Unchanged' } $bunBin = Join-Path $HOME '.bun\bin' function Get-ToolVersion([string]$Name) { $cmd = Get-Command $Name -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $cmd) { return $null } try { (& $cmd.Source --version 2>$null | Select-Object -First 1) } catch { $null } } # 1. Bun (or npm). if ($UseNpm) { $summary.Installer = 'npm' $summary.InstallerVersion = Get-ToolVersion 'npm' if (-not $summary.InstallerVersion) { throw 'npm not found. Install Node.js LTS first, or run without -UseNpm to use Bun.' } } else { $summary.Installer = 'bun' if (-not (Get-Command bun -ErrorAction SilentlyContinue) -and (Test-Path -LiteralPath (Join-Path $bunBin 'bun.exe'))) { $env:Path = "$env:Path;$bunBin" # installed earlier, but this session hasn't picked up the PATH change yet } $summary.InstallerVersion = Get-ToolVersion 'bun' if (-not $summary.InstallerVersion -and $PSCmdlet.ShouldProcess('Bun', 'Install with the official installer from bun.sh')) { $installer = Join-Path ([IO.Path]::GetTempPath()) "bun-install-$([guid]::NewGuid().ToString('N')).ps1" try { Invoke-WebRequest -Uri 'https://bun.sh/install.ps1' -OutFile $installer -UseBasicParsing & $installer } finally { Remove-Item -LiteralPath $installer -ErrorAction SilentlyContinue } if (-not (Get-Command bun -ErrorAction SilentlyContinue)) { $env:Path = "$env:Path;$bunBin" } $summary.InstallerVersion = Get-ToolVersion 'bun' if (-not $summary.InstallerVersion) { throw 'Bun install finished but bun still is not on PATH. Open a new terminal and run this again, or install Bun from bun.sh.' } } } # 2. The CLI itself. if ($PSCmdlet.ShouldProcess($PackageName, "Install globally with $($summary.Installer)")) { if ($UseNpm) { & npm install -g $PackageName } else { & bun add -g $PackageName } if ($LASTEXITCODE -ne 0) { throw "$($summary.Installer) exited with code $LASTEXITCODE while installing $PackageName." } $summary.PackageStatus = 'Installed' if (-not $UseNpm -and ($env:Path -split ';') -notcontains $bunBin) { $env:Path = "$env:Path;$bunBin" } } # 3. The API key: prompted, never a parameter, never echoed. if (-not $SkipApiKey) { $existing = [Environment]::GetEnvironmentVariable('GROK_API_KEY', 'User') if ($existing -and -not $ReplaceApiKey) { $summary.ApiKey = 'AlreadySet' Write-Verbose 'GROK_API_KEY is already set for this user. Use -ReplaceApiKey to change it.' } elseif ($PSCmdlet.ShouldProcess('GROK_API_KEY (user environment variable)', 'Set from a secure prompt')) { $secure = Read-Host -Prompt 'Paste your xAI API key (input is hidden)' -AsSecureString $plain = [Net.NetworkCredential]::new('', $secure).Password try { if ([string]::IsNullOrWhiteSpace($plain)) { throw 'No key entered. Nothing was changed.' } [Environment]::SetEnvironmentVariable('GROK_API_KEY', $plain.Trim(), 'User') $env:GROK_API_KEY = $plain.Trim() $summary.ApiKey = if ($existing) { 'Replaced' } else { 'Set' } } finally { $plain = $null; $secure.Dispose() } } } # 4. Does it run? $grok = Get-Command grok -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 if ($grok) { $summary.GrokPath = $grok.Source $summary.GrokVersion = Get-ToolVersion 'grok' } elseif ($summary.PackageStatus -eq 'Installed') { Write-Warning "grok isn't on PATH in this session yet. Open a new terminal and run: grok --version" } [pscustomobject]$summary