Files
Kigi-CLI/crates/codegen/kigi-tui/scripts/install-enterprise.ps1
T
ZacharyZhang-NY d6c20fc13f M0: compilable skeleton — Kigi 0.1.0 fork surgery
Hard fork of xai-org/grok-build (Apache-2.0) re-targeted as Kigi, an
unofficial Kimi Code CLI community build.

Rename & identity
- 72 xai-*/xai-grok-* crates -> kigi-* (explicit: xai-grok-pager-bin ->
  kigi-bin [binary `kigi`], xai-grok-pager -> kigi-tui; rest mechanical);
  ptyctl, ptyctl-cli, third_party/ unchanged; proto package
  xai.grok.tools.v1 -> kigi.tools.v1
- Config home ~/.kigi (KIGI_SHARE_DIR override), env prefix GROK_* ->
  KIGI_*, `kigi --version` carries the unofficial-community-build notice
- clap identity, help text, startup banner, prompt templates rebranded
  (templates re-encrypted)

Deletions (PRD removal list #5/#6/#7/#9/#10)
- voice input (xai-grok-voice) and all TUI wiring
- telemetry: Mixpanel client, external OTel stream, Sentry, OTLP layers,
  trace/GCS/S3 upload queues (kigi-file-utils halved), workspace upload
  module & dc_log, heap-profile uploader, auth-diagnostics uploader,
  session-analytics halves of feedback; local zero-egress observability
  preserved in new kigi-log crate (unified log, --debug firehose,
  subsystem file logs, opt-in instrumentation)
- announcements (crate, remote-settings fields, TUI surfaces)
- plugin marketplace (crate, sources/browse/CTA/extensions-modal tab);
  direct plugin install/uninstall/update via kigi-agent git_install kept
- relay/gateway/assets endpoints and features (agent relay, headless
  relay transport, gateway bridge, LeaderEnvUrls); leader IPC socket now
  ~/.kigi/leader.sock + KIGI_LEADER_SOCKET, no ws-url derivation
- functional types rehomed instead of deleted: PermissionMode ->
  kigi-config-types, McpInitStrategy -> kigi-mcp, PrCreationSource ->
  session signals, TerminalDiagnostics -> kigi-pager-render, agent_id ->
  shell util

Endpoints
- kigi-env rewritten: single production KigiEndpoints {coding_api_base_url
  https://api.kimi.com/coding/v1 (KIGI_CODE_BASE_URL), oauth_host
  https://auth.kimi.com (KIGI_OAUTH_HOST), update_base_url (GitHub
  Releases API), upgrade_page_url}; GrokBuildEnvironment enum deleted

Toolchain & workspace hygiene
- Rust 1.97.0 pinned; edition 2024; full cargo update; git2 hoisted to
  workspace at 0.21 (Option->Result API migration), quick-xml 0.41
- Root Cargo.toml hand-maintained (PRD §8.1): version 0.1.0 inherited by
  all members, members sorted, unused deps pruned
- cargo-deny advisories gate (deny.toml with documented transitive
  exceptions); CI workflow (check/clippy/fmt/deny/test, macOS+Linux)
- cross-crate test seams re-gated behind `test-support` cargo feature;
  insta snapshot baselines renamed to the kigi_tui prefix
- clippy --workspace --all-targets: zero warnings; fmt clean

Fixes surfaced by the port
- updater probe/installer divergence (bin/kigi vs bin/grok symlink set)
- idle model-metadata refresh dead under KIGI_CODE_BASE_URL override
  (new is_effective_coding_endpoint_url, loopback+override aware)
- macOS symlinked-TMPDIR fixture canonicalization (foreign_sessions,
  fast-worktree); RSS measurement tests serialized via serial_test

Docs & legal (Apache §4)
- NOTICE added (upstream attribution + change statement); THIRD-PARTY
  notices sustained; kigi-tools ported-code notices extended; README,
  CONTRIBUTING, SECURITY, AGENTS.md rewritten

Out of scope for M0 (tracked): Kimi auth/inference (M1), search/fetch,
command parity, config import (M2), Computer Hub excision & final
brand-token sweep (M2), distribution & self-update rewrite (M3).
2026-07-17 05:31:01 -04:00

335 lines
12 KiB
PowerShell

