# KinetixAI command line (kx) installer for Windows (PowerShell 5.1+ or 7+). # # irm https://kinetixai.ai/install.ps1 | iex # # What it does, and nothing else: # 1. finds Python 3.10+ (py launcher, then python); it never installs Python # and never needs Administrator # 2. downloads the kx wheel published with the site, checks its SHA-256, # and keeps it in %USERPROFILE%\.kinetixai\wheels (pipx needs it there # later for `pipx reinstall` / `pipx upgrade`) # 3. installs it with pipx if you have it, otherwise into its own virtual # environment at %USERPROFILE%\.kinetixai\venv, and adds that Scripts # folder to your *user* PATH (set KX_NO_MODIFY_PATH=1 to skip) # # Options (environment variables): KX_EXTRAS (default "rag,mcp"; "" for the # bare CLI), KX_SOURCE (site | pypi | git; PyPI only when asked, kx is not # published there yet), KX_WHEEL (local wheel path), KX_HOME (default # %USERPROFILE%\.kinetixai), KX_NO_PIPX=1 (use the venv even with pipx). # # Everything runs inside Install-Kx, called on the last line, so a download # cut off part-way runs nothing and `iex` leaves no settings behind. function Install-Kx { $ErrorActionPreference = 'Stop' Set-StrictMode -Version 2.0 [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 function Say([string]$msg) { Write-Host "kx-install: $msg" } function Fail([string]$msg) { Write-Host "kx-install: error: $msg" -ForegroundColor Red; throw "kx install failed" } function EnvOr([string]$name, [string]$default) { $v = [Environment]::GetEnvironmentVariable($name) if ($null -eq $v) { return $default } else { return $v } } $BaseUrl = (EnvOr 'KX_BASE_URL' 'https://kinetixai.ai').TrimEnd('/') $Extras = EnvOr 'KX_EXTRAS' 'rag,mcp' $Source = EnvOr 'KX_SOURCE' 'site' $KxHome = EnvOr 'KX_HOME' (Join-Path $env:USERPROFILE '.kinetixai') $Wheel = EnvOr 'KX_WHEEL' '' $Wheels = Join-Path $KxHome 'wheels' $GitUrl = 'git+https://github.com/kinetixai-ai/kx.git' # Parse the base URL exactly: http only for a loopback host, never with # userinfo (http://localhost@evil) or a look-alike (http://localhost.evil). $u = $null if (-not [Uri]::TryCreate($BaseUrl, [UriKind]::Absolute, [ref]$u)) { Fail 'KX_BASE_URL is not a URL.' } if ($u.UserInfo) { Fail 'KX_BASE_URL must not contain a user name.' } if ($u.Scheme -eq 'http') { if (-not ($u.IsLoopback -and @('localhost', '127.0.0.1', '[::1]') -contains $u.Host.ToLower())) { Fail 'KX_BASE_URL must be https (plain http only for localhost testing).' } } elseif ($u.Scheme -ne 'https') { Fail 'KX_BASE_URL must be an https URL.' } if (@('site', 'pypi', 'git') -notcontains $Source) { Fail 'KX_SOURCE must be site, pypi or git.' } # ── 1. Python 3.10+ ──────────────────────────────────────────────────── # The Microsoft Store "python.exe" stub opens the Store instead of running, # so every candidate is actually executed and its version checked. $Py = $null $candidates = @(@('py', '-3'), @('python'), @('python3')) foreach ($c in $candidates) { $exe = $c[0] if (-not (Get-Command $exe -ErrorAction SilentlyContinue)) { continue } $pre = @() if ($c.Length -gt 1) { $pre = $c[1..($c.Length - 1)] } try { $out = & $exe @pre -c "import sys; print(sys.executable if sys.version_info >= (3, 10) else '')" 2>$null if ($LASTEXITCODE -eq 0 -and $out -and (Test-Path ([string]$out).Trim())) { $Py = ([string]$out).Trim(); break } } catch { continue } } if (-not $Py) { Fail "Python 3.10 or newer is required and was not found. Install it with: winget install Python.Python.3.12 or from https://www.python.org/downloads/ (tick 'Add python.exe to PATH'). Then open a new PowerShell window and run this installer again." } $pyver = & $Py -c "import sys; print('%d.%d.%d' % sys.version_info[:3])" Say "using Python $pyver at $Py" $Tmp = Join-Path ([IO.Path]::GetTempPath()) ("kxinstall-" + [Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $Tmp | Out-Null try { if ($Extras) { $Suffix = "[$Extras]" } else { $Suffix = '' } # ── 2. what to install ─────────────────────────────────────────────── $Spec = $null $Downloaded = $null $Name = $null if ($Wheel) { if (-not (Test-Path $Wheel)) { Fail "KX_WHEEL=$Wheel does not exist." } $Name = Split-Path -Leaf $Wheel if ($Name -notmatch '^kinetixai-[A-Za-z0-9_.\-]+\.whl$') { Fail 'KX_WHEEL must be a kinetixai-*.whl file.' } $Downloaded = Join-Path $Tmp $Name Copy-Item -Path $Wheel -Destination $Downloaded Say "installing local wheel $Wheel" } elseif ($Source -eq 'site') { $latest = $null try { $latest = (Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/cli/dl/latest.txt").Content } catch { $latest = $null } if (-not $latest) { Fail "could not read $BaseUrl/cli/dl/latest.txt. Check your connection and try again. (This installer does not fall back to it on its own; run again with KX_SOURCE=pypi to install from PyPI.)" } if ($latest -is [byte[]]) { $latest = [Text.Encoding]::UTF8.GetString($latest) } $parts = ([string]$latest).Trim().Split(@(' ', "`t", "`n", "`r"), [StringSplitOptions]::RemoveEmptyEntries) if ($parts.Length -lt 2) { Fail "unexpected content in $BaseUrl/cli/dl/latest.txt" } $rel = $parts[0]; $want = $parts[1].ToLower() # "/kinetixai--py3-none-any.whl": immutable per build. if ($rel -notmatch '^[0-9a-f]{12}/kinetixai-[A-Za-z0-9_.\-]+\.whl$') { Fail "unexpected entry in $BaseUrl/cli/dl/latest.txt" } if ($want -notmatch '^[0-9a-f]{64}$') { Fail "bad checksum in $BaseUrl/cli/dl/latest.txt" } $Name = $rel.Split('/')[1] Say "downloading $Name" $Downloaded = Join-Path $Tmp $Name Invoke-WebRequest -UseBasicParsing -Uri "$BaseUrl/cli/dl/$rel" -OutFile $Downloaded $got = (Get-FileHash -Algorithm SHA256 -Path $Downloaded).Hash.ToLower() if ($got -ne $want) { Fail "checksum mismatch for $Name (expected $want, got $got). Not installing." } Say "checksum ok ($got)" } if ($Downloaded) { # Keep the wheel where pipx can find it again: pipx records this path # and re-reads it on `pipx reinstall` and `pipx upgrade`. New-Item -ItemType Directory -Force -Path $Wheels | Out-Null $Kept = Join-Path $Wheels $Name Move-Item -Force -Path $Downloaded -Destination $Kept $Spec = "kinetixai$Suffix @ " + ([Uri]$Kept).AbsoluteUri } elseif ($Source -eq 'pypi') { Say 'installing from PyPI because KX_SOURCE=pypi' $Spec = "kinetixai$Suffix" } else { $Spec = "kinetixai$Suffix @ $GitUrl" } # ── 3. install ─────────────────────────────────────────────────────── $Kx = $null if (((EnvOr 'KX_NO_PIPX' '') -ne '1') -and (Get-Command pipx -ErrorAction SilentlyContinue)) { # Replace an existing kx cleanly (`--force` can't reuse a venv made by # another pipx backend). Settings, library and token are kept. $listed = (& pipx list --short 2>$null) -join "`n" if ($listed -match '(?m)^kinetixai ') { Say 'replacing the kx already installed with pipx' & pipx uninstall kinetixai | Out-Null if ($LASTEXITCODE -ne 0) { Fail 'pipx could not remove the old kx. Run: pipx uninstall kinetixai' } } Say 'installing with pipx' & pipx install --python $Py $Spec if ($LASTEXITCODE -ne 0) { Fail 'pipx could not install kx. See the messages above.' } & pipx ensurepath | Out-Null $pipxBin = $null try { $pipxBin = (& pipx environment --value PIPX_BIN_DIR 2>$null | Select-Object -First 1) } catch { $pipxBin = $null } if (-not $pipxBin) { $pipxBin = Join-Path $env:USERPROFILE '.local\bin' } $pipxBin = ([string]$pipxBin).Trim() $Kx = Join-Path $pipxBin 'kx.exe' $env:Path = "$pipxBin;$env:Path" } else { $Venv = Join-Path $KxHome 'venv' Say "installing into $Venv" & $Py -m venv $Venv if ($LASTEXITCODE -ne 0) { Fail 'could not create a virtual environment.' } $VPy = Join-Path $Venv 'Scripts\python.exe' & $VPy -m pip install --quiet --disable-pip-version-check --upgrade $Spec if ($LASTEXITCODE -ne 0) { Fail 'pip could not install kx. See the messages above.' } $Scripts = Join-Path $Venv 'Scripts' $Kx = Join-Path $Scripts 'kx.exe' Add-UserPath $Scripts $env:Path = "$Scripts;$env:Path" } if (-not (Test-Path $Kx)) { Fail "kx was installed but $Kx was not found. Open a new terminal and run: kx doctor" } $ver = & $Kx --version if ($LASTEXITCODE -ne 0) { Fail "kx was installed but does not run. Try: $Kx doctor" } Say "installed $ver" # Older wheels are no longer referenced by any install. if ($Downloaded) { Get-ChildItem -Path $Wheels -Filter 'kinetixai-*.whl' | Where-Object { $_.Name -ne $Name } | Remove-Item -Force -ErrorAction SilentlyContinue } Say "next steps: kx doctor check everything is ready kx code 2205.14135 find code for a paper kx library add 1706.03762 start your local library kx login optional: token from $BaseUrl/account/tokens/" } finally { Remove-Item -Recurse -Force $Tmp -ErrorAction SilentlyContinue } } # Add a folder to the *user* PATH without expanding what is already there. # [Environment]::SetEnvironmentVariable would rewrite entries such as # %USERPROFILE%\bin as fixed paths and turn REG_EXPAND_SZ into REG_SZ, so # this reads and writes the raw registry value and keeps its kind. function Add-UserPath([string]$Dir) { if ((Get-Item Env:KX_NO_MODIFY_PATH -ErrorAction SilentlyContinue) -and $env:KX_NO_MODIFY_PATH -eq '1') { Write-Host "kx-install: add this folder to your PATH to use kx anywhere: $Dir" return } $key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true) if ($null -eq $key) { $key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment') } try { $raw = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) $kind = [Microsoft.Win32.RegistryValueKind]::ExpandString if ($key.GetValueNames() -contains 'Path') { $kind = $key.GetValueKind('Path') } $entries = @($raw.Split(';') | Where-Object { $_ -ne '' }) $present = $entries | Where-Object { [Environment]::ExpandEnvironmentVariables($_).TrimEnd('\') -ieq $Dir.TrimEnd('\') } if ($present) { return } $new = (@($entries) + $Dir) -join ';' $key.SetValue('Path', $new, $kind) } finally { $key.Close() } # Tell Explorer and new terminals that the environment changed # (setting and clearing a throwaway variable broadcasts WM_SETTINGCHANGE). [Environment]::SetEnvironmentVariable('KX_INSTALL_REFRESH', '1', 'User') [Environment]::SetEnvironmentVariable('KX_INSTALL_REFRESH', $null, 'User') Write-Host "kx-install: added $Dir to your user PATH (open a new terminal to pick it up)" } Install-Kx