Skip to content

Instantly share code, notes, and snippets.

@akashsharma95
Created June 9, 2026 20:32
Show Gist options
  • Select an option

  • Save akashsharma95/6962eefdc6c0cf04be59a9b5d59011a2 to your computer and use it in GitHub Desktop.

Select an option

Save akashsharma95/6962eefdc6c0cf04be59a9b5d59011a2 to your computer and use it in GitHub Desktop.
#!/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