Files
2026-05-29 16:35:10 -07:00

331 lines
8.2 KiB
Bash

#!/bin/sh
set -eu
REPO_GIT_URL="git+https://github.com/Alishahryar1/free-claude-code.git"
PYTHON_VERSION="3.14.0"
MIN_UV_VERSION="0.11.0"
UV_INSTALL_URL="https://astral.sh/uv/install.sh"
dry_run=0
voice_nim=0
voice_local=0
voice_all=0
torch_backend=""
show_usage() {
cat <<'USAGE'
Usage: install.sh [options]
Installs Claude Code if missing, installs or updates uv, Python 3.14.0, and Free Claude Code.
Options:
--voice-nim Install NVIDIA NIM voice transcription support.
--voice-local Install local Whisper voice transcription support.
--voice-all Install all voice transcription backends.
--torch-backend VALUE Use a uv PyTorch backend, such as cu130. Requires local voice.
--dry-run Print commands without running them.
--help Show this help text.
USAGE
}
fail() {
printf 'error: %s\n' "$*" >&2
exit 1
}
step() {
printf '\n==> %s\n' "$1"
}
quote_arg() {
case "$1" in
*[!A-Za-z0-9_./:@%+=,-]*|"")
escaped=$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf '"%s"' "$escaped"
;;
*)
printf '%s' "$1"
;;
esac
}
print_command() {
printf '+'
for arg in "$@"; do
printf ' '
quote_arg "$arg"
done
printf '\n'
}
run() {
print_command "$@"
if [ "$dry_run" -eq 0 ]; then
"$@"
fi
}
run_uv_installer() {
printf '+ curl -LsSf %s | sh\n' "$UV_INSTALL_URL"
if [ "$dry_run" -eq 0 ]; then
command -v curl >/dev/null 2>&1 || fail "curl is required to install uv."
curl -LsSf "$UV_INSTALL_URL" | sh
fi
}
add_path_entry() {
[ -n "$1" ] || return 0
case ":$PATH:" in
*":$1:"*) ;;
*) PATH="$1:$PATH" ;;
esac
}
add_uv_to_path() {
if [ -n "${XDG_BIN_HOME:-}" ]; then
add_path_entry "$XDG_BIN_HOME"
fi
if [ -n "${HOME:-}" ]; then
add_path_entry "$HOME/.local/bin"
add_path_entry "$HOME/.cargo/bin"
fi
export PATH
}
require_command() {
if [ "$dry_run" -eq 0 ] && ! command -v "$1" >/dev/null 2>&1; then
fail "$1 is required. Install it first, then rerun this installer."
fi
}
current_uv_version() {
version=$(uv self version --short 2>/dev/null || true)
if [ -z "$version" ]; then
version=$(uv --version 2>/dev/null | sed 's/^uv //; s/ .*//' || true)
fi
[ -n "$version" ] || return 1
printf '%s\n' "$version"
}
version_ge() {
current=${1%%[-+]*}
minimum=${2%%[-+]*}
old_ifs=$IFS
IFS=.
set -- $current
current_major=${1:-0}
current_minor=${2:-0}
current_patch=${3:-0}
set -- $minimum
minimum_major=${1:-0}
minimum_minor=${2:-0}
minimum_patch=${3:-0}
IFS=$old_ifs
[ "$current_major" -gt "$minimum_major" ] && return 0
[ "$current_major" -lt "$minimum_major" ] && return 1
[ "$current_minor" -gt "$minimum_minor" ] && return 0
[ "$current_minor" -lt "$minimum_minor" ] && return 1
[ "$current_patch" -ge "$minimum_patch" ]
}
uv_version_satisfies_minimum() {
version=$(current_uv_version) || return 1
version_ge "$version" "$MIN_UV_VERSION"
}
validate_uv_version() {
[ "$dry_run" -eq 1 ] && return 0
version=$(current_uv_version) || fail "Unable to determine uv version."
if ! version_ge "$version" "$MIN_UV_VERSION"; then
fail "uv $MIN_UV_VERSION or newer is required; found uv $version. Upgrade uv with its installer or package manager, then rerun this installer."
fi
}
uv_self_update_supported() {
uv self update --dry-run >/dev/null 2>&1
}
uv_installed_by_homebrew() {
command -v brew >/dev/null 2>&1 && brew list --versions uv >/dev/null 2>&1
}
uv_installed_by_pipx() {
command -v pipx >/dev/null 2>&1 && pipx list 2>/dev/null | grep -Eq '(^|[[:space:]])package uv([[:space:]]|$)'
}
uv_installed_in_active_virtualenv() {
[ -n "${VIRTUAL_ENV:-}" ] || return 1
uv_path=$(command -v uv)
case "$uv_path" in
"$VIRTUAL_ENV"/*) return 0 ;;
*) return 1 ;;
esac
}
update_existing_uv() {
if uv_self_update_supported; then
run uv self update
return 0
fi
if uv_installed_by_homebrew; then
run brew upgrade uv
return 0
fi
if uv_installed_by_pipx; then
run pipx upgrade uv
return 0
fi
if uv_installed_in_active_virtualenv; then
run python -m pip install --upgrade uv
return 0
fi
if uv_version_satisfies_minimum; then
printf 'uv is already installed and satisfies >=%s; skipping automatic uv update because the install source was not detected.\n' "$MIN_UV_VERSION"
return 0
fi
version=$(current_uv_version 2>/dev/null || printf 'unknown')
fail "uv $MIN_UV_VERSION or newer is required; found uv $version. The existing uv install source was not detected. Upgrade uv manually with the package manager that installed it, then rerun this installer."
}
install_claude_if_missing() {
if command -v claude >/dev/null 2>&1; then
printf 'Claude Code already found on PATH; skipping install.\n'
return 0
fi
require_command npm
run npm install -g @anthropic-ai/claude-code
}
install_or_update_uv() {
add_uv_to_path
if command -v uv >/dev/null 2>&1; then
update_existing_uv
validate_uv_version
return 0
fi
run_uv_installer
add_uv_to_path
if [ "$dry_run" -eq 0 ] && ! command -v uv >/dev/null 2>&1; then
fail "uv was installed, but it is not available on PATH. Open a new terminal or add uv's bin directory to PATH."
fi
validate_uv_version
}
parse_args() {
while [ "$#" -gt 0 ]; do
case "$1" in
--voice-nim)
voice_nim=1
;;
--voice-local)
voice_local=1
;;
--voice-all)
voice_all=1
;;
--torch-backend)
shift
[ "$#" -gt 0 ] || fail "--torch-backend requires a value."
torch_backend="$1"
[ -n "$torch_backend" ] || fail "--torch-backend requires a non-empty value."
;;
--torch-backend=*)
torch_backend="${1#*=}"
[ -n "$torch_backend" ] || fail "--torch-backend requires a non-empty value."
;;
--dry-run)
dry_run=1
;;
--help|-h)
show_usage
exit 0
;;
*)
show_usage >&2
fail "unknown option: $1"
;;
esac
shift
done
}
validate_args() {
include_local=$voice_local
if [ "$voice_all" -eq 1 ]; then
include_local=1
fi
if [ -n "$torch_backend" ] && [ "$include_local" -ne 1 ]; then
fail "--torch-backend requires --voice-local or --voice-all."
fi
}
package_spec() {
include_nim=$voice_nim
include_local=$voice_local
if [ "$voice_all" -eq 1 ]; then
include_nim=1
include_local=1
fi
if [ -n "$torch_backend" ] && [ "$include_local" -ne 1 ]; then
fail "--torch-backend requires --voice-local or --voice-all."
fi
if [ "$include_nim" -eq 1 ] && [ "$include_local" -eq 1 ]; then
printf 'free-claude-code[voice,voice_local] @ %s' "$REPO_GIT_URL"
elif [ "$include_nim" -eq 1 ]; then
printf 'free-claude-code[voice] @ %s' "$REPO_GIT_URL"
elif [ "$include_local" -eq 1 ]; then
printf 'free-claude-code[voice_local] @ %s' "$REPO_GIT_URL"
else
printf '%s' "$REPO_GIT_URL"
fi
}
install_free_claude_code() {
spec=$(package_spec)
if [ -n "$torch_backend" ]; then
run uv tool install --force --torch-backend "$torch_backend" "$spec"
else
run uv tool install --force "$spec"
fi
}
parse_args "$@"
validate_args
step "Installing Claude Code if missing"
install_claude_if_missing
step "Installing uv if missing, updating if present"
install_or_update_uv
step "Installing Python $PYTHON_VERSION"
run uv python install "$PYTHON_VERSION"
step "Installing or updating Free Claude Code"
install_free_claude_code
printf '\nFree Claude Code is installed. Start the proxy with: fcc-server\n'