param( [string]$Source ) $ErrorActionPreference = 'Stop' if ($Source -and -not (Test-Path -LiteralPath $Source -PathType Leaf)) { throw "Client file not found: $Source" } $nodeCommand = Get-Command node -ErrorAction SilentlyContinue if (-not $nodeCommand) { throw 'Install Node.js 22 or newer on this laptop, then rerun this installer.' } $nodeVersion = (& node --version).Trim() if ($nodeVersion -notmatch '^v(\d+)\.') { throw "Could not read Node.js version: $nodeVersion" } $nodeMajor = [int]$Matches[1] if ($nodeMajor -lt 22) { throw "Whisper terminal needs Node.js 22 or newer (found $nodeMajor)." } $appDir = Join-Path $env:LOCALAPPDATA 'WhisperTerminal' $binDir = Join-Path $appDir 'bin' $target = Join-Path $binDir 'whisper.cmd' $existing = Get-Command whisper -ErrorAction SilentlyContinue if ((Test-Path -LiteralPath $target) -and -not ((Get-Content -LiteralPath $target -TotalCount 2) -contains 'rem Whisper terminal launcher v1')) { throw "Another command already exists at $target. Nothing was overwritten." } if ($existing -and $existing.Source -ne $target) { Write-Host "An existing whisper command was found at $($existing.Source)." Write-Host 'The other command and its files will stay intact.' $answer = Read-Host 'Install this Whisper launcher and give it PATH priority? [y/N]' if ($answer -notmatch '^(y|yes)$') { Write-Host 'Installation cancelled.' exit 0 } } $downloaded = $null try { if ($Source) { $client = $Source } else { $downloaded = Join-Path $env:TEMP ("whisper-terminal-$([guid]::NewGuid().ToString('N')).mjs") Invoke-WebRequest -Uri 'https://whisper.rachida.dev/downloads/whisper.mjs' ` -OutFile $downloaded -UseBasicParsing $client = $downloaded } $firstLine = Get-Content -LiteralPath $client -TotalCount 1 if ($firstLine -ne '#!/usr/bin/env node') { throw 'The downloaded file is not the Whisper terminal client.' } New-Item -ItemType Directory -Path $binDir -Force | Out-Null Copy-Item -LiteralPath $client -Destination (Join-Path $appDir 'whisper.mjs') -Force $launcher = @' @echo off rem Whisper terminal launcher v1 node "%LOCALAPPDATA%\WhisperTerminal\whisper.mjs" %* '@ $temporary = "$target.tmp" [IO.File]::WriteAllText($temporary, $launcher) Move-Item -LiteralPath $temporary -Destination $target -Force & node (Join-Path $appDir 'whisper.mjs') server 'https://whisper-api.rachida.dev' } finally { if ($downloaded) { Remove-Item -LiteralPath $downloaded -Force -ErrorAction SilentlyContinue } } $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') $entries = @($userPath -split ';' | Where-Object { $_ -and $_ -ne $binDir }) $pathEntries = @($binDir) + $entries [Environment]::SetEnvironmentVariable('Path', ($pathEntries -join ';'), 'User') Write-Host 'Installed Whisper terminal. Open a new terminal, then run: whisper' if ($existing -and $existing.Source -ne $target) { Write-Host "Previous command: $($existing.Source) (still installed)." Write-Host 'If it still runs first, put WhisperTerminal\bin before its directory in PATH.' }