SCRIPT LIBRARY · POWERSHELL
Install the Grok CLI on Windows Without Pasting Your API Key into a Script
Install Bun if it's missing, install the open-source Grok CLI globally, and save your xAI API key as a user environment variable from a hidden prompt instead of a line in a script.
- What it does
- Installs Bun with its official installer if needed, installs or updates the open-source Grok CLI package globally with Bun (or npm), prompts for your xAI API key without echoing it and stores it as the GROK_API_KEY user environment variable, then checks the grok command runs.
- Requires
- Windows PowerShell 5.1 or PowerShell 7+
- Internet access to bun.sh and the npm registry
- An xAI API key
- For -UseNpm, Node.js already installed
- Permissions
- A normal user account. Bun, the CLI and the environment variable all go in your own profile, so no admin rights are needed.
- Runs on
- Windows 10/11
- Tested
- Parse-checked and dry-run with -WhatIf in PowerShell 7.4 on Linux, with and without Bun on the PATH and with -UseNpm. The actual installs and the user environment variable are Windows-only and weren't run here.
Grok CLI is an open-source terminal agent for xAI's Grok models, in the same family as the other AI coding CLIs: it runs in your project folder, reads and edits files, and runs commands. Getting it going on Windows is three small jobs. Install Bun, install the package, give it your API key.
My original setup was a batch file, and like a lot of quick setup scripts it had the API key typed straight into it, then wrote it into a settings file too. That's the part worth fixing. This version asks for the key at a hidden prompt, stores it once as a user environment variable, and never takes it as a parameter, so it doesn't end up in your console history or in the script.
To be clear about what that buys you: an environment variable is still plain text in your user profile, readable by anything running as you. It's the normal place for a CLI key, and much better than a script you might share, but it isn't a vault.
<#
.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
Parameters
| Parameter | Type | Default | What it's for |
|---|---|---|---|
-PackageName | string | grok-dev | The npm package to install. Older guides use @vibe-kit/grok-cli; grok-dev is the current name. |
-UseNpm | switch | — | Install with npm instead of Bun. Node.js has to be installed already. |
-SkipApiKey | switch | — | Install or update the CLI only, and leave GROK_API_KEY alone. |
-ReplaceApiKey | switch | — | Prompt for a new key even if GROK_API_KEY is already set. Use it when you rotate the key. |
Run it
First-time setup. You'll be asked for the key once.
.\Install-GrokCli.ps1See what it would do without installing anything.
.\Install-GrokCli.ps1 -WhatIfUpdate the CLI later without touching the key.
.\Install-GrokCli.ps1 -SkipApiKeyRotate the key after revoking the old one in the xAI console (it updates the CLI while it's there).
.\Install-GrokCli.ps1 -ReplaceApiKeyWhat you'll see
bun add v1.3.13
installed [email protected] with binaries:
- grok
Paste your xAI API key (input is hidden): ********
Installer : bun
InstallerVersion : 1.3.13
Package : grok-dev
PackageStatus : Installed
GrokPath : C:\Users\jdoe\.bun\bin\grok.exe
GrokVersion : 1.1.7
ApiKey : Set
How it works
- Find or install Bun. It checks the PATH, then
%USERPROFILE%\.bun\binin case Bun was installed but this session hasn't picked it up yet. If there's no Bun, it downloads Bun's officialinstall.ps1to a temp file, runs it and cleans up. With-UseNpmit checks for npm instead and stops with a clear message if Node isn't installed. - Install the CLI.
bun add -g grok-dev(ornpm install -g) installs or updates it, and a non-zero exit code stops the script instead of carrying on half-installed. - Ask for the key. If
GROK_API_KEYis already set for your user, it's left alone unless you pass-ReplaceApiKey. OtherwiseRead-Host -AsSecureStringprompts with the input hidden, and the key is saved with[Environment]::SetEnvironmentVariable(..., 'User')and set for the current session. An empty answer changes nothing. - Check it runs. It looks for
grokon the PATH and asks for its version, then returns a summary object.-WhatIfwalks through every step without installing or prompting.
Take it further
- Keep keys out of prompts, too. Storing the key well is half of it; the other half is not pasting secrets into the conversation. I wrote about that in Keeping Secrets Out of Prompts.
- Run more than one agent. If you end up with several AI CLIs, how I build with three Claude Code agents at once covers keeping them out of each other's way.
- Pin a model. The CLI also reads
GROK_MODEL. Set it the same way, with[Environment]::SetEnvironmentVariable('GROK_MODEL', '<model>', 'User'), if you always use the same one.
Things that'll trip you up
- New terminals only. Windows hands environment variables to programs when they start. The script sets GROK_API_KEY for its own session too, but terminals and editors that were already open won't see it until you restart them.
- The key is plain text in your profile. User environment variables live in HKCU\Environment. Anything running as you can read them, and so can anyone with admin rights on the machine. Don't do this on a shared or untrusted PC, and revoke the key in the xAI console if the machine is ever lost.
- One key, one place. If an older setup wrote an apiKey into %USERPROFILE%\.grok\user-settings.json, take it out, so there's only one copy to rotate and no stale key hiding in a JSON file.
- The Bun installer runs from the internet. The script downloads bun.sh/install.ps1 and runs it, which is Bun's documented install method. If that's not allowed where you are, install Bun with winget install Oven-sh.Bun or use -UseNpm, then run the script again.