#
# Grok CLI installer (enterprise channel) for PowerShell — https://x.ai/cli/enterprise-install.ps1
#
# Standalone installer for the enterprise channel. Intentionally a full copy of
# the install logic so changes to the stable installer cannot break enterprise.
#
# Auth: KIGI_DEPLOYMENT_KEY env var (takes precedence) or ~/.grok/auth.json from `grok login`.
# Env: KIGI_BIN_DIR, KIGI_PROXY_URL
#
# Usage:
# irm https://x.ai/cli/enterprise-install.ps1 | iex # latest enterprise
# & ([scriptblock]::Create((irm https://x.ai/cli/enterprise-install.ps1))) -Version 0.1.42 # specific version
# $env:KIGI_VERSION="0.1.42"; irm https://x.ai/cli/enterprise-install.ps1 | iex # specific version (alt)
# $env:KIGI_DEPLOYMENT_KEY="<key>"; irm https://x.ai/cli/enterprise-install.ps1 | iex
#
param(
[Parameter(Position = 0)]
[string]$Version
)
$ErrorActionPreference = 'Stop'
# PS 5.1 defaults to TLS 1.0; GCS requires TLS 1.2.
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
# PS 5.1's Invoke-WebRequest progress bar is extremely slow; disable it.
$ProgressPreference = 'SilentlyContinue'
# Accept version from environment variable (useful with irm | iex).
if (-not $Version -and $env:KIGI_VERSION) {
$Version = $env:KIGI_VERSION
}
# This script is Windows-only. PS 5.1 has no Platform property and only runs on Windows.
if ($PSVersionTable.Platform -and $PSVersionTable.Platform -ne 'Win32NT') {
Write-Error "This installer is for Windows. On macOS/Linux, use: curl -fsSL https://x.ai/cli/enterprise-install.sh | bash"
exit 1
}
$GrokDir = Join-Path $env:USERPROFILE '.grok'
# --- Helpers ---
function Download-String([string]$Url) {
try {
$response = Invoke-WebRequest -Uri $Url -UseBasicParsing
return $response.Content
} catch {
return $null
}
}
function Download-File([string]$Url, [string]$OutFile) {
# TODO: parallel byte-range download (matches install-enterprise.sh download_file_parallel).
# Skipped for now: requires Start-ThreadJob / RunspacePool for true parallelism on PS 5.1
# and HEAD + Range request orchestration. Single-connection HttpWebRequest below remains.
# Stream via HttpWebRequest — faster than Invoke-WebRequest on PS 5.1 and supports progress.
$request = [System.Net.HttpWebRequest]::Create($Url)
$request.Timeout = 300000 # 5 min
$request.AutomaticDecompression = [System.Net.DecompressionMethods]::GZip -bor [System.Net.DecompressionMethods]::Deflate
$response = $request.GetResponse()
$totalBytes = $response.ContentLength
$stream = $response.GetResponseStream()
$fileStream = [System.IO.File]::Create($OutFile)
$buffer = New-Object byte[] 65536
$totalRead = 0
$lastPercent = -1
$lastMb = -1
try {
while (($read = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$fileStream.Write($buffer, 0, $read)
$totalRead += $read
$mb = [math]::Round($totalRead / 1MB, 1)
if ($totalBytes -gt 0) {
$percent = [math]::Min(100, [math]::Floor(($totalRead / $totalBytes) * 100))
if ($percent -ne $lastPercent) {
$totalMb = [math]::Round($totalBytes / 1MB, 1)
Write-Host "`r Downloading... ${mb} MB / ${totalMb} MB (${percent}%)" -NoNewline
$lastPercent = $percent
}
} elseif ($mb -ne $lastMb) {
Write-Host "`r Downloading... ${mb} MB" -NoNewline
$lastMb = $mb
}
}
Write-Host ''
} finally {
$fileStream.Close()
$stream.Close()
$response.Close()
}
}
function Read-GrokToken([string]$Scope) {
$authFile = Join-Path $GrokDir 'auth.json'
if (-not (Test-Path $authFile)) { return $null }
try {
$auth = Get-Content -Raw $authFile | ConvertFrom-Json
$entry = $auth.$Scope
if ($entry -and $entry.key) { return $entry.key }
} catch {}
return $null
}
# --- Validate version ---
if ($Version -and $Version -notmatch '^\d+\.\d+\.\d+(-\S+)?$') {
Write-Error "Invalid version format: $Version (expected X.Y.Z or X.Y.Z-suffix)"
exit 1
}
# --- Resolve auth ---
$OidcScope = 'https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828'
$LegacyScope = 'https://accounts.x.ai/sign-in'
$AuthSource = ''
if ($env:KIGI_DEPLOYMENT_KEY) {
$AuthSource = 'deployment key'
Write-Host 'Auth: using deployment key.' -ForegroundColor DarkGray
} else {
$oidcToken = Read-GrokToken $OidcScope
$legacyToken = Read-GrokToken $LegacyScope
if ($oidcToken) {
$AuthSource = 'auth.json (oidc)'
Write-Host 'Auth: using OIDC token from ~/.grok/auth.json.' -ForegroundColor DarkGray
} elseif ($legacyToken) {
$AuthSource = 'auth.json (legacy)'
Write-Host 'Auth: using legacy token from ~/.grok/auth.json.' -ForegroundColor DarkGray
}
}
# --- Detect architecture ---
$arch = switch ($env:PROCESSOR_ARCHITECTURE) {
'AMD64' { 'x86_64' }
'x86' { 'x86_64' } # 32-bit PS on 64-bit Windows
'ARM64' { 'aarch64' }
default { $null }
}
if (-not $arch) {
Write-Error "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE"
exit 1
}
$platform = "windows-$arch"
# --- Resolve version ---
$BaseUrlPrimary = 'https://x.ai/cli'
$BaseUrlFallback = 'https://storage.googleapis.com/grok-build-public-artifacts/cli'
$DownloadDir = Join-Path $GrokDir 'downloads'
$BinDir = if ($env:KIGI_BIN_DIR) { $env:KIGI_BIN_DIR } else { Join-Path $GrokDir 'bin' }
New-Item -ItemType Directory -Path $DownloadDir -Force | Out-Null
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
$Channel = 'enterprise'
# Pick a working BaseUrl: try Cloudflare-fronted x.ai first, fall back to
# direct GCS if it's unreachable. The probe doubles as the channel-pointer
# fetch when no -Version was passed, so the happy path costs zero extra requests.
if (-not $Version) { Write-Host "Fetching latest $Channel version..." -ForegroundColor DarkGray }
$probeResult = Download-String "$BaseUrlPrimary/$Channel"
if ($probeResult) {
$BaseUrl = $BaseUrlPrimary
} else {
Write-Host "Note: $BaseUrlPrimary unreachable, falling back to direct GCS." -ForegroundColor Yellow
$BaseUrl = $BaseUrlFallback
$probeResult = Download-String "$BaseUrl/$Channel"
}
if ($Version) {
$resolvedVersion = $Version
} elseif ($probeResult) {
$resolvedVersion = $probeResult.Trim()
} else {
Write-Error "Failed to fetch latest version from $BaseUrlPrimary/$Channel and $BaseUrlFallback/$Channel"
exit 1
}
if ($AuthSource) {
Write-Host "Installing Grok $resolvedVersion ($platform, $AuthSource)..." -ForegroundColor Cyan
} else {
Write-Host "Installing Grok $resolvedVersion ($platform)..." -ForegroundColor Cyan
}
# --- Download binary ---
$binaryPath = Join-Path $DownloadDir "grok-$platform.exe"
$artifactBase = "$BaseUrl/grok-$resolvedVersion-$platform"
$downloaded = $false
foreach ($url in @("$artifactBase.exe", $artifactBase)) {
try {
Download-File $url $binaryPath
$downloaded = $true
break
} catch {
continue
}
}
if (-not $downloaded) {
if (Test-Path $binaryPath) { Remove-Item $binaryPath -Force }
Write-Error "Binary download failed from $artifactBase.exe and $artifactBase"
exit 1
}
# --- Install binary (locked-file safe) ---
foreach ($binName in @('grok.exe', 'agent.exe')) {
$dest = Join-Path $BinDir $binName
$old = "$dest.old"
if (Test-Path $old) { Remove-Item $old -Force -ErrorAction SilentlyContinue }
try {
Copy-Item -Path $binaryPath -Destination $dest -Force
} catch {
try {
if (Test-Path $dest) { Rename-Item $dest $old -Force -ErrorAction SilentlyContinue }
Copy-Item -Path $binaryPath -Destination $dest -Force
} catch {
if (Test-Path $old) { Rename-Item $old $dest -Force -ErrorAction SilentlyContinue }
Write-Error "Failed to install $binName"
exit 1
}
}
}
Write-Host " Installed to $BinDir\grok.exe and $BinDir\agent.exe." -ForegroundColor DarkGray
# --- Generate completions (best-effort) ---
$completionsDir = Join-Path (Join-Path $GrokDir 'completions') 'powershell'
try {
New-Item -ItemType Directory -Path $completionsDir -Force | Out-Null
& (Join-Path $BinDir 'grok.exe') completions powershell 2>$null |
Set-Content (Join-Path $completionsDir 'grok.ps1') -ErrorAction SilentlyContinue
} catch {}
# --- Persist installer config ---
$ConfigFile = Join-Path $GrokDir 'config.toml'
$cliLines = @('installer = "internal"', 'channel = "enterprise"')
if (-not (Test-Path $ConfigFile)) {
New-Item -ItemType Directory -Path (Split-Path $ConfigFile) -Force | Out-Null
$content = "[cli]`r`n" + ($cliLines -join "`r`n") + "`r`n"
[System.IO.File]::WriteAllText($ConfigFile, $content, [System.Text.Encoding]::UTF8)
} elseif ((Get-Content -Raw $ConfigFile) -match '(?m)^\[cli\]') {
# Section-aware: only replace installer/channel under [cli], not other sections.
$existingLines = Get-Content $ConfigFile
$output = [System.Collections.ArrayList]::new()
$inCli = $false
foreach ($line in $existingLines) {
if ($line -match '^\[cli\]\s*(#.*)?$') {
[void]$output.Add($line)
foreach ($cl in $cliLines) { [void]$output.Add($cl) }
$inCli = $true
continue
}
if ($line -match '^\[.+\]\s*(#.*)?$') {
$inCli = $false
}
if ($inCli -and $line -match '^\s*(installer|channel)\s*=') {
continue
}
[void]$output.Add($line)
}
[System.IO.File]::WriteAllLines($ConfigFile, [string[]]$output.ToArray(), [System.Text.Encoding]::UTF8)
} else {
Add-Content -Path $ConfigFile -Value "`r`n[cli]`r`n$($cliLines -join "`r`n")`r`n"
}
# --- Fetch deployment config (deployment key only) ---
if ($env:KIGI_DEPLOYMENT_KEY) {
$ProxyUrl = if ($env:KIGI_PROXY_URL) { $env:KIGI_PROXY_URL } else { 'https://cli-chat-proxy.grok.com/v1' }
Write-Host ' Fetching deployment config...' -ForegroundColor DarkGray
try {
$headers = @{ 'Authorization' = "Bearer $($env:KIGI_DEPLOYMENT_KEY)" }
$deployResponse = Invoke-RestMethod -Uri "$ProxyUrl/deployment/config" -Headers $headers -UseBasicParsing
} catch {
Write-Host " Warning: failed to fetch deployment config from $ProxyUrl/deployment/config" -ForegroundColor Yellow
$deployResponse = $null
}
if ($deployResponse) {
$managedConfig = $deployResponse.managed_config
$requirements = $deployResponse.requirements
$managedConfigPath = Join-Path $GrokDir 'managed_config.toml'
$requirementsPath = Join-Path $GrokDir 'requirements.toml'
if ($managedConfig -and $managedConfig -ne 'null') {
[System.IO.File]::WriteAllText($managedConfigPath, $managedConfig, [System.Text.Encoding]::UTF8)
Write-Host ' Managed config applied.' -ForegroundColor DarkGray
} else {
if (Test-Path $managedConfigPath) { Remove-Item $managedConfigPath -Force }
}
if ($requirements -and $requirements -ne 'null') {
[System.IO.File]::WriteAllText($requirementsPath, $requirements, [System.Text.Encoding]::UTF8)
Write-Host ' Requirements applied.' -ForegroundColor DarkGray
} else {
if (Test-Path $requirementsPath) { Remove-Item $requirementsPath -Force }
}
}
}
Write-Host "Grok $resolvedVersion installed to $BinDir\grok.exe" -ForegroundColor Green
# --- Ensure grok is on PATH ---
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
$pathEntries = if ($userPath) { $userPath -split ';' | Where-Object { $_ -ne '' } } else { @() }
if ($pathEntries -notcontains $BinDir) {
$newPath = (@($BinDir) + $pathEntries) -join ';'
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
Write-Host " Added $BinDir to your User PATH." -ForegroundColor DarkGray
# Update current session so grok works immediately.
if ($env:Path -notlike "*$BinDir*") {
$env:Path = "$BinDir;$env:Path"
}
}
Write-Host ''
Write-Host "Run 'grok' or 'agent' to get started!" -ForegroundColor Cyan