96f5241aa1
Add support for persistently adding the install directory to the Windows user PATH via the registry. This works from Git Bash (MINGW64), MSYS2, and Cygwin, converting paths appropriately and using PowerShell to modify the registry. Also adds to the current session's PATH for immediate use.
535 lines
16 KiB
Bash
Executable File
535 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
APP=kilo # kilocode_change
|
|
|
|
MUTED='\033[0;2m'
|
|
RED='\033[0;31m'
|
|
ORANGE='\033[38;5;214m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# kilocode_change start - usage text
|
|
usage() {
|
|
cat <<EOF
|
|
Kilo Code Installer
|
|
|
|
Usage: install [options]
|
|
|
|
Options:
|
|
-h, --help Display this help message
|
|
-v, --version <version> Install a specific version (e.g., 1.0.180)
|
|
-b, --binary <path> Install from a local binary instead of downloading
|
|
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
|
|
|
|
Examples:
|
|
curl -fsSL https://kilo.ai/cli/install | bash
|
|
curl -fsSL https://kilo.ai/cli/install | bash -s -- --version 1.0.180
|
|
./install --binary /path/to/kilo
|
|
EOF
|
|
}
|
|
# kilocode_change end
|
|
|
|
requested_version=${VERSION:-}
|
|
no_modify_path=false
|
|
binary_path=""
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
-v|--version)
|
|
if [[ -n "${2:-}" ]]; then
|
|
requested_version="$2"
|
|
shift 2
|
|
else
|
|
echo -e "${RED}Error: --version requires a version argument${NC}"
|
|
exit 1
|
|
fi
|
|
;;
|
|
-b|--binary)
|
|
if [[ -n "${2:-}" ]]; then
|
|
binary_path="$2"
|
|
shift 2
|
|
else
|
|
echo -e "${RED}Error: --binary requires a path argument${NC}"
|
|
exit 1
|
|
fi
|
|
;;
|
|
--no-modify-path)
|
|
no_modify_path=true
|
|
shift
|
|
;;
|
|
*)
|
|
echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
INSTALL_DIR=$HOME/.kilo/bin # kilocode_change
|
|
mkdir -p "$INSTALL_DIR"
|
|
|
|
# If --binary is provided, skip all download/detection logic
|
|
if [ -n "$binary_path" ]; then
|
|
if [ ! -f "$binary_path" ]; then
|
|
echo -e "${RED}Error: Binary not found at ${binary_path}${NC}"
|
|
exit 1
|
|
fi
|
|
specific_version="local"
|
|
else
|
|
raw_os=$(uname -s)
|
|
os=$(echo "$raw_os" | tr '[:upper:]' '[:lower:]')
|
|
case "$raw_os" in
|
|
Darwin*) os="darwin" ;;
|
|
Linux*) os="linux" ;;
|
|
MINGW*|MSYS*|CYGWIN*) os="windows" ;;
|
|
esac
|
|
|
|
arch=$(uname -m)
|
|
if [[ "$arch" == "aarch64" ]]; then
|
|
arch="arm64"
|
|
fi
|
|
if [[ "$arch" == "x86_64" ]]; then
|
|
arch="x64"
|
|
fi
|
|
|
|
if [ "$os" = "darwin" ] && [ "$arch" = "x64" ]; then
|
|
rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0)
|
|
if [ "$rosetta_flag" = "1" ]; then
|
|
arch="arm64"
|
|
fi
|
|
fi
|
|
|
|
combo="$os-$arch"
|
|
case "$combo" in
|
|
linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64)
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unsupported OS/Arch: $os/$arch${NC}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
archive_ext=".zip"
|
|
if [ "$os" = "linux" ]; then
|
|
archive_ext=".tar.gz"
|
|
fi
|
|
|
|
is_musl=false
|
|
if [ "$os" = "linux" ]; then
|
|
if [ -f /etc/alpine-release ]; then
|
|
is_musl=true
|
|
fi
|
|
|
|
if command -v ldd >/dev/null 2>&1; then
|
|
if ldd --version 2>&1 | grep -qi musl; then
|
|
is_musl=true
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
needs_baseline=false
|
|
if [ "$arch" = "x64" ]; then
|
|
if [ "$os" = "linux" ]; then
|
|
if ! grep -qwi avx2 /proc/cpuinfo 2>/dev/null; then
|
|
needs_baseline=true
|
|
fi
|
|
fi
|
|
|
|
if [ "$os" = "darwin" ]; then
|
|
avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0)
|
|
if [ "$avx2" != "1" ]; then
|
|
needs_baseline=true
|
|
fi
|
|
fi
|
|
|
|
if [ "$os" = "windows" ]; then
|
|
ps="(Add-Type -MemberDefinition \"[DllImport(\"\"kernel32.dll\"\")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);\" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)"
|
|
out=""
|
|
if command -v powershell.exe >/dev/null 2>&1; then
|
|
out=$(powershell.exe -NoProfile -NonInteractive -Command "$ps" 2>/dev/null || true)
|
|
elif command -v pwsh >/dev/null 2>&1; then
|
|
out=$(pwsh -NoProfile -NonInteractive -Command "$ps" 2>/dev/null || true)
|
|
fi
|
|
out=$(echo "$out" | tr -d '\r' | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')
|
|
if [ "$out" != "true" ] && [ "$out" != "1" ]; then
|
|
needs_baseline=true
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
target="$os-$arch"
|
|
if [ "$needs_baseline" = "true" ]; then
|
|
target="$target-baseline"
|
|
fi
|
|
if [ "$is_musl" = "true" ]; then
|
|
target="$target-musl"
|
|
fi
|
|
|
|
filename="$APP-$target$archive_ext"
|
|
|
|
|
|
if [ "$os" = "linux" ]; then
|
|
if ! command -v tar >/dev/null 2>&1; then
|
|
echo -e "${RED}Error: 'tar' is required but not installed.${NC}"
|
|
exit 1
|
|
fi
|
|
else
|
|
if ! command -v unzip >/dev/null 2>&1; then
|
|
echo -e "${RED}Error: 'unzip' is required but not installed.${NC}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$requested_version" ]; then
|
|
url="https://github.com/Kilo-Org/kilocode/releases/latest/download/$filename"
|
|
specific_version=$(curl -s https://api.github.com/repos/Kilo-Org/kilocode/releases/latest | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
|
|
|
|
if [[ $? -ne 0 || -z "$specific_version" ]]; then
|
|
echo -e "${RED}Failed to fetch version information${NC}"
|
|
exit 1
|
|
fi
|
|
else
|
|
# Strip leading 'v' if present
|
|
requested_version="${requested_version#v}"
|
|
url="https://github.com/Kilo-Org/kilocode/releases/download/v${requested_version}/$filename"
|
|
specific_version=$requested_version
|
|
|
|
# Verify the release exists before downloading
|
|
http_status=$(curl -sI -o /dev/null -w "%{http_code}" "https://github.com/Kilo-Org/kilocode/releases/tag/v${requested_version}")
|
|
if [ "$http_status" = "404" ]; then
|
|
echo -e "${RED}Error: Release v${requested_version} not found${NC}"
|
|
echo -e "${MUTED}Available releases: https://github.com/Kilo-Org/kilocode/releases${NC}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
print_message() {
|
|
local level=$1
|
|
local message=$2
|
|
local color=""
|
|
|
|
case $level in
|
|
info) color="${NC}" ;;
|
|
warning) color="${NC}" ;;
|
|
error) color="${RED}" ;;
|
|
esac
|
|
|
|
echo -e "${color}${message}${NC}"
|
|
}
|
|
|
|
# kilocode_change start
|
|
check_version() {
|
|
if command -v kilo >/dev/null 2>&1; then
|
|
kilo_path=$(which kilo)
|
|
|
|
## Check the installed version
|
|
installed_version=$(kilo --version 2>/dev/null || echo "")
|
|
|
|
if [[ "$installed_version" != "$specific_version" ]]; then
|
|
print_message info "${MUTED}Installed version: ${NC}$installed_version."
|
|
else
|
|
print_message info "${MUTED}Version ${NC}$specific_version${MUTED} already installed"
|
|
exit 0
|
|
fi
|
|
fi
|
|
}
|
|
# kilocode_change end
|
|
|
|
unbuffered_sed() {
|
|
if echo | sed -u -e "" >/dev/null 2>&1; then
|
|
sed -nu "$@"
|
|
elif echo | sed -l -e "" >/dev/null 2>&1; then
|
|
sed -nl "$@"
|
|
else
|
|
local pad="$(printf "\n%512s" "")"
|
|
sed -ne "s/$/\\${pad}/" "$@"
|
|
fi
|
|
}
|
|
|
|
print_progress() {
|
|
local bytes="$1"
|
|
local length="$2"
|
|
[ "$length" -gt 0 ] || return 0
|
|
|
|
local width=50
|
|
local percent=$(( bytes * 100 / length ))
|
|
[ "$percent" -gt 100 ] && percent=100
|
|
local on=$(( percent * width / 100 ))
|
|
local off=$(( width - on ))
|
|
|
|
local filled=$(printf "%*s" "$on" "")
|
|
filled=${filled// /■}
|
|
local empty=$(printf "%*s" "$off" "")
|
|
empty=${empty// /・}
|
|
|
|
printf "\r${ORANGE}%s%s %3d%%${NC}" "$filled" "$empty" "$percent" >&4
|
|
}
|
|
|
|
download_with_progress() {
|
|
local url="$1"
|
|
local output="$2"
|
|
|
|
if [ -t 2 ]; then
|
|
exec 4>&2
|
|
else
|
|
exec 4>/dev/null
|
|
fi
|
|
|
|
local tmp_dir=${TMPDIR:-/tmp}
|
|
local basename="${tmp_dir}/kilo_install_$$" # kilocode_change
|
|
local tracefile="${basename}.trace"
|
|
|
|
rm -f "$tracefile"
|
|
mkfifo "$tracefile"
|
|
|
|
# Hide cursor
|
|
printf "\033[?25l" >&4
|
|
|
|
trap "trap - RETURN; rm -f \"$tracefile\"; printf '\033[?25h' >&4; exec 4>&-" RETURN
|
|
|
|
(
|
|
curl --trace-ascii "$tracefile" -fsL -o "$output" "$url" # kilocode_change
|
|
) &
|
|
local curl_pid=$!
|
|
|
|
unbuffered_sed \
|
|
-e 'y/ACDEGHLNORTV/acdeghlnortv/' \
|
|
-e '/^0000: content-length:/p' \
|
|
-e '/^<= recv data/p' \
|
|
"$tracefile" | \
|
|
{
|
|
local length=0
|
|
local bytes=0
|
|
|
|
while IFS=" " read -r -a line; do
|
|
[ "${#line[@]}" -lt 2 ] && continue
|
|
local tag="${line[0]} ${line[1]}"
|
|
|
|
if [ "$tag" = "0000: content-length:" ]; then
|
|
length="${line[2]}"
|
|
length=$(echo "$length" | tr -d '\r')
|
|
bytes=0
|
|
elif [ "$tag" = "<= recv" ]; then
|
|
local size="${line[3]}"
|
|
bytes=$(( bytes + size ))
|
|
if [ "$length" -gt 0 ]; then
|
|
print_progress "$bytes" "$length"
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
wait $curl_pid
|
|
local ret=$?
|
|
echo "" >&4
|
|
return $ret
|
|
}
|
|
|
|
download_and_install() {
|
|
# kilocode_change start
|
|
print_message info "\n${MUTED}Installing ${NC}Kilo Code ${MUTED}version: ${NC}$specific_version"
|
|
local tmp_dir="${TMPDIR:-/tmp}/kilo_install_$$"
|
|
# kilocode_change end
|
|
mkdir -p "$tmp_dir"
|
|
|
|
if [[ "$os" == "windows" ]] || ! [ -t 2 ] || ! download_with_progress "$url" "$tmp_dir/$filename"; then
|
|
# Fallback to standard curl on Windows, non-TTY environments, or if custom progress fails
|
|
curl -f# -L -o "$tmp_dir/$filename" "$url" # kilocode_change
|
|
fi
|
|
|
|
if [ "$os" = "linux" ]; then
|
|
tar -xzf "$tmp_dir/$filename" -C "$tmp_dir"
|
|
else
|
|
unzip -q "$tmp_dir/$filename" -d "$tmp_dir"
|
|
fi
|
|
|
|
# kilocode_change start
|
|
mv "$tmp_dir/kilo" "$INSTALL_DIR"
|
|
chmod 755 "${INSTALL_DIR}/kilo"
|
|
# kilocode_change end
|
|
rm -rf "$tmp_dir"
|
|
}
|
|
|
|
install_from_binary() {
|
|
# kilocode_change start
|
|
print_message info "\n${MUTED}Installing ${NC}Kilo Code ${MUTED}from: ${NC}$binary_path"
|
|
cp "$binary_path" "${INSTALL_DIR}/kilo"
|
|
chmod 755 "${INSTALL_DIR}/kilo"
|
|
# kilocode_change end
|
|
}
|
|
|
|
if [ -n "$binary_path" ]; then
|
|
install_from_binary
|
|
else
|
|
check_version
|
|
download_and_install
|
|
fi
|
|
|
|
|
|
add_to_path() {
|
|
local config_file=$1
|
|
local command=$2
|
|
|
|
if grep -Fxq "$command" "$config_file"; then
|
|
print_message info "Command already exists in $config_file, skipping write."
|
|
elif [[ -w $config_file ]]; then
|
|
echo -e "\n# kilo" >> "$config_file" # kilocode_change
|
|
echo "$command" >> "$config_file"
|
|
print_message info "${MUTED}Successfully added ${NC}kilo ${MUTED}to \$PATH in ${NC}$config_file" # kilocode_change
|
|
else
|
|
print_message warning "Manually add the directory to $config_file (or similar):"
|
|
print_message info " $command"
|
|
fi
|
|
}
|
|
|
|
# Persistently add $INSTALL_DIR to the Windows user PATH via the registry.
|
|
# Works from Git Bash (MINGW64), MSYS2, and Cygwin.
|
|
# Converts the MSYS/Cygwin path to native Windows form so that cmd and
|
|
# PowerShell also see the entry.
|
|
add_to_windows_path() {
|
|
local win_install_dir
|
|
if command -v cygpath >/dev/null 2>&1; then
|
|
win_install_dir=$(cygpath -w "$INSTALL_DIR")
|
|
else
|
|
# Fallback: naive conversion /c/Users/... -> C:\Users\...
|
|
win_install_dir=$(echo "$INSTALL_DIR" | sed -e 's|^/\([a-zA-Z]\)/|\1:\\|' -e 's|/|\\|g')
|
|
fi
|
|
|
|
# Escape single-quotes for PowerShell single-quoted strings ('' is the escape sequence)
|
|
local win_install_dir_ps="${win_install_dir//\'/\'\'}"
|
|
|
|
local ps_cmd
|
|
ps_cmd=$(cat <<EOFPS
|
|
\$kiloPath = '$win_install_dir_ps'
|
|
\$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
\$pathParts = if ([string]::IsNullOrEmpty(\$currentPath)) { @() } else { \$currentPath -split ';' }
|
|
if (\$pathParts -notcontains \$kiloPath) {
|
|
\$newPath = if ([string]::IsNullOrEmpty(\$currentPath)) { \$kiloPath } else { "\$currentPath;\$kiloPath" }
|
|
[Environment]::SetEnvironmentVariable('Path', \$newPath, 'User')
|
|
Write-Output 'added'
|
|
}
|
|
EOFPS
|
|
)
|
|
|
|
local ps_output
|
|
if command -v powershell.exe >/dev/null 2>&1; then
|
|
ps_output=$(powershell.exe -NoProfile -NonInteractive -Command "$ps_cmd" 2>/dev/null) || return 1
|
|
elif command -v pwsh >/dev/null 2>&1; then
|
|
ps_output=$(pwsh -NoProfile -NonInteractive -Command "$ps_cmd" 2>/dev/null) || return 1
|
|
else
|
|
return 1
|
|
fi
|
|
|
|
if [[ "$ps_output" == *"added"* ]]; then
|
|
print_message info "${MUTED}Successfully added ${NC}kilo ${MUTED}to Windows user PATH${NC}"
|
|
fi
|
|
|
|
# Also add to the current bash session so `kilo` works immediately
|
|
# without the user needing to open a new terminal.
|
|
if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
|
|
export PATH="$INSTALL_DIR:$PATH"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
|
|
|
|
current_shell=$(basename "$SHELL")
|
|
case $current_shell in
|
|
fish)
|
|
config_files="$HOME/.config/fish/config.fish"
|
|
;;
|
|
zsh)
|
|
config_files="${ZDOTDIR:-$HOME}/.zshrc ${ZDOTDIR:-$HOME}/.zshenv $XDG_CONFIG_HOME/zsh/.zshrc $XDG_CONFIG_HOME/zsh/.zshenv"
|
|
;;
|
|
bash)
|
|
config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
|
|
;;
|
|
ash)
|
|
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
|
|
;;
|
|
sh)
|
|
config_files="$HOME/.ashrc $HOME/.profile /etc/profile"
|
|
;;
|
|
*)
|
|
# Default case if none of the above matches
|
|
config_files="$HOME/.bashrc $HOME/.bash_profile $XDG_CONFIG_HOME/bash/.bashrc $XDG_CONFIG_HOME/bash/.bash_profile"
|
|
;;
|
|
esac
|
|
|
|
if [[ "$no_modify_path" != "true" ]]; then
|
|
# Detect if we are running on Windows (MINGW, MSYS, Cygwin) regardless
|
|
# of whether the binary was installed from download or --binary.
|
|
_running_on_windows=false
|
|
case "$(uname -s)" in
|
|
MINGW*|MSYS*|CYGWIN*) _running_on_windows=true ;;
|
|
esac
|
|
|
|
config_file=""
|
|
for file in $config_files; do
|
|
if [[ -f $file ]]; then
|
|
config_file=$file
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ "$_running_on_windows" == "true" ]] && add_to_windows_path; then
|
|
# Handled via the Windows user-level PATH in the registry.
|
|
# The change is visible in new cmd / PowerShell / Git Bash sessions.
|
|
# We also exported it into the current session above.
|
|
:
|
|
elif [[ -z $config_file ]]; then
|
|
print_message warning "No config file found for $current_shell. You may need to manually add to PATH:"
|
|
print_message info " export PATH=$INSTALL_DIR:\$PATH"
|
|
elif [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then
|
|
case $current_shell in
|
|
fish)
|
|
add_to_path "$config_file" "fish_add_path $INSTALL_DIR"
|
|
;;
|
|
zsh)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
bash)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
ash)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
sh)
|
|
add_to_path "$config_file" "export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
*)
|
|
export PATH=$INSTALL_DIR:$PATH
|
|
print_message warning "Manually add the directory to $config_file (or similar):"
|
|
print_message info " export PATH=$INSTALL_DIR:\$PATH"
|
|
;;
|
|
esac
|
|
fi
|
|
fi
|
|
|
|
if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
|
|
echo "$INSTALL_DIR" >> $GITHUB_PATH
|
|
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
|
|
fi
|
|
|
|
# kilocode_change start
|
|
echo -e ""
|
|
echo -e "██ ▄█▀ ██ ██ ▄████▄ ▄█████ ██ ██ "
|
|
echo -e "████ ██ ██ ██ ██ ██ ██ ██ "
|
|
echo -e "██ ▀█▄ ██ ██████ ▀████▀ ▀█████ ██████ ██ "
|
|
echo -e ""
|
|
echo -e ""
|
|
echo -e "${MUTED}Kilo includes free models so you can start immediately:${NC}"
|
|
echo -e ""
|
|
echo -e "cd <project> ${MUTED}# Open directory${NC}"
|
|
echo -e "kilo ${MUTED}# Run Kilo${NC}"
|
|
echo -e ""
|
|
echo -e "${MUTED}For more information visit ${NC}https://kilo.ai/docs"
|
|
echo -e ""
|
|
echo -e ""
|
|
# kilocode_change end
|