summaryrefslogtreecommitdiff
path: root/extra
diff options
context:
space:
mode:
Diffstat (limited to 'extra')
-rw-r--r--extra/install-claude.sh165
-rw-r--r--extra/install-cursor.sh151
-rw-r--r--extra/install-kimi.sh185
-rw-r--r--extra/install-xai.sh349
4 files changed, 850 insertions, 0 deletions
diff --git a/extra/install-claude.sh b/extra/install-claude.sh
new file mode 100644
index 0000000..41313f3
--- /dev/null
+++ b/extra/install-claude.sh
@@ -0,0 +1,165 @@
+#!/bin/bash
+
+set -e
+
+# Parse command line arguments
+TARGET="$1" # Optional target parameter
+
+# Validate target if provided
+if [[ -n "$TARGET" ]] && [[ ! "$TARGET" =~ ^(stable|latest|[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?)$ ]]; then
+ echo "Usage: $0 [stable|latest|VERSION]" >&2
+ exit 1
+fi
+
+DOWNLOAD_BASE_URL="https://downloads.claude.ai/claude-code-releases"
+DOWNLOAD_DIR="$HOME/.claude/downloads"
+
+# Check for required dependencies
+DOWNLOADER=""
+if command -v curl >/dev/null 2>&1; then
+ DOWNLOADER="curl"
+elif command -v wget >/dev/null 2>&1; then
+ DOWNLOADER="wget"
+else
+ echo "Either curl or wget is required but neither is installed" >&2
+ exit 1
+fi
+
+# Check if jq is available (optional)
+HAS_JQ=false
+if command -v jq >/dev/null 2>&1; then
+ HAS_JQ=true
+fi
+
+# Download function that works with both curl and wget
+download_file() {
+ local url="$1"
+ local output="$2"
+
+ if [ "$DOWNLOADER" = "curl" ]; then
+ if [ -n "$output" ]; then
+ curl -fsSL -o "$output" "$url"
+ else
+ curl -fsSL "$url"
+ fi
+ elif [ "$DOWNLOADER" = "wget" ]; then
+ if [ -n "$output" ]; then
+ wget -q -O "$output" "$url"
+ else
+ wget -q -O - "$url"
+ fi
+ else
+ return 1
+ fi
+}
+
+# Simple JSON parser for extracting checksum when jq is not available
+get_checksum_from_manifest() {
+ local json="$1"
+ local platform="$2"
+
+ # Normalize JSON to single line and extract checksum
+ json=$(echo "$json" | tr -d '\n\r\t' | sed 's/ \+/ /g')
+
+ # Extract checksum for platform using bash regex
+ if [[ $json =~ \"$platform\"[^}]*\"checksum\"[[:space:]]*:[[:space:]]*\"([a-f0-9]{64})\" ]]; then
+ echo "${BASH_REMATCH[1]}"
+ return 0
+ fi
+
+ return 1
+}
+
+# Detect platform
+case "$(uname -s)" in
+ Darwin) os="darwin" ;;
+ Linux) os="linux" ;;
+ MINGW*|MSYS*|CYGWIN*) echo "Windows is not supported by this script. See https://code.claude.com/docs for installation options." >&2; exit 1 ;;
+ *) echo "Unsupported operating system: $(uname -s). See https://code.claude.com/docs for supported platforms." >&2; exit 1 ;;
+esac
+
+case "$(uname -m)" in
+ x86_64|amd64) arch="x64" ;;
+ arm64|aarch64) arch="arm64" ;;
+ *) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
+esac
+
+# Detect Rosetta 2 on macOS: if the shell is running as x64 under Rosetta on an ARM Mac,
+# download the native arm64 binary instead of the x64 one
+if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
+ if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
+ arch="arm64"
+ fi
+fi
+
+# Check for musl on Linux and adjust platform accordingly
+if [ "$os" = "linux" ]; then
+ if [ -f /lib/libc.musl-x86_64.so.1 ] || [ -f /lib/libc.musl-aarch64.so.1 ] || ldd /bin/ls 2>&1 | grep -q musl; then
+ platform="linux-${arch}-musl"
+ else
+ platform="linux-${arch}"
+ fi
+else
+ platform="${os}-${arch}"
+fi
+mkdir -p "$DOWNLOAD_DIR"
+
+# Always download latest version (which has the most up-to-date installer)
+version=$(download_file "$DOWNLOAD_BASE_URL/latest")
+
+# Reject non-version content (e.g. an HTML error page) before it reaches the manifest URL
+if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
+ echo "Failed to get a valid version from downloads.claude.ai (got unexpected content)." >&2
+ echo "This can happen if the download service is unreachable or not available in your region - see https://www.anthropic.com/supported-countries" >&2
+ exit 1
+fi
+
+# Download manifest and extract checksum
+manifest_json=$(download_file "$DOWNLOAD_BASE_URL/$version/manifest.json")
+
+# Use jq if available, otherwise fall back to pure bash parsing
+if [ "$HAS_JQ" = true ]; then
+ checksum=$(echo "$manifest_json" | jq -r ".platforms[\"$platform\"].checksum // empty")
+else
+ checksum=$(get_checksum_from_manifest "$manifest_json" "$platform")
+fi
+
+# Validate checksum format (SHA256 = 64 hex characters)
+if [ -z "$checksum" ] || [[ ! "$checksum" =~ ^[a-f0-9]{64}$ ]]; then
+ echo "Platform $platform not found in manifest" >&2
+ exit 1
+fi
+
+# Download and verify
+binary_path="$DOWNLOAD_DIR/claude-$version-$platform"
+if ! download_file "$DOWNLOAD_BASE_URL/$version/$platform/claude" "$binary_path"; then
+ echo "Download failed" >&2
+ rm -f "$binary_path"
+ exit 1
+fi
+
+# Pick the right checksum tool
+if [ "$os" = "darwin" ]; then
+ actual=$(shasum -a 256 "$binary_path" | cut -d' ' -f1)
+else
+ actual=$(sha256sum "$binary_path" | cut -d' ' -f1)
+fi
+
+if [ "$actual" != "$checksum" ]; then
+ echo "Checksum verification failed" >&2
+ rm -f "$binary_path"
+ exit 1
+fi
+
+chmod +x "$binary_path"
+
+# Run claude install to set up launcher and shell integration
+echo "Setting up Claude Code..."
+"$binary_path" install ${TARGET:+"$TARGET"}
+
+# Clean up downloaded file
+rm -f "$binary_path"
+
+echo ""
+echo "✅ Installation complete!"
+echo ""
diff --git a/extra/install-cursor.sh b/extra/install-cursor.sh
new file mode 100644
index 0000000..fb9ca2c
--- /dev/null
+++ b/extra/install-cursor.sh
@@ -0,0 +1,151 @@
+#!/usr/bin/env bash
+
+# Color support: check NO_COLOR or non-TTY (stdout)
+if [ -n "${NO_COLOR+x}" ] || [ ! -t 1 ]; then
+ CYAN=''
+ GREEN=''
+ YELLOW=''
+ BLUE=''
+ PURPLE=''
+ RED=''
+ BOLD=''
+ DIM=''
+ NC=''
+else
+ CYAN='\033[0;36m'
+ GREEN='\033[0;32m'
+ YELLOW='\033[0;33m'
+ BLUE='\033[0;34m'
+ PURPLE='\033[0;35m'
+ RED='\033[0;31m'
+ BOLD='\033[1m'
+ DIM='\033[2m'
+ NC='\033[0m'
+fi
+
+# Fancy header
+echo ""
+echo -e "${BOLD}Cursor Agent Installer${NC}"
+echo ""
+
+# Function to print steps with style
+print_step() {
+ echo -e "${BLUE}▸${NC} ${1}"
+}
+
+# Function to print success
+print_success() {
+ # Move cursor up one line and clear it
+ echo -ne "\033[1A\033[2K"
+ echo -e "${GREEN}✓${NC} ${1}"
+}
+
+# Function to print error
+print_error() {
+ echo -e "${RED}✗${NC} ${1}"
+}
+
+# Detect OS and Architecture
+print_step "Detecting system architecture..."
+
+# Detect OS
+OS="$(uname -s)"
+case "${OS}" in
+ Linux*) OS="linux";;
+ Darwin*) OS="darwin";;
+ *)
+ print_error "Unsupported operating system: ${OS}"
+ exit 1
+ ;;
+esac
+
+# Detect Architecture
+ARCH="$(uname -m)"
+case "${ARCH}" in
+ x86_64|amd64) ARCH="x64";;
+ arm64|aarch64) ARCH="arm64";;
+ *)
+ print_error "Unsupported architecture: ${ARCH}"
+ exit 1
+ ;;
+esac
+
+print_success "Detected ${OS}/${ARCH}"
+
+# Installation steps
+print_step "Creating installation directory..."
+# Create temporary directory for atomic download inside versions folder
+TEMP_EXTRACT_DIR="$HOME/.local/share/cursor-agent/versions/.tmp-2026.05.27-fe9a6e2-$(date +%s)"
+mkdir -p "${TEMP_EXTRACT_DIR}"
+
+print_success "Directory created"
+
+
+print_step "Downloading Cursor Agent package..."
+DOWNLOAD_URL="https://downloads.cursor.com/lab/2026.05.27-fe9a6e2/${OS}/${ARCH}/agent-cli-package.tar.gz"
+echo -e "${DIM} Download URL: ${DOWNLOAD_URL}${NC}"
+
+# Cleanup function
+cleanup() {
+ command rm -rf "${TEMP_EXTRACT_DIR}"
+}
+trap cleanup EXIT
+
+# Download with progress bar and better error handling
+if curl -fSL --progress-bar "${DOWNLOAD_URL}" \
+ | tar --strip-components=1 -xzf - -C "${TEMP_EXTRACT_DIR}"; then
+ echo -ne "\033[1A\033[2K"
+ echo -ne "\033[1A\033[2K"
+ echo -ne "\033[1A\033[2K"
+ print_success "Package downloaded and extracted"
+else
+ print_error "Download failed. Please check your internet connection and try again."
+ print_error "If the problem persists, the package might not be available for ${OS}/${ARCH}."
+ cleanup
+ exit 1
+fi
+
+print_step "Finalizing installation..."
+# Atomically move from temp to final destination
+FINAL_DIR="$HOME/.local/share/cursor-agent/versions/2026.05.27-fe9a6e2"
+command rm -rf "${FINAL_DIR}"
+if mv "${TEMP_EXTRACT_DIR}" "${FINAL_DIR}"; then
+ print_success "Package installed successfully"
+else
+ print_error "Failed to install package. Please check permissions."
+ cleanup
+ exit 1
+fi
+
+
+print_step "Creating bin directory..."
+mkdir -p ~/.local/bin
+print_success "Bin directory ready"
+
+
+print_step "Creating symlinks to agent executable..."
+# Remove any existing symlink or file
+command rm -f ~/.local/bin/cursor-agent
+# Create symlinks to the Cursor Agent executable
+ln -s ~/.local/share/cursor-agent/versions/2026.05.27-fe9a6e2/cursor-agent ~/.local/bin/cursor-agent
+print_success "Symlink created"
+
+# Success message
+echo ""
+echo -e "${BOLD}${GREEN}✨ Installation Complete! ${NC}"
+echo ""
+echo ""
+
+# Check if ~/.local/bin is already in PATH
+if [[ ":${PATH}:" == *":${HOME}/.local/bin:"* ]]; then
+ # PATH already configured, just show usage
+ echo -e "${BOLD}Start using Cursor Agent:${NC}"
+ echo -e " ${BOLD}cursor-agent${NC}"
+else
+ echo -e "${BOLD}Add ~/.local/bin to PATH:${NC}"
+fi
+
+echo ""
+echo ""
+echo -e "${BOLD}${CYAN}Happy coding! 🚀${NC}"
+echo ""
diff --git a/extra/install-kimi.sh b/extra/install-kimi.sh
new file mode 100644
index 0000000..d1013ca
--- /dev/null
+++ b/extra/install-kimi.sh
@@ -0,0 +1,185 @@
+#!/usr/bin/env bash
+#
+# kimi-code installer for macOS and Linux.
+#
+# Usage:
+# curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
+# KIMI_VERSION=0.5.0 curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
+# KIMI_INSTALL_DIR=/usr/local curl -fsSL https://code.kimi.com/kimi-code/install.sh | sudo -E bash
+#
+# Optional env:
+# KIMI_VERSION Explicit version; if unset, fetch from https://code.kimi.com/kimi-code/latest
+# KIMI_INSTALL_DIR Installation directory, default $HOME/.kimi-code
+
+set -euo pipefail
+
+KIMI_DOWNLOAD_BASE="https://code.kimi.com/kimi-code"
+
+KIMI_VERSION="${KIMI_VERSION:-}"
+KIMI_INSTALL_DIR="${KIMI_INSTALL_DIR:-$HOME/.local/bin}"
+
+# ---------- helpers ----------
+
+_have() { command -v "$1" >/dev/null 2>&1; }
+
+_log() {
+ if [ -t 1 ]; then
+ printf '\033[1;36m==>\033[0m %s\n' "$*"
+ else
+ printf '==> %s\n' "$*"
+ fi
+}
+
+_err() {
+ printf 'error: %s\n' "$*" >&2
+ exit 1
+}
+
+_detect_target() {
+ local os arch
+ case "$(uname -s)" in
+ Darwin) os="darwin" ;;
+ Linux) os="linux" ;;
+ MINGW*|MSYS*|CYGWIN*)
+ _err "Windows is not supported by install.sh — use install.ps1 (PowerShell)"
+ ;;
+ *) _err "unsupported OS: $(uname -s)" ;;
+ esac
+ case "$(uname -m)" in
+ x86_64|amd64) arch="x64" ;;
+ arm64|aarch64) arch="arm64" ;;
+ *) _err "unsupported architecture: $(uname -m)" ;;
+ esac
+
+ # Rosetta 2: when running under an x64 shell on an ARM Mac, switch back to the native arm64 binary
+ if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
+ if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then
+ arch="arm64"
+ fi
+ fi
+
+ # musl detection (Alpine, etc.) — we currently only ship glibc binaries, so failing early is better than a runtime dlopen error
+ if [ "$os" = "linux" ]; then
+ if [ -f "/lib/libc.musl-x86_64.so.1" ] || \
+ [ -f "/lib/libc.musl-aarch64.so.1" ] || \
+ ldd /bin/ls 2>&1 | grep -q musl; then
+ _err "Alpine / musl Linux is not currently supported. Use Node.js + 'npm install -g @moonshot-ai/kimi-code' instead."
+ fi
+ fi
+
+ echo "${os}-${arch}"
+}
+
+_download() {
+ local url="$1" dest="${2:-}"
+ if _have curl; then
+ if [ -n "$dest" ]; then
+ if [ -t 1 ]; then
+ curl --fail --location --progress-bar -o "$dest" "$url"
+ else
+ curl --fail --location --silent -o "$dest" "$url"
+ fi
+ else
+ curl --fail --location --silent "$url"
+ fi
+ elif _have wget; then
+ if [ -n "$dest" ]; then
+ wget -q -O "$dest" "$url"
+ else
+ wget -q -O - "$url"
+ fi
+ else
+ _err "curl or wget is required"
+ fi
+}
+
+# Prefer jq; otherwise parse a single manifest field using pure bash regex
+_manifest_field() {
+ local manifest_json="$1" target="$2" field="$3"
+ if _have jq; then
+ printf '%s' "$manifest_json" | jq -er ".platforms[\"$target\"].$field // empty"
+ else
+ local one_line
+ one_line="$(printf '%s' "$manifest_json" | tr -d '\n\r\t' | sed 's/ \+/ /g')"
+ if [[ $one_line =~ \"$target\"[^}]*\"$field\"[[:space:]]*:[[:space:]]*\"([^\"]+)\" ]]; then
+ printf '%s' "${BASH_REMATCH[1]}"
+ fi
+ fi
+}
+
+_sha256_check() {
+ local file="$1" expected="$2"
+ local actual
+ if _have shasum; then
+ actual="$(shasum -a 256 "$file" | cut -d' ' -f1)"
+ elif _have sha256sum; then
+ actual="$(sha256sum "$file" | cut -d' ' -f1)"
+ else
+ _err "shasum or sha256sum required to verify download"
+ fi
+ if [ "$actual" != "$expected" ]; then
+ _err "checksum mismatch: expected $expected, got $actual"
+ fi
+}
+
+# ---------- main ----------
+
+TMPDIR_INSTALL=""
+_cleanup() {
+ if [ -n "$TMPDIR_INSTALL" ] && [ -d "$TMPDIR_INSTALL" ]; then
+ rm -rf "$TMPDIR_INSTALL"
+ fi
+}
+trap _cleanup EXIT
+
+main() {
+ local target version manifest_url manifest filename checksum binary_url tmpdir
+
+ target="$(_detect_target)"
+ _log "Detected target: $target"
+
+ # 1. Resolve version
+ if [ -n "$KIMI_VERSION" ]; then
+ version="$KIMI_VERSION"
+ _log "Using pinned version $version"
+ else
+ _log "Resolving latest version from ${KIMI_DOWNLOAD_BASE}/latest"
+ version="$(_download "${KIMI_DOWNLOAD_BASE}/latest" | tr -d '[:space:]')"
+ [ -n "$version" ] || _err "could not resolve latest version"
+ _log "Latest version: $version"
+ fi
+
+ # 2. Fetch manifest
+ manifest_url="${KIMI_DOWNLOAD_BASE}/${version}/manifest.json"
+ _log "Fetching manifest ${manifest_url}"
+ manifest="$(_download "$manifest_url")"
+ [ -n "$manifest" ] || _err "manifest is empty or unreachable: $manifest_url"
+
+ # 3. Find current platform entry
+ filename="$(_manifest_field "$manifest" "$target" "filename")"
+ checksum="$(_manifest_field "$manifest" "$target" "checksum")"
+ [ -n "$filename" ] || _err "platform $target not found in manifest"
+ [[ "$checksum" =~ ^[a-f0-9]{64}$ ]] || _err "invalid checksum for $target: $checksum"
+
+ # 4. Download binary
+ TMPDIR_INSTALL="$(mktemp -d 2>/dev/null || mktemp -d -t kimi-install)"
+ tmpdir="$TMPDIR_INSTALL"
+ binary_url="${KIMI_DOWNLOAD_BASE}/${version}/${filename}"
+ _log "Downloading ${binary_url}"
+ _download "$binary_url" "${tmpdir}/${filename}"
+
+ # 5. Verify checksum
+ _log "Verifying checksum"
+ _sha256_check "${tmpdir}/${filename}" "$checksum"
+
+ # 6. Install
+ chmod +x "${tmpdir}/${filename}"
+ mkdir -p "${KIMI_INSTALL_DIR}"
+ install -m 0755 "${tmpdir}/${filename}" "${KIMI_INSTALL_DIR}/kimi"
+ _log "Installed to ${KIMI_INSTALL_DIR}/kimi"
+
+
+ _log "Done. Run: kimi --version"
+}
+
+main "$@"
diff --git a/extra/install-xai.sh b/extra/install-xai.sh
new file mode 100644
index 0000000..0f2e1f4
--- /dev/null
+++ b/extra/install-xai.sh
@@ -0,0 +1,349 @@
+#!/bin/bash
+#
+# Grok CLI installer — https://x.ai/cli/install.sh
+#
+# Auth: GROK_DEPLOYMENT_KEY (takes precedence) or ~/.grok/auth.json from `grok login`.
+# Env: GROK_CHANNEL (stable|alpha|enterprise, default: stable), GROK_BIN_DIR, GROK_PROXY_URL
+#
+# Usage:
+# curl -fsSL https://x.ai/cli/install.sh | bash # latest stable
+# curl -fsSL https://x.ai/cli/install.sh | bash -s 0.1.42 # specific version
+# GROK_DEPLOYMENT_KEY=<key> bash <(curl -fsSL https://x.ai/cli/install.sh)
+#
+# Windows: run under Git for Windows / MSYS2 Bash (same curl | bash flow); WSL
+# uses the Linux binary.
+
+set -e
+
+TARGET="$1"
+
+if [[ -n "$TARGET" ]] && [[ ! "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[^[:space:]]+)?$ ]]; then
+ echo "Invalid version format: $TARGET (expected X.Y.Z or X.Y.Z-suffix)" >&2
+ exit 1
+fi
+
+DOWNLOADER=""
+if command -v curl >/dev/null 2>&1; then
+ DOWNLOADER="curl"
+elif command -v wget >/dev/null 2>&1; then
+ DOWNLOADER="wget"
+else
+ echo "Either curl or wget is required but neither is installed" >&2
+ exit 1
+fi
+
+download_file() {
+ local url="$1" output="$2"
+ if [ "$DOWNLOADER" = "curl" ]; then
+ if [ -n "$output" ]; then
+ curl -fsSL -o "$output" "$url"
+ else
+ curl -fsSL "$url"
+ fi
+ else
+ if [ -n "$output" ]; then
+ wget -q -O "$output" "$url"
+ else
+ wget -q -O - "$url"
+ fi
+ fi
+}
+
+# Parallel byte-range download. Falls back to single-connection download_file
+# whenever HEAD lacks Content-Length, the file is small (<16 MiB), curl is
+# unavailable, or any chunk fetch / concat fails.
+download_file_parallel() {
+ local url="$1" output="$2"
+ if [ "$DOWNLOADER" != "curl" ]; then
+ download_file "$url" "$output"
+ return
+ fi
+ local size
+ size=$(curl -fsSL --head "$url" 2>/dev/null | awk -F'[: \r\n]+' 'tolower($1)=="content-length"{print $2; exit}')
+ if [ -z "$size" ] || ! [ "$size" -ge 16777216 ] 2>/dev/null; then
+ download_file "$url" "$output"
+ return
+ fi
+ local n=8
+ local chunk_size=$(( (size + n - 1) / n ))
+ local tmpdir
+ tmpdir=$(mktemp -d 2>/dev/null) || { download_file "$url" "$output"; return; }
+ local pids=() i start end
+ for i in $(seq 0 $((n - 1))); do
+ start=$((i * chunk_size))
+ end=$((start + chunk_size - 1))
+ [ $end -ge $size ] && end=$((size - 1))
+ curl -fsSL -r "${start}-${end}" -o "${tmpdir}/$(printf 'chunk.%03d' "$i")" "$url" &
+ pids+=($!)
+ done
+ local all_ok=true pid
+ for pid in "${pids[@]}"; do
+ wait "$pid" || all_ok=false
+ done
+ if [ "$all_ok" = true ] && cat "${tmpdir}"/chunk.* > "$output" 2>/dev/null; then
+ rm -rf "$tmpdir"
+ return 0
+ fi
+ rm -rf "$tmpdir"
+ download_file "$url" "$output"
+}
+
+# Return 0 if a HEAD request for the URL gets HTTP 404.
+is_not_found() {
+ local url="$1" code
+ if [ "$DOWNLOADER" = "curl" ]; then
+ code=$(curl -o /dev/null -sSL -w '%{http_code}' --head "$url" 2>/dev/null) || true
+ else
+ code=$(wget --server-response --spider "$url" 2>&1 | awk '/HTTP\//{print $2}' | tail -1) || true
+ fi
+ [ "$code" = "404" ]
+}
+
+# JSON field extractor — extract a top-level string value using sed.
+json_get() {
+ local json="$1" field="$2"
+ # Extract value (handling \" inside strings), then unescape JSON sequences.
+ printf '%s' "$json" | sed -n -E 's/.*"'"$field"'"[[:space:]]*:[[:space:]]*"(([^"\\]|\\.)*)".*/\1/p' | head -1 \
+ | sed -e 's/\\"/"/g' -e 's/\\n/\'$'\n''/g' -e 's/\\t/\'$'\t''/g' -e 's/\\\\/\\/g'
+}
+
+# Read a token from ~/.grok/auth.json for the given scope key.
+# Format: {"scope_url": {"key": "token"}, ...}
+read_grok_token() {
+ local auth_file="$HOME/.grok/auth.json"
+ local scope="$1"
+ [ -f "$auth_file" ] || return 1
+ # Flatten to one line then extract: find the scope, then the "key" value after it
+ tr -d '\n' < "$auth_file" | sed -n 's|.*"'"$scope"'"[[:space:]]*:[[:space:]]*{[^}]*"key"[[:space:]]*:[[:space:]]*"\([^"]*\)".*|\1|p' | head -1
+}
+
+# Resolve auth: GROK_DEPLOYMENT_KEY > OIDC token > legacy token
+OIDC_SCOPE="https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828"
+LEGACY_SCOPE="https://accounts.x.ai/sign-in"
+AUTH_SOURCE=""
+
+if [ -n "$GROK_DEPLOYMENT_KEY" ]; then
+ AUTH_SOURCE="deployment key"
+ echo "Auth: using deployment key." >&2
+else
+ OIDC_TOKEN=$(read_grok_token "$OIDC_SCOPE" 2>/dev/null) || true
+ LEGACY_TOKEN=$(read_grok_token "$LEGACY_SCOPE" 2>/dev/null) || true
+ if [ -n "$OIDC_TOKEN" ]; then
+ AUTH_SOURCE="auth.json (oidc)"
+ echo "Auth: using OIDC token from ~/.grok/auth.json." >&2
+ elif [ -n "$LEGACY_TOKEN" ]; then
+ AUTH_SOURCE="auth.json (legacy)"
+ echo "Auth: using legacy token from ~/.grok/auth.json." >&2
+ fi
+fi
+
+case "$(uname -s)" in
+ Darwin) os="macos" ;;
+ Linux) os="linux" ;;
+ # Git for Windows / MSYS2 / Cygwin host — native Windows builds
+ MINGW* | MSYS* | CYGWIN*) os="windows" ;;
+ *) echo "Unsupported OS: $(uname -s)" >&2; exit 1 ;;
+esac
+
+case "$(uname -m)" in
+ x86_64|amd64|AMD64) arch="x86_64" ;;
+ arm64|aarch64|ARM64) arch="aarch64" ;;
+ *) echo "Unsupported architecture: $(uname -m)" >&2; exit 1 ;;
+esac
+
+BASE_URL_PRIMARY="https://x.ai/cli"
+BASE_URL_FALLBACK="https://storage.googleapis.com/grok-build-public-artifacts/cli"
+DOWNLOAD_DIR="$HOME/.grok/downloads"
+BIN_DIR="${GROK_BIN_DIR:-$HOME/.local/bin}"
+mkdir -p "$DOWNLOAD_DIR" "$BIN_DIR"
+
+platform="${os}-${arch}"
+CHANNEL="${GROK_CHANNEL:-stable}"
+
+# Pick a working BASE_URL: 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 explicit TARGET was passed, so the happy path costs zero
+# extra HTTP requests.
+if [ -z "$TARGET" ]; then echo "Fetching latest ${CHANNEL} version..." >&2; fi
+probe_result=$(download_file "${BASE_URL_PRIMARY}/${CHANNEL}" 2>/dev/null) || true
+if [ -n "$probe_result" ]; then
+ BASE_URL="$BASE_URL_PRIMARY"
+else
+ echo "Note: ${BASE_URL_PRIMARY} unreachable, falling back to direct GCS." >&2
+ BASE_URL="$BASE_URL_FALLBACK"
+ probe_result=$(download_file "${BASE_URL}/${CHANNEL}" 2>/dev/null) || true
+fi
+
+if [ -n "$TARGET" ]; then
+ version="$TARGET"
+else
+ version="$probe_result"
+ if [ -z "$version" ]; then
+ echo "Error: failed to fetch latest version from ${BASE_URL_PRIMARY}/${CHANNEL} and ${BASE_URL_FALLBACK}/${CHANNEL}" >&2
+ exit 1
+ fi
+fi
+
+if [ -n "$AUTH_SOURCE" ]; then
+ echo "Installing Grok $version ($platform, $AUTH_SOURCE)..." >&2
+else
+ echo "Installing Grok $version ($platform)..." >&2
+fi
+
+binary_path="$DOWNLOAD_DIR/grok-$platform"
+artifact_base="${BASE_URL}/grok-${version}-${platform}"
+
+if [ "$os" = "windows" ]; then
+ binary_path="${binary_path}.exe"
+fi
+
+echo " Downloading grok ${version}..." >&2
+if [ "$os" = "windows" ]; then
+ if ! download_file_parallel "${artifact_base}.exe" "$binary_path"; then
+ if ! download_file_parallel "$artifact_base" "$binary_path"; then
+ rm -f "$binary_path"
+ if is_not_found "${artifact_base}.exe"; then
+ echo "Error: Grok is not yet available for your system ($platform)." >&2
+ else
+ echo "Error: binary download failed (${artifact_base}.exe and ${artifact_base})" >&2
+ fi
+ exit 1
+ fi
+ fi
+elif ! download_file_parallel "$artifact_base" "$binary_path"; then
+ rm -f "$binary_path"
+ if is_not_found "$artifact_base"; then
+ echo "Error: Grok is not yet available for your system ($platform)." >&2
+ else
+ echo "Error: binary download failed from ${artifact_base}" >&2
+ fi
+ exit 1
+fi
+
+if [ "$os" = "windows" ]; then
+ # Symlinks require Developer Mode on Windows; copy instead.
+ # If the exe is locked by a running process, rename it aside then retry.
+ for bin_name in grok.exe ; do
+ rm -f "$BIN_DIR/$bin_name.old" 2>/dev/null || true # stale backup from prior update
+ if ! cp -f "$binary_path" "$BIN_DIR/$bin_name" 2>/dev/null; then
+ mv -f "$BIN_DIR/$bin_name" "$BIN_DIR/$bin_name.old" 2>/dev/null || true
+ if ! cp -f "$binary_path" "$BIN_DIR/$bin_name" 2>/dev/null; then
+ # Rollback: restore the old binary so the install isn't broken.
+ mv -f "$BIN_DIR/$bin_name.old" "$BIN_DIR/$bin_name" 2>/dev/null || true
+ echo "Error: failed to install $bin_name" >&2
+ exit 1
+ fi
+ fi
+ done
+ echo " Binary installed to $BIN_DIR/grok.exe" >&2
+else
+ chmod +x "$binary_path"
+ # Use relative symlinks when BIN_DIR and DOWNLOAD_DIR share a parent
+ # (default layout: ~/.grok/bin and ~/.grok/downloads are siblings).
+ # Relative symlinks survive Docker bind-mounts with a different $HOME.
+ if [ "$(dirname "$BIN_DIR")" = "$(dirname "$DOWNLOAD_DIR")" ]; then
+ link_target="../$(basename "$DOWNLOAD_DIR")/$(basename "$binary_path")"
+ else
+ link_target="$binary_path"
+ fi
+ ln -sf "$link_target" "$BIN_DIR/grok"
+ echo " Binary linked to $BIN_DIR/grok" >&2
+fi
+
+# # Generate shell completions (best-effort)
+# mkdir -p "$HOME/.grok/completions/bash" "$HOME/.grok/completions/zsh"
+# "$BIN_DIR/grok" completions bash > "$HOME/.grok/completions/bash/grok.bash" 2>/dev/null || true
+# "$BIN_DIR/grok" completions zsh > "$HOME/.grok/completions/zsh/_grok" 2>/dev/null || true
+# # Fish: write to the auto-loaded completions dir so it works immediately
+# if mkdir -p "$HOME/.config/fish/completions" 2>/dev/null; then
+# "$BIN_DIR/grok" completions fish > "$HOME/.config/fish/completions/grok.fish" 2>/dev/null || true
+# fi
+
+# Persist installer source and channel to config
+CONFIG_FILE="$HOME/.grok/config.toml"
+CLI_BLOCK="installer = \"internal\""
+if [ "$CHANNEL" != "stable" ]; then
+ CLI_BLOCK="${CLI_BLOCK}\nchannel = \"${CHANNEL}\""
+fi
+if [ ! -f "$CONFIG_FILE" ]; then
+ printf '[cli]\n%b\n' "$CLI_BLOCK" > "$CONFIG_FILE"
+elif grep -q '^\[cli\]' "$CONFIG_FILE"; then
+ tmp="$CONFIG_FILE.tmp.$$"
+ awk -v block="$CLI_BLOCK" '
+ /^\[cli\][[:space:]]*(#.*)?$/ { print; printf "%s\n", block; in_cli=1; next }
+ /^\[.*\][[:space:]]*(#.*)?$/ { in_cli=0 }
+ in_cli && /^[[:space:]]*(installer|channel)[[:space:]]*=/ { next }
+ { print }
+ ' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
+else
+ printf '\n[cli]\n%b\n' "$CLI_BLOCK" >> "$CONFIG_FILE"
+fi
+
+# Fetch managed_config.toml + requirements.toml from server (deployment key only).
+if [ -n "$GROK_DEPLOYMENT_KEY" ]; then
+ PROXY_URL="${GROK_PROXY_URL:-https://cli-chat-proxy.grok.com/v1}"
+ echo " Fetching deployment config..." >&2
+ DEPLOY_RESPONSE=$(curl -sS -f \
+ -H "Authorization: Bearer ${GROK_DEPLOYMENT_KEY}" \
+ "${PROXY_URL}/deployment/config" 2>/dev/null) || DEPLOY_RESPONSE=""
+ if [ -z "$DEPLOY_RESPONSE" ]; then
+ echo " Warning: failed to fetch deployment config from ${PROXY_URL}/deployment/config" >&2
+ fi
+ if [ -n "$DEPLOY_RESPONSE" ]; then
+ MANAGED_CONFIG=$(json_get "$DEPLOY_RESPONSE" "managed_config")
+ REQUIREMENTS=$(json_get "$DEPLOY_RESPONSE" "requirements")
+ if [ -n "$MANAGED_CONFIG" ] && [ "$MANAGED_CONFIG" != "null" ]; then
+ printf '%s\n' "$MANAGED_CONFIG" > "$HOME/.grok/managed_config.toml"
+ echo " Managed config applied." >&2
+ else
+ rm -f "$HOME/.grok/managed_config.toml"
+ fi
+ if [ -n "$REQUIREMENTS" ] && [ "$REQUIREMENTS" != "null" ]; then
+ printf '%s\n' "$REQUIREMENTS" > "$HOME/.grok/requirements.toml"
+ echo " Requirements applied." >&2
+ else
+ rm -f "$HOME/.grok/requirements.toml"
+ fi
+ fi
+fi
+
+if [ "$os" = "windows" ]; then
+ echo "Grok $version installed to $BIN_DIR/grok.exe" >&2
+else
+ echo "Grok $version installed to $BIN_DIR/grok" >&2
+fi
+
+# --- Ensure grok is on PATH ---
+
+path_has_dir() {
+ case ":$PATH:" in *":$1:"*) return 0 ;; *) return 1 ;; esac
+}
+
+# Try to symlink into a directory already on PATH so grok works immediately
+# without restarting the shell. Candidate dirs in preference order.
+SYMLINK_CREATED=""
+if [ "$os" != "windows" ] && ! path_has_dir "$BIN_DIR"; then
+ for candidate in "$HOME/.local/bin" "/usr/local/bin"; do
+ if path_has_dir "$candidate" && [ -d "$candidate" ] && [ -w "$candidate" ]; then
+ ln -sf "$BIN_DIR/grok" "$candidate/grok"
+ SYMLINK_CREATED="$candidate"
+ echo " Symlinked $candidate/grok -> $BIN_DIR/grok" >&2
+ break
+ fi
+ done
+fi
+
+
+echo "" >&2
+if path_has_dir "$BIN_DIR" || [ -n "$SYMLINK_CREATED" ]; then
+ echo "Run 'grok' to get started!" >&2
+elif [ -n "$config_file" ]; then
+ echo "Restart your terminal, then run 'grok' to get started!" >&2
+else
+ echo "Add $BIN_DIR to your PATH, then run 'grok' to get started:" >&2
+ echo ' export PATH="$HOME/.grok/bin:$PATH"' >&2
+fi
+
+if [ "$os" = "windows" ]; then
+ echo "To use grok from cmd.exe or PowerShell, add %USERPROFILE%\\.grok\\bin to your PATH." >&2
+fi