Created
June 9, 2026 20:32
-
-
Save akashsharma95/6962eefdc6c0cf04be59a9b5d59011a2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| set -eu | |
| # ============================================================================= | |
| # Kiro CLI Installation Script | |
| # ============================================================================= | |
| # Configuration | |
| BINARY_NAME="kiro-cli" | |
| CHAT_BINARY_NAME="kiro-cli-chat" | |
| LINUX_PACKAGE_NAME="kirocli" | |
| CLI_NAME="Kiro CLI" | |
| COMMAND_NAME="kiro-cli" | |
| Q_COMMAND="q" | |
| Q_COMMAND_PATH="$HOME/.local/bin/q" | |
| SCRIPT_URL="https://cli.kiro.dev/install" | |
| BASE_URL="https://prod.download.cli.kiro.dev" | |
| CHANNEL="stable" | |
| MANIFEST_URL="" | |
| MACOS_FILENAME="Kiro CLI.dmg" | |
| MACOS_FILENAME_ESCAPED="Kiro%20CLI.dmg" | |
| # Installation directories | |
| MACOS_APP_DIR="/Applications" | |
| LINUX_INSTALL_DIR="$HOME/.local/bin" | |
| DOWNLOAD_DIR="" # Will be set using mktemp in main() | |
| # Global variables | |
| use_musl=false | |
| mounted_dmg="" | |
| SUCCESS=false | |
| force_install=false | |
| # Set color codes if outputting to a terminal | |
| GREEN='' BOLD='' PURPLE='' ORANGE='' NC='' | |
| if [[ -t 1 ]]; then | |
| GREEN=$(printf '\033[0;32m') | |
| BOLD=$(printf '\033[1m') | |
| PURPLE=$(printf '\033[0;95m') | |
| ORANGE=$(printf '\033[38;5;214m') | |
| NC=$(printf '\033[0m') | |
| fi | |
| # ============================================================================= | |
| # Utility Functions | |
| # ============================================================================= | |
| log_info() { | |
| printf "%s\n" "$1" >&2 | |
| } | |
| log() { | |
| printf "%s✓%s %s\n" "$GREEN" "$NC" "$1" >&2 | |
| } | |
| print_header() { | |
| echo "Kiro CLI installer:" >&2 | |
| echo >&2 | |
| } | |
| success() { | |
| echo >&2 | |
| printf "🎉 %sInstallation complete!%s Happy coding!\n" "$BOLD" "$NC" >&2 | |
| echo >&2 | |
| echo "Next steps:" | |
| printf "Use the command \"%s%s%s\" to get started!\n" "$PURPLE" "$COMMAND_NAME" "$NC" >&2 | |
| echo >&2 | |
| } | |
| success_without_path() { | |
| echo >&2 | |
| printf "🎉 %sInstallation complete!%s Happy coding!\n" "$BOLD" "$NC" >&2 | |
| echo >&2 | |
| printf "1. %sImportant!%s Before you can continue, you must update your PATH to include:\n" "$ORANGE" "$NC" >&2 | |
| printf " %s\n" "$HOME/.local/bin" >&2 | |
| echo >&2 | |
| echo "Add it to your PATH by adding this line to your shell configuration file:" >&2 | |
| echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" >&2 | |
| echo >&2 | |
| printf "2. Use the command \"%s%s%s\" to get started!\n" "$PURPLE" "$COMMAND_NAME" "$NC" >&2 | |
| echo >&2 | |
| } | |
| error() { | |
| echo "⌠$1" >&2 | |
| exit 1 | |
| } | |
| warning() { | |
| echo "âš ï¸ $1" >&2 | |
| } | |
| # Parse command line arguments | |
| parse_args() { | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| --help|-h) | |
| show_help | |
| exit 0 | |
| ;; | |
| --force|-f) | |
| force_install=true | |
| shift | |
| ;; | |
| --channel) | |
| CHANNEL="$2" | |
| shift 2 | |
| ;; | |
| *) | |
| error "Unknown option: $1" | |
| ;; | |
| esac | |
| done | |
| } | |
| show_help() { | |
| cat << EOF | |
| $CLI_NAME Installation Script | |
| Usage: $0 [OPTIONS] | |
| Options: | |
| --help, -h Show this help message | |
| --channel CHANNEL Specify a release channel (default: stable) | |
| This script will: | |
| 1. Detect your platform and architecture | |
| 2. Download the appropriate $CLI_NAME package | |
| 3. Verify checksums | |
| 4. Install $CLI_NAME on your system | |
| 5. If Amazon Q CLI is already installed, automatically update it to Kiro CLI | |
| For more information, visit: https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-installing.html | |
| EOF | |
| } | |
| # Check for required dependencies | |
| check_dependencies() { | |
| local missing_deps=() | |
| # Check for downloader | |
| if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then | |
| missing_deps+=("curl or wget") | |
| fi | |
| # Check for unzip on Linux | |
| if [[ "$os" == "linux" ]] && ! command -v unzip >/dev/null 2>&1; then | |
| missing_deps+=("unzip") | |
| fi | |
| # Check for shasum/sha256sum | |
| if [[ "$os" == "darwin" ]] && ! command -v shasum >/dev/null 2>&1; then | |
| missing_deps+=("shasum") | |
| elif [[ "$os" == "linux" ]] && ! command -v sha256sum >/dev/null 2>&1; then | |
| missing_deps+=("sha256sum") | |
| fi | |
| if [[ ${#missing_deps[@]} -gt 0 ]]; then | |
| error "Missing required dependencies: ${missing_deps[*]}" | |
| fi | |
| } | |
| # Check if Amazon Q CLI is already installed | |
| check_existing_installation() { | |
| if [[ "$force_install" == "true" ]]; then | |
| return 0 | |
| fi | |
| # Check if q command exists anywhere in PATH or in standard locations | |
| local user_q_location | |
| user_q_location=$(command -v "$Q_COMMAND" 2>/dev/null || true) | |
| if [[ -z "$user_q_location" ]]; then | |
| # q not in PATH, check standard installation locations | |
| if [[ -x "$Q_COMMAND_PATH" ]]; then | |
| user_q_location="$Q_COMMAND_PATH" | |
| fi | |
| fi | |
| if [[ -z "$user_q_location" ]]; then | |
| # q not found, proceed with installation | |
| return 0 | |
| fi | |
| log_info "Detected existing Amazon Q CLI installation" | |
| log_info "Kiro CLI is the new version of Amazon Q CLI. Updating to latest version." | |
| echo | |
| # Resolve one level of symlink if it exists | |
| local user_real_q_location="$user_q_location" | |
| if [[ -L "$user_q_location" ]]; then | |
| user_real_q_location=$(readlink "$user_q_location") | |
| fi | |
| # Determine if the binary is in an updatable location | |
| local is_updatable=false | |
| # Check for Linux: ~/.local/bin/q | |
| if [[ "$user_real_q_location" == "$Q_COMMAND_PATH" ]]; then | |
| is_updatable=true | |
| fi | |
| # Check for macOS: /Applications | |
| if [[ "$user_real_q_location" == /Applications/* ]]; then | |
| is_updatable=true | |
| fi | |
| if [[ "$is_updatable" == "true" ]]; then | |
| if "$user_q_location" update; then | |
| echo | |
| success | |
| SUCCESS=true | |
| exit 0 | |
| else | |
| echo | |
| error "Q CLI update failed. Please run 'q update' manually, or uninstall Q CLI and rerun this script to install $CLI_NAME." | |
| fi | |
| else | |
| echo | |
| # q exists but not in ~/.local/bin | |
| warning "Q CLI at $user_real_q_location does not support auto-update. Please uninstall it and rerun this script to install $CLI_NAME." | |
| exit 1 | |
| fi | |
| } | |
| # Download function that works with both curl and wget | |
| download_file() { | |
| local url="$1" | |
| local output="${2:-}" | |
| if command -v curl >/dev/null 2>&1; then | |
| if [[ -n "$output" ]]; then | |
| curl -fsSL -o "$output" "$url" || error "Failed to download $url" | |
| else | |
| curl -fsSL "$url" || error "Failed to download $url" | |
| fi | |
| elif command -v wget >/dev/null 2>&1; then | |
| if [[ -n "$output" ]]; then | |
| wget -q -O "$output" "$url" || error "Failed to download $url" | |
| else | |
| wget -q -O - "$url" || error "Failed to download $url" | |
| fi | |
| else | |
| error "No downloader available" | |
| fi | |
| } | |
| # Get checksum from manifest.json | |
| get_checksum() { | |
| local json="$1" | |
| local filename="$2" | |
| if command -v jq >/dev/null 2>&1; then | |
| # Use jq to find the package with matching download filename | |
| echo "$json" | jq -r ".packages[] | select(.download | endswith(\"$filename\")) | .sha256 // empty" | |
| else | |
| # Fallback: parse JSON manually | |
| # Normalize to single line | |
| local package_obj | |
| package_obj=$(echo "$json" | tr -d '\n\r' | grep -o '{[^}]*"download"[^}]*'"$filename"'[^}]*}') | |
| if [[ -n "$package_obj" ]]; then | |
| if [[ $package_obj =~ \"sha256\"[[:space:]]*:[[:space:]]*\"([a-f0-9]{64})\" ]]; then | |
| echo "${BASH_REMATCH[1]}" | |
| return 0 | |
| fi | |
| fi | |
| return 1 | |
| fi | |
| } | |
| # ============================================================================= | |
| # Platform Detection | |
| # ============================================================================= | |
| detect_platform() { | |
| case "$(uname -s)" in | |
| Darwin) os="darwin" ;; | |
| Linux) os="linux" ;; | |
| *) error "Unsupported operating system: $(uname -s)" ;; | |
| esac | |
| case "$(uname -m)" in | |
| x86_64|amd64) arch="x86_64" ;; | |
| arm64|aarch64) arch="aarch64" ;; | |
| *) error "Unsupported architecture: $(uname -m)" ;; | |
| esac | |
| } | |
| # Minimum required glibc version | |
| GLIBC_MIN_MAJOR=2 | |
| GLIBC_MIN_MINOR=34 | |
| # Check if a glibc version meets the minimum requirement | |
| is_glibc_version_sufficient() { | |
| local version="$1" | |
| local major minor | |
| IFS='.' read -r major minor <<EOF | |
| $version | |
| EOF | |
| if [[ -z "$minor" ]]; then | |
| minor=0 | |
| fi | |
| if (( major > GLIBC_MIN_MAJOR || (major == GLIBC_MIN_MAJOR && minor >= GLIBC_MIN_MINOR) )); then | |
| return 0 | |
| else | |
| return 1 | |
| fi | |
| } | |
| # Check glibc version for Linux | |
| check_glibc() { | |
| if [[ "$os" != "linux" ]]; then | |
| return 0 | |
| fi | |
| local glibc_version | |
| # Method 1: Try common libc.so.6 locations | |
| for LIBC_PATH in /lib64/libc.so.6 /lib/libc.so.6 /usr/lib/x86_64-linux-gnu/libc.so.6 \ | |
| /lib/aarch64-linux-gnu/libc.so.6; do | |
| if [[ -f "$LIBC_PATH" ]]; then | |
| glibc_version=$("$LIBC_PATH" | sed -n 's/^GNU C Library (.*) stable release version \([0-9]*\)\.\([0-9]*\).*$/\1.\2/p') | |
| if [[ -n "$glibc_version" ]]; then | |
| if is_glibc_version_sufficient "$glibc_version"; then | |
| return 0 | |
| else | |
| use_musl=true | |
| return 0 | |
| fi | |
| fi | |
| fi | |
| done | |
| # Method 2: Try ldd --version as a more reliable alternative | |
| if command -v ldd >/dev/null 2>&1; then | |
| glibc_version=$(ldd --version 2>/dev/null | head -n 1 | grep -o '[0-9]\+\.[0-9]\+' | head -n 1) | |
| if [[ -n "$glibc_version" ]]; then | |
| if is_glibc_version_sufficient "$glibc_version"; then | |
| return 0 | |
| else | |
| use_musl=true | |
| return 0 | |
| fi | |
| fi | |
| fi | |
| # Method 3: Try getconf as a fallback | |
| if command -v getconf >/dev/null 2>&1; then | |
| glibc_version=$(getconf GNU_LIBC_VERSION 2>/dev/null | awk '{print $2}') | |
| if [[ -n "$glibc_version" ]]; then | |
| if is_glibc_version_sufficient "$glibc_version"; then | |
| return 0 | |
| else | |
| use_musl=true | |
| return 0 | |
| fi | |
| fi | |
| fi | |
| # Check for musl directly | |
| 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 | |
| use_musl=true | |
| return 0 | |
| fi | |
| use_musl=true | |
| return 0 | |
| } | |
| # ============================================================================= | |
| # Download and Installation Functions | |
| # ============================================================================= | |
| # Download and verify file | |
| download_and_verify() { | |
| local download_url="$1" | |
| local filename="$2" | |
| local file_path="$DOWNLOAD_DIR/$filename" | |
| download_file "$download_url" "$file_path" | |
| local manifest_json | |
| manifest_json=$(download_file "$MANIFEST_URL") | |
| local expected_checksum | |
| expected_checksum=$(get_checksum "$manifest_json" "$filename") | |
| if [[ -z "$expected_checksum" ]] || [[ ! "$expected_checksum" =~ ^[a-f0-9]{64}$ ]]; then | |
| error "Could not find valid checksum for $filename" | |
| fi | |
| local actual_checksum | |
| if [[ "$os" == "darwin" ]]; then | |
| actual_checksum=$(shasum -a 256 "$file_path" | cut -d' ' -f1) | |
| else | |
| actual_checksum=$(sha256sum "$file_path" | cut -d' ' -f1) | |
| fi | |
| if [[ "$actual_checksum" != "$expected_checksum" ]]; then | |
| rm -f "$file_path" | |
| error "Checksum verification failed. Expected: $expected_checksum, Got: $actual_checksum" | |
| fi | |
| } | |
| # Install on macOS | |
| install_macos() { | |
| local dmg_path="$1" | |
| if [[ ! -f "$dmg_path" ]]; then | |
| error "DMG file not found: $dmg_path" | |
| fi | |
| local mount_path | |
| mount_path=$(hdiutil attach "$dmg_path" -nobrowse -readonly | grep Volumes | cut -f 3) | |
| if [[ -z "$mount_path" ]]; then | |
| error "Failed to mount DMG" | |
| fi | |
| mounted_dmg="$mount_path" | |
| # Find the .app bundle | |
| local app_bundle | |
| app_bundle=$(find "$mount_path" -name "*.app" -maxdepth 1 -type d | head -1) | |
| if [[ -z "$app_bundle" ]]; then | |
| error "Could not find application bundle in DMG" | |
| fi | |
| local app_name | |
| app_name=$(basename "$app_bundle") | |
| # Check if app already exists and warn user | |
| if [[ -d "$MACOS_APP_DIR/$app_name" ]]; then | |
| warning "Existing $CLI_NAME installation found" | |
| echo "Do you want to replace it? (y/N): " | |
| read -r response < /dev/tty | |
| if [[ ! "$response" =~ ^[Yy]$ ]]; then | |
| error "Installation cancelled by user" | |
| fi | |
| rm -rf "$MACOS_APP_DIR/$app_name" | |
| log "Existing installation removed" | |
| fi | |
| # Use ditto instead of cp for better handling of extended attributes and resource forks | |
| if ! ditto "$app_bundle" "$MACOS_APP_DIR/$app_name"; then | |
| error "Failed to copy application bundle to $MACOS_APP_DIR" | |
| fi | |
| open -g -a "$MACOS_APP_DIR/$app_name" --args --no-dashboard | |
| sleep 3 | |
| } | |
| # Install on Linux | |
| install_linux() { | |
| local zip_path="$1" | |
| local extract_dir="$DOWNLOAD_DIR/extract" | |
| mkdir -p "$extract_dir" | |
| # Check if binary already exists and warn user | |
| if [[ -f "$LINUX_INSTALL_DIR/$BINARY_NAME" ]]; then | |
| warning "Existing $CLI_NAME installation found" | |
| echo "Do you want to replace it? (y/N): " | |
| read -r response < /dev/tty | |
| if [[ ! "$response" =~ ^[Yy]$ ]]; then | |
| error "Installation cancelled by user" | |
| fi | |
| rm -f "$LINUX_INSTALL_DIR/$BINARY_NAME" | |
| rm -f "$LINUX_INSTALL_DIR/$CHAT_BINARY_NAME" | |
| log "Existing installation removed" | |
| fi | |
| unzip -q "$zip_path" -d "$extract_dir" | |
| # Find and run the install script | |
| local install_script="$extract_dir/${LINUX_PACKAGE_NAME}/install.sh" | |
| if [[ ! -f "$install_script" ]]; then | |
| error "Install script not found in archive" | |
| fi | |
| chmod +x "$install_script" | |
| KIRO_CLI_SKIP_SETUP=1 "$install_script" | |
| } | |
| # Cleanup function - removes temporary download directory | |
| cleanup() { | |
| if [ "$SUCCESS" = false ]; then | |
| error "Installation failed. Cleaning up..." | |
| fi | |
| # Detach mounted DMG if any | |
| if [[ -n "$mounted_dmg" ]]; then | |
| hdiutil detach "$mounted_dmg" -quiet 2>/dev/null || true | |
| fi | |
| # Remove entire download directory | |
| if [[ -n "$DOWNLOAD_DIR" ]] && [[ -d "$DOWNLOAD_DIR" ]]; then | |
| rm -rf "$DOWNLOAD_DIR" | |
| fi | |
| } | |
| # ============================================================================= | |
| # Main Installation Process | |
| # ============================================================================= | |
| main() { | |
| # Parse command line arguments | |
| parse_args "$@" | |
| print_header | |
| # Check if already installed (unless --force is used) | |
| check_existing_installation | |
| # Set up cleanup trap | |
| trap cleanup EXIT | |
| # Create temporary download directory | |
| DOWNLOAD_DIR=$(mktemp -d "${TMPDIR:-/tmp}/${BINARY_NAME}-install-XXXXXX") | |
| # Platform detection and validation | |
| detect_platform | |
| check_dependencies | |
| check_glibc | |
| # Construct URLs with channel | |
| local channel_base_url="${BASE_URL}/${CHANNEL}" | |
| MANIFEST_URL="${channel_base_url}/latest/manifest.json" | |
| # Get download information | |
| local download_url filename | |
| if [[ "$os" == "darwin" ]]; then | |
| filename="$MACOS_FILENAME" | |
| download_url="${channel_base_url}/latest/${MACOS_FILENAME_ESCAPED}" | |
| else | |
| # Linux | |
| if [[ "$use_musl" == "true" ]]; then | |
| filename="${LINUX_PACKAGE_NAME}-${arch}-linux-musl.zip" | |
| else | |
| filename="${LINUX_PACKAGE_NAME}-${arch}-linux.zip" | |
| fi | |
| download_url="${channel_base_url}/latest/$filename" | |
| fi | |
| # Download and verify | |
| log_info "Downloading package..." | |
| download_and_verify "$download_url" "$filename" | |
| local downloaded_file="$DOWNLOAD_DIR/$filename" | |
| log "Downloaded and extracted" | |
| # Install based on platform | |
| if [[ "$os" == "darwin" ]]; then | |
| install_macos "$downloaded_file" | |
| else | |
| install_linux "$downloaded_file" | |
| fi | |
| log "Package installed successfully" | |
| SUCCESS=true | |
| # Check if ~/.local/bin is on PATH and show appropriate success message | |
| if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then | |
| success_without_path | |
| else | |
| success | |
| fi | |
| } | |
| # Run main function | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment