# Okibi installer for Windows (PowerShell). # # irm https://okibi.ai/install.ps1 | iex # # Installs the public okibi thin client (okibi.exe + okibi-tui.exe) and its # Agent Skill. Public release host, no auth. Detects the release, downloads + # SHA-256 verifies the zip, extracts under %LOCALAPPDATA%\okibi, puts okibi on # your PATH, installs the skill under ~/.agents/skills, and exposes it to # detected agents through their native skill directories. Re-run to upgrade # the client and its managed skill to whatever latest.json now points at. # # Env overrides: OKIBI_RELEASE_BASE, OKIBI_INSTALL_ROOT, OKIBI_BIN_DIR, # OKIBI_SKILLS_DIR. # # The public /install.ps1 route serves this source verbatim. The release base # is baked to prod; override with $env:OKIBI_RELEASE_BASE for staging. & { $ErrorActionPreference = 'Stop' $ReleaseBase = if ($env:OKIBI_RELEASE_BASE) { $env:OKIBI_RELEASE_BASE } else { 'https://releases.okibi.ai' } $InstallRoot = if ($env:OKIBI_INSTALL_ROOT) { $env:OKIBI_INSTALL_ROOT } else { Join-Path $env:LOCALAPPDATA 'okibi' } $BinDir = if ($env:OKIBI_BIN_DIR) { $env:OKIBI_BIN_DIR } else { Join-Path $InstallRoot 'bin' } $SkillsDir = if ($env:OKIBI_SKILLS_DIR) { $env:OKIBI_SKILLS_DIR } else { Join-Path $env:USERPROFILE '.agents\skills' } function Info($m) { Write-Host "install: $m" } function Fail($m) { throw "install: $m" } function Remove-ManagedSkillPath($path) { $item = Get-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue if ($null -eq $item) { return } if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { # Windows PowerShell 5.1 follows directory junctions when Remove-Item is # given -Recurse. DirectoryInfo.Delete() removes only the reparse point. $item.Delete() } else { Remove-Item -LiteralPath $path -Recurse -Force } } function Install-AgentSkillForDetectedClient($agentName, $agentHome, $skillName, $skillDest, $owner) { if (-not (Test-Path -LiteralPath $agentHome -PathType Container)) { return } $agentSkills = Join-Path $agentHome 'skills' $agentDest = Join-Path $agentSkills $skillName if ([System.StringComparer]::OrdinalIgnoreCase.Equals($agentDest, $skillDest)) { return } $item = Get-Item -LiteralPath $agentDest -Force -ErrorAction SilentlyContinue if ($null -ne $item) { foreach ($target in @($item.Target)) { if ($target -and [System.StringComparer]::OrdinalIgnoreCase.Equals([string]$target, $skillDest)) { return } } $agentMarker = Join-Path $agentDest '.okibi-managed' if ((Test-Path -LiteralPath $agentMarker) -and ((Get-Content -Raw $agentMarker).Trim() -eq $owner)) { Remove-ManagedSkillPath $agentDest } else { Info "NOTE: $agentName skill not linked because $agentDest already exists and is not managed by $owner" return } } New-Item -ItemType Directory -Path $agentSkills -Force | Out-Null try { New-Item -ItemType Junction -Path $agentDest -Target $skillDest -ErrorAction Stop | Out-Null } catch { New-Item -ItemType Directory -Path $agentDest -Force | Out-Null Copy-Item -Path (Join-Path $skillDest '*') -Destination $agentDest -Recurse -Force Copy-Item -Path (Join-Path $skillDest '.okibi-managed') -Destination (Join-Path $agentDest '.okibi-managed') -Force } Info "linked Agent Skill for $agentName at $agentDest" } function Install-DetectedAgentSkills($skillName, $skillDest, $owner) { $claudeHome = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $env:USERPROFILE '.claude' } $codexHome = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $env:USERPROFILE '.codex' } $configHome = if ($env:XDG_CONFIG_HOME) { $env:XDG_CONFIG_HOME } else { Join-Path $env:USERPROFILE '.config' } Install-AgentSkillForDetectedClient 'Claude Code' $claudeHome $skillName $skillDest $owner Install-AgentSkillForDetectedClient 'Codex' $codexHome $skillName $skillDest $owner Install-AgentSkillForDetectedClient 'Cursor' (Join-Path $env:USERPROFILE '.cursor') $skillName $skillDest $owner Install-AgentSkillForDetectedClient 'Gemini CLI' (Join-Path $env:USERPROFILE '.gemini') $skillName $skillDest $owner Install-AgentSkillForDetectedClient 'GitHub Copilot' (Join-Path $env:USERPROFILE '.copilot') $skillName $skillDest $owner Install-AgentSkillForDetectedClient 'Windsurf' (Join-Path $env:USERPROFILE '.codeium\windsurf') $skillName $skillDest $owner Install-AgentSkillForDetectedClient 'OpenCode' (Join-Path $configHome 'opencode') $skillName $skillDest $owner } # Only a windows-x64 build is published; it runs on ARM64 Windows via # emulation, so request it regardless of host arch. $platform = 'windows-x64' Info "platform: $platform" Info "fetching $ReleaseBase/latest.json" try { $manifest = Invoke-RestMethod -Uri "$ReleaseBase/latest.json" -UseBasicParsing } catch { Fail "could not reach the release host at $ReleaseBase" } $version = $manifest.version if (-not $version) { Fail 'latest.json is missing a version field' } # The version becomes a path segment that is Remove-Item -Recurse -Force'd # below, so a hostile or corrupt manifest must not be able to smuggle in # path separators or "..". if ($version -notmatch '^[A-Za-z0-9._+-]+$' -or $version -match '\.\.') { Fail "latest.json has an unsafe version string: $version" } $skillName = 'okibi' $skillDest = Join-Path $SkillsDir $skillName $installedMarker = Join-Path $skillDest '.okibi-managed' $skillManaged = (Test-Path $installedMarker) -and ((Get-Content -Raw $installedMarker).Trim() -eq 'okibi') $artifact = $manifest.artifacts | Where-Object { $_.platform -eq $platform } | Select-Object -First 1 if (-not $artifact) { Fail "no okibi build published for $platform yet" } $url = $artifact.url if ($url -notmatch '^https?://') { $url = "$ReleaseBase/$url" } $tmp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ('okibi-' + [guid]::NewGuid())) try { $zip = Join-Path $tmp 'okibi.zip' Info "downloading okibi $version" Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing $actual = (Get-FileHash -Algorithm SHA256 -Path $zip).Hash.ToLower() if ($actual -ne $artifact.sha256.ToLower()) { Fail "checksum mismatch: expected $($artifact.sha256), got $actual" } Info 'checksum verified' $verDir = Join-Path (Join-Path $InstallRoot 'versions') $version if (Test-Path $verDir) { Remove-Item -Recurse -Force $verDir } New-Item -ItemType Directory -Path $verDir | Out-Null Expand-Archive -Path $zip -DestinationPath $tmp -Force # The archive wraps everything in okibi-client--windows-x64\; lift its # contents into the version dir. $inner = Get-ChildItem $tmp -Directory | Select-Object -First 1 Copy-Item -Path (Join-Path $inner.FullName '*') -Destination $verDir -Recurse -Force if (-not (Test-Path (Join-Path $verDir 'okibi.exe'))) { Fail 'archive did not contain okibi.exe' } Info "installed to $verDir" $skillSource = Join-Path (Join-Path $verDir 'skills') $skillName $skillFile = Join-Path $skillSource 'SKILL.md' if (Test-Path $skillFile) { $declaredName = Get-Content $skillFile | Where-Object { $_ -match '^name:\s*' } | Select-Object -First 1 if ($declaredName -notmatch '^name:\s*okibi\s*$') { Fail 'bundled SKILL.md name does not match okibi' } $sourceMarker = Join-Path $skillSource '.okibi-managed' if (-not (Test-Path $sourceMarker) -or (Get-Content -Raw $sourceMarker).Trim() -ne 'okibi') { Fail 'bundled skill is missing its Okibi ownership marker' } New-Item -ItemType Directory -Path $SkillsDir -Force | Out-Null if ($skillManaged) { Remove-ManagedSkillPath $skillDest } if (Test-Path $skillDest) { Info "NOTE: skill not installed because $skillDest already exists and is not Okibi-managed" } else { New-Item -ItemType Directory -Path $skillDest -Force | Out-Null Copy-Item -Path (Join-Path $skillSource '*') -Destination $skillDest -Recurse -Force Copy-Item -Path $sourceMarker -Destination (Join-Path $skillDest '.okibi-managed') -Force Info "installed Agent Skill to $skillDest" Install-DetectedAgentSkills $skillName $skillDest 'okibi' } } else { Info 'NOTE: this older release has no bundled Agent Skill' } # Windows symlinks need admin, so copy the current version's exes into the # stable bin dir on PATH (re-running the installer overwrites = upgrade). New-Item -ItemType Directory -Path $BinDir -Force | Out-Null Copy-Item -Path (Join-Path $verDir 'okibi.exe') -Destination (Join-Path $BinDir 'okibi.exe') -Force if (Test-Path (Join-Path $verDir 'okibi-tui.exe')) { Copy-Item -Path (Join-Path $verDir 'okibi-tui.exe') -Destination (Join-Path $BinDir 'okibi-tui.exe') -Force } $userPath = [Environment]::GetEnvironmentVariable('Path', 'User') if ($userPath -notlike "*$BinDir*") { [Environment]::SetEnvironmentVariable('Path', "$userPath;$BinDir", 'User') Info "added $BinDir to your user PATH (restart your shell to pick it up)" } $cfg = Join-Path $env:USERPROFILE '.okibi' New-Item -ItemType Directory -Path $cfg -Force | Out-Null Set-Content -Path (Join-Path $cfg 'installed-from') -Value $ReleaseBase -NoNewline Info "done - installed okibi $version. Get started with: okibi login" } finally { Remove-Item -Recurse -Force $tmp } }