#!/usr/bin/env bash set -euo pipefail # Koder CLI Installer # Downloads pre-built binary package from Koder Flow releases # # Usage: curl -fsSL https://install.kode.koder.dev | bash # # Environment variables: # KODER_VERSION - Install specific version (e.g., "v2.4.2") # KODER_INSTALL_DIR - Custom install directory (default: ~/.koder/cli) # KODER_TOKEN - Auth token for private repo access # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[38;2;91;200;245m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' # Configuration INSTALL_DIR="${KODER_INSTALL_DIR:-$HOME/.koder/cli}" FLOW_API="https://flow.koder.dev/api/v1/repos/Koder/koder" FLOW_AUTH="root:koder@123" requested_version="${KODER_VERSION:-}" # Detect OS and architecture detect_platform() { local os arch os=$(uname -s | tr '[:upper:]' '[:lower:]') arch=$(uname -m) case "$arch" in x86_64) arch="x64" ;; aarch64) arch="arm64" ;; esac case "$os" in darwin|linux) ;; *) print_error "Unsupported OS: $os" exit 1 ;; esac if [[ "$arch" != "x64" && "$arch" != "arm64" ]]; then print_error "Unsupported architecture: $arch" exit 1 fi platform="$os-$arch" } print_step() { echo -e "${BLUE}→${NC} ${DIM}$1${NC}"; } print_ok() { echo -e "${GREEN}✓${NC} $1"; } print_error() { echo -e "${RED}✗${NC} ${RED}$1${NC}" >&2; } # Check prerequisites check_prerequisites() { print_step "Checking prerequisites" for cmd in curl tar node; do if ! command -v "$cmd" >/dev/null 2>&1; then print_error "'$cmd' is required but not installed" if [ "$cmd" = "node" ]; then echo -e "${DIM}Install Node.js v20+: https://nodejs.org/${NC}" fi exit 1 fi done # Check Node.js version (>= 20) local node_major node_major=$(node -v | sed 's/v//' | cut -d. -f1) if [ "$node_major" -lt 20 ] 2>/dev/null; then print_error "Node.js v20+ required (found $(node -v))" echo -e "${DIM}Update: https://nodejs.org/${NC}" exit 1 fi print_ok "Prerequisites: curl, tar, node $(node -v)" } # Remove conflicting installations automatically check_conflicts() { local bin_dir="$INSTALL_DIR/bin" local existing_koder existing_kode=$(command -v kode 2>/dev/null || true) if [ -n "$existing_kode" ] && [ "$existing_kode" != "$bin_dir/kode" ]; then print_step "Removing existing kode at $existing_kode" # Try npm uninstall (covers both npm global and npm link) if npm list -g kode --depth=0 >/dev/null 2>&1; then npm uninstall -g kode 2>/dev/null || true print_ok "Removed npm global package" elif [ -L "$existing_kode" ]; then # Symlink (npm link) — remove it rm -f "$existing_kode" 2>/dev/null || true print_ok "Removed symlink $existing_kode" else # Unknown installation — try npm uninstall as fallback npm uninstall -g kode 2>/dev/null || true print_ok "Removed existing installation" fi else print_ok "No conflicting installations" fi } # Fetch release info from Koder Flow API get_release_info() { if [ -z "$requested_version" ]; then print_step "Fetching latest release" local response response=$(curl -fsSL -u "$FLOW_AUTH" "$FLOW_API/releases?limit=1" 2>&1) || { print_error "Failed to fetch releases" echo -e "${DIM}Check your internet connection${NC}" exit 1 } # Extract tag and find matching asset download URL if command -v jq >/dev/null 2>&1; then cli_tag=$(echo "$response" | jq -r '.[0].tag_name' 2>/dev/null) download_url=$(echo "$response" | \ jq -r --arg plat "$platform" \ '.[0].assets[] | select(.name | contains($plat) and endswith(".tar.gz")) | .browser_download_url' 2>/dev/null | head -1) else cli_tag=$(echo "$response" | grep -o '"tag_name": *"[^"]*"' | head -1 | cut -d'"' -f4) download_url=$(echo "$response" | grep -o "\"browser_download_url\": *\"[^\"]*${platform}[^\"]*\.tar\.gz\"" | head -1 | cut -d'"' -f4) fi if [ -z "$cli_tag" ] || [ -z "${download_url:-}" ]; then print_error "No package found for platform: $platform" exit 1 fi else print_step "Fetching version $requested_version" cli_tag="$requested_version" local response response=$(curl -fsSL -u "$FLOW_AUTH" "$FLOW_API/releases/tags/$requested_version" 2>&1) || { print_error "Version $requested_version not found" exit 1 } if command -v jq >/dev/null 2>&1; then download_url=$(echo "$response" | \ jq -r --arg plat "$platform" \ '.assets[] | select(.name | contains($plat) and endswith(".tar.gz")) | .browser_download_url' 2>/dev/null | head -1) else download_url=$(echo "$response" | grep -o "\"browser_download_url\": *\"[^\"]*${platform}[^\"]*\.tar\.gz\"" | head -1 | cut -d'"' -f4) fi if [ -z "${download_url:-}" ]; then print_error "No package for $platform in release $cli_tag" exit 1 fi fi print_ok "Found ${BLUE}${BOLD}$cli_tag${NC} for $platform" } # Show upgrade info if already installed check_existing() { if [ -f "$INSTALL_DIR/VERSION" ]; then local installed_version installed_version=$(cat "$INSTALL_DIR/VERSION" 2>/dev/null || echo "") local release_version release_version=$(echo "$cli_tag" | sed 's/^v//') if [ -n "$installed_version" ] && [ "$installed_version" != "$release_version" ]; then echo -e "${YELLOW}Upgrading from v$installed_version to $cli_tag${NC}" elif [ -n "$installed_version" ]; then echo -e "${DIM}Reinstalling v$installed_version${NC}" fi fi } # Download and install install_koder() { print_step "Downloading Koder CLI" local tmp_dir tmp_dir=$(mktemp -d) trap "rm -rf '$tmp_dir'" EXIT local package_file="$tmp_dir/koder.tar.gz" echo -e "${BLUE}${BOLD}" if ! curl -#fSL --http1.1 -u "$FLOW_AUTH" -o "$package_file" "$download_url"; then echo -e "${NC}" print_error "Download failed" echo -e "${DIM}URL: $download_url${NC}" exit 1 fi echo -e "${NC}" local file_size file_size=$(stat -c%s "$package_file" 2>/dev/null || stat -f%z "$package_file" 2>/dev/null || echo "0") local human_size human_size=$(numfmt --to=iec "$file_size" 2>/dev/null || echo "${file_size} bytes") print_ok "Downloaded ($human_size)" # Remove existing installation if [ -d "$INSTALL_DIR" ]; then rm -rf "$INSTALL_DIR" fi # Extract print_step "Installing to $INSTALL_DIR" mkdir -p "$INSTALL_DIR" if ! tar -xzf "$package_file" -C "$INSTALL_DIR" --strip-components=1; then print_error "Failed to extract package" exit 1 fi # Ensure binaries are executable chmod +x "$INSTALL_DIR/bin/kode" # Create backward-compatible symlink ln -sf kode "$INSTALL_DIR/bin/koder" print_ok "Installed to ${BLUE}$INSTALL_DIR${NC}" } # Configure PATH configure_path() { local bin_dir="$INSTALL_DIR/bin" local XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config} print_step "Configuring PATH" # Already in PATH? if echo "$PATH" | tr ':' '\n' | grep -qx "$bin_dir"; then print_ok "Already in PATH" return fi local current_shell current_shell=$(basename "${SHELL:-bash}") local config_file="" case $current_shell in fish) config_file="$HOME/.config/fish/config.fish" mkdir -p "$(dirname "$config_file")" ;; zsh) for f in "$HOME/.zshrc" "$HOME/.zshenv" "$XDG_CONFIG_HOME/zsh/.zshrc"; do [ -f "$f" ] && config_file="$f" && break done [ -z "$config_file" ] && config_file="$HOME/.zshrc" ;; bash) for f in "$HOME/.bashrc" "$HOME/.bash_profile" "$HOME/.profile"; do [ -f "$f" ] && config_file="$f" && break done [ -z "$config_file" ] && config_file="$HOME/.bashrc" ;; *) config_file="$HOME/.profile" ;; esac touch "$config_file" if ! grep -q "$bin_dir" "$config_file" 2>/dev/null; then case $current_shell in fish) echo -e "\n# Koder CLI\nfish_add_path $bin_dir" >> "$config_file" ;; *) echo -e "\n# Koder CLI\nexport PATH=\"$bin_dir:\$PATH\"" >> "$config_file" ;; esac print_ok "Added to PATH in $(basename "$config_file")" else print_ok "Already in PATH config" fi } # Register koder:// URI scheme handler register_scheme() { print_step "Registering koder:// URI scheme" if "$INSTALL_DIR/bin/kode" register-scheme >/dev/null 2>&1; then print_ok "URI scheme koder:// registered" else echo -e "${DIM} (optional — run 'kode register-scheme' later)${NC}" fi } # Success message print_success() { echo "" echo -e "${GREEN}${BOLD} ✓ Koder CLI installed successfully${NC}" echo "" echo -e " Start using ${BLUE}${BOLD}kode${NC} now:" echo "" echo -e " ${YELLOW}${BOLD}exec \$SHELL${NC}" echo "" echo -e " ${DIM}(or open a new terminal)${NC}" echo "" echo -e " ${DIM}To uninstall: ${BLUE}kode uninstall${NC}" echo "" } # Main main() { echo "" echo -e "${BLUE}${BOLD} Koder CLI Installer${NC}" echo "" detect_platform print_ok "Platform: ${BLUE}${BOLD}$platform${NC}" check_prerequisites check_conflicts get_release_info check_existing install_koder configure_path register_scheme print_success } main "$@"