| #!/bin/sh |
| set -eu |
|
|
| REPO_ARCHIVE_URL="https://github.com/Alishahryar1/free-claude-code/archive/refs/heads/main.zip" |
| PYTHON_VERSION="3.14.0" |
| MIN_UV_VERSION="0.11.16" |
| CLAUDE_INSTALL_URL="https://claude.ai/install.sh" |
| CODEX_INSTALL_URL="https://chatgpt.com/codex/install.sh" |
| PI_INSTALL_URL="https://pi.dev/install.sh" |
| UV_INSTALL_URL="https://astral.sh/uv/install.sh" |
| FCC_MACOS_BUNDLE_ID="io.github.alishahryar1.free-claude-code" |
| FCC_MACOS_OWNER_FILE=".free-claude-code-owner" |
| |
| FCC_COMMANDS="fcc-desktop fcc-server fcc-claude fcc-codex fcc-pi fcc-init free-claude-code" |
|
|
| dry_run=0 |
| voice_nim=0 |
| voice_local=0 |
| voice_all=0 |
| install_claude=1 |
| install_codex=1 |
| install_pi=1 |
| torch_backend="" |
| temporary_script="" |
| tool_bin="" |
| pi_available=0 |
|
|
| show_usage() { |
| cat <<'USAGE' |
| Usage: install.sh [options] |
|
|
| Installs or updates Free Claude Code and lets you choose which coding agents to install or verify. |
|
|
| 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 |
| } |
|
|
| installer_is_interactive() { |
| [ -t 1 ] && ( : </dev/tty ) 2>/dev/null |
| } |
|
|
| prompt_yes_no() { |
| question=$1 |
| while :; do |
| printf '%s [Y/n] ' "$question" >&4 |
| if ! IFS= read -r answer <&3; then |
| fail "Could not read the coding agent selection." |
| fi |
| case "$answer" in |
| ""|[Yy]|[Yy][Ee][Ss]) return 0 ;; |
| [Nn]|[Nn][Oo]) return 1 ;; |
| *) printf 'Please answer Y or N.\n' >&4 ;; |
| esac |
| done |
| } |
|
|
| choose_coding_agents() { |
| selection_input=$1 |
| selection_output=$2 |
| exec 3<"$selection_input" |
| exec 4>"$selection_output" |
|
|
| while :; do |
| if prompt_yes_no "Install or verify Claude Code for fcc-claude?"; then |
| install_claude=1 |
| else |
| install_claude=0 |
| fi |
| if prompt_yes_no "Install or verify Codex for fcc-codex?"; then |
| install_codex=1 |
| else |
| install_codex=0 |
| fi |
| if prompt_yes_no "Install or verify Pi for fcc-pi?"; then |
| install_pi=1 |
| else |
| install_pi=0 |
| fi |
|
|
| if [ "$install_claude" -eq 1 ] || [ "$install_codex" -eq 1 ] || [ "$install_pi" -eq 1 ]; then |
| break |
| fi |
| printf 'Select at least one coding agent.\n\n' >&4 |
| done |
|
|
| exec 3<&- |
| exec 4>&- |
| } |
|
|
| 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 1 ]; then |
| return 0 |
| fi |
|
|
| if "$@"; then |
| return 0 |
| else |
| status=$? |
| fi |
|
|
| fail "Command failed with exit code $status: $1" |
| } |
|
|
| cleanup() { |
| if [ -n "$temporary_script" ] && [ -e "$temporary_script" ]; then |
| rm -f "$temporary_script" |
| fi |
| } |
|
|
| trap cleanup EXIT |
| trap 'exit 130' INT |
| trap 'exit 143' HUP TERM |
|
|
| add_path_entry() { |
| [ -n "$1" ] || return 0 |
| case ":$PATH:" in |
| *":$1:"*) ;; |
| *) PATH="$1:$PATH" ;; |
| esac |
| } |
|
|
| add_known_bin_directories() { |
| 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" |
| add_path_entry "${XDG_DATA_HOME:-$HOME/.local/share}/pi-node/current/bin" |
| fi |
|
|
| export PATH |
| hash -r 2>/dev/null || true |
| } |
|
|
| add_pi_bin_directories() { |
| [ "$dry_run" -eq 0 ] || return 0 |
| add_known_bin_directories |
| if command -v npm >/dev/null 2>&1; then |
| pi_npm_prefix=$(npm prefix -g 2>/dev/null || npm config get prefix 2>/dev/null || true) |
| if [ -n "$pi_npm_prefix" ]; then |
| add_path_entry "$pi_npm_prefix/bin" |
| export PATH |
| hash -r 2>/dev/null || true |
| fi |
| fi |
| } |
|
|
| fcc_process_ids() { |
| command_name=$1 |
|
|
| if command -v pgrep >/dev/null 2>&1; then |
| { |
| pgrep -x "$command_name" 2>/dev/null || true |
| pgrep -f "(^|/)${command_name}([[:space:]]|$)" 2>/dev/null || true |
| } | sort -nu |
| return 0 |
| fi |
|
|
| ps -A -o pid= -o args= 2>/dev/null | |
| awk -v command_name="$command_name" ' |
| BEGIN { |
| pattern = "(^|/)" command_name "([[:space:]]|$)" |
| } |
| { |
| process_id = $1 |
| sub(/^[[:space:]]*[0-9]+[[:space:]]+/, "") |
| if ($0 ~ pattern) { |
| print process_id |
| } |
| } |
| ' || true |
| } |
|
|
| assert_no_fcc_processes_running() { |
| running="" |
| for command_name in $FCC_COMMANDS; do |
| process_ids=$(fcc_process_ids "$command_name") |
| [ -n "$process_ids" ] || continue |
|
|
| for process_id in $process_ids; do |
| process="$command_name (PID $process_id)" |
| if [ -n "$running" ]; then |
| running="$running, $process" |
| else |
| running=$process |
| fi |
| done |
| done |
|
|
| if [ -n "$running" ]; then |
| fail "Free Claude Code is still running ($running). Stop those processes, then rerun the installer." |
| fi |
| } |
|
|
| 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 |
| } |
|
|
| download_and_run() { |
| url=$1 |
| interpreter=$2 |
| label=$3 |
| non_interactive=${4:-0} |
|
|
| if [ "$dry_run" -eq 1 ]; then |
| print_command curl -fsSL "$url" -o "<temporary-script>" |
| if [ "$non_interactive" -eq 1 ]; then |
| printf '+ CODEX_NON_INTERACTIVE=1 ' |
| quote_arg "$interpreter" |
| printf ' <temporary-script>\n' |
| else |
| print_command "$interpreter" "<temporary-script>" |
| fi |
| return 0 |
| fi |
|
|
| temporary_script=$(mktemp "${TMPDIR:-/tmp}/fcc-install.XXXXXX") || fail "Unable to create a temporary file for $label." |
| print_command curl -fsSL "$url" -o "$temporary_script" |
| if curl -fsSL "$url" -o "$temporary_script"; then |
| : |
| else |
| status=$? |
| fail "Could not download the $label installer (curl exit code $status)." |
| fi |
|
|
| if [ ! -s "$temporary_script" ]; then |
| fail "The downloaded $label installer was empty." |
| fi |
|
|
| if [ "$non_interactive" -eq 1 ]; then |
| printf '+ CODEX_NON_INTERACTIVE=1 ' |
| quote_arg "$interpreter" |
| printf ' ' |
| quote_arg "$temporary_script" |
| printf '\n' |
| if CODEX_NON_INTERACTIVE=1 "$interpreter" "$temporary_script"; then |
| : |
| else |
| status=$? |
| fail "$label installation failed with exit code $status." |
| fi |
| else |
| print_command "$interpreter" "$temporary_script" |
| if "$interpreter" "$temporary_script"; then |
| : |
| else |
| status=$? |
| fail "$label installation failed with exit code $status." |
| fi |
| fi |
|
|
| rm -f "$temporary_script" |
| temporary_script="" |
| } |
|
|
| verify_command() { |
| command_name=$1 |
| display_name=$2 |
|
|
| if [ "$dry_run" -eq 1 ]; then |
| print_command "$command_name" --version |
| return 0 |
| fi |
|
|
| command_path=$(command -v "$command_name" 2>/dev/null) || fail "$display_name was installed, but '$command_name' is not available on PATH." |
| run "$command_path" --version |
| } |
|
|
| pi_command_is_compatible() { |
| pi_command_path=$(command -v pi 2>/dev/null) || return 1 |
| pi_help=$("$pi_command_path" --help 2>/dev/null) || return 1 |
| case "$pi_help" in |
| *--extension*) ;; |
| *) return 1 ;; |
| esac |
| case "$pi_help" in |
| *--models*) return 0 ;; |
| *) return 1 ;; |
| esac |
| } |
|
|
| verify_pi_command() { |
| if [ "$dry_run" -eq 1 ]; then |
| printf '+ pi --help (verify --extension and --models support)\n' |
| print_command pi --version |
| return 0 |
| fi |
|
|
| pi_command_path=$(command -v pi 2>/dev/null) || fail "Pi was installed, but 'pi' is not available on PATH." |
| pi_command_is_compatible || fail "The 'pi' command at $pi_command_path is not a compatible Pi Coding Agent." |
| run "$pi_command_path" --version |
| } |
|
|
| ensure_claude() { |
| if command -v claude >/dev/null 2>&1; then |
| printf 'Claude Code already found on PATH; verifying it.\n' |
| else |
| download_and_run "$CLAUDE_INSTALL_URL" bash "Claude Code" |
| add_known_bin_directories |
| fi |
|
|
| verify_command claude "Claude Code" |
| } |
|
|
| ensure_codex() { |
| if command -v codex >/dev/null 2>&1; then |
| printf 'Codex already found on PATH; verifying it.\n' |
| else |
| download_and_run "$CODEX_INSTALL_URL" sh "Codex" 1 |
| add_known_bin_directories |
| fi |
|
|
| verify_command codex "Codex" |
| } |
|
|
| ensure_pi() { |
| pi_available=0 |
| add_pi_bin_directories |
| existing_pi_path=$(command -v pi 2>/dev/null || true) |
|
|
| if [ "$dry_run" -eq 1 ] && command -v pi >/dev/null 2>&1; then |
| printf 'Pi already found on PATH; verifying it.\n' |
| elif pi_command_is_compatible; then |
| printf 'Pi already found on PATH; verifying it.\n' |
| else |
| if [ -n "$existing_pi_path" ]; then |
| printf "The existing 'pi' command at %s is not Pi Coding Agent; installing Pi.\n" "$existing_pi_path" |
| fi |
| download_and_run "$PI_INSTALL_URL" sh "Pi" |
| add_pi_bin_directories |
|
|
| if [ "$dry_run" -eq 0 ]; then |
| current_pi_path=$(command -v pi 2>/dev/null || true) |
| if [ -z "$current_pi_path" ] || |
| { [ -n "$existing_pi_path" ] && |
| [ "$current_pi_path" = "$existing_pi_path" ] && |
| ! pi_command_is_compatible; }; then |
| printf 'Pi was not installed; continuing without it.\n' |
| return 0 |
| fi |
| fi |
| fi |
|
|
| verify_pi_command |
| pi_available=1 |
| } |
|
|
| ensure_selected_coding_agents() { |
| if [ "$install_claude" -eq 1 ]; then |
| step "Ensuring Claude Code is installed" |
| ensure_claude |
| fi |
|
|
| if [ "$install_codex" -eq 1 ]; then |
| step "Ensuring Codex is installed" |
| ensure_codex |
| fi |
|
|
| if [ "$install_pi" -eq 1 ]; then |
| step "Checking or installing Pi" |
| ensure_pi |
| fi |
|
|
| if [ "$install_claude" -eq 0 ] && [ "$install_codex" -eq 0 ] && [ "$pi_available" -eq 0 ]; then |
| fail "No selected coding agent was installed. Re-run the installer and choose at least one." |
| fi |
| } |
|
|
| current_uv_version() { |
| if output=$(uv --version); then |
| : |
| else |
| return 1 |
| fi |
|
|
| case "$output" in |
| uv\ *) version=${output#uv } ;; |
| *) version=$output ;; |
| esac |
| version=${version%% *} |
|
|
| case "$version" in |
| [0-9]*.[0-9]*.[0-9]*) printf '%s\n' "$version" ;; |
| *) return 1 ;; |
| esac |
| } |
|
|
| uv_version_is_supported() { |
| case "$1" in |
| *-*) return 1 ;; |
| esac |
|
|
| 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 |
|
|
| case "$current_major$current_minor$current_patch$minimum_major$minimum_minor$minimum_patch" in |
| *[!0-9]*) return 1 ;; |
| esac |
|
|
| [ "$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" ] |
| } |
|
|
| verify_uv() { |
| if [ "$dry_run" -eq 1 ]; then |
| print_command uv --version |
| return 0 |
| fi |
|
|
| command -v uv >/dev/null 2>&1 || fail "uv was installed, but it is not available on PATH." |
| version=$(current_uv_version) || fail "uv is present, but 'uv --version' did not return a valid version." |
| if ! uv_version_is_supported "$version" "$MIN_UV_VERSION"; then |
| fail "Stable uv $MIN_UV_VERSION or newer is required; found uv $version after installation." |
| fi |
|
|
| printf 'Verified uv %s.\n' "$version" |
| } |
|
|
| ensure_uv() { |
| if [ "$dry_run" -eq 1 ]; then |
| if command -v uv >/dev/null 2>&1; then |
| print_command uv --version |
| printf 'A compatible existing uv will be left unchanged; an obsolete one will be replaced by the standalone installer.\n' |
| else |
| printf 'uv is not installed; the current standalone uv would be installed.\n' |
| download_and_run "$UV_INSTALL_URL" sh "uv" |
| verify_uv |
| fi |
| return 0 |
| fi |
|
|
| if command -v uv >/dev/null 2>&1; then |
| version=$(current_uv_version) || fail "uv is present, but 'uv --version' did not return a valid version." |
| if uv_version_is_supported "$version" "$MIN_UV_VERSION"; then |
| printf 'uv %s already satisfies >=%s; leaving it unchanged.\n' "$version" "$MIN_UV_VERSION" |
| return 0 |
| fi |
| printf 'uv %s does not satisfy stable >=%s; installing the current standalone uv.\n' "$version" "$MIN_UV_VERSION" |
| else |
| printf 'uv is not installed; installing the current standalone uv.\n' |
| fi |
|
|
| download_and_run "$UV_INSTALL_URL" sh "uv" |
| add_known_bin_directories |
| verify_uv |
| } |
|
|
| 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 [ "$include_nim" -eq 1 ] && [ "$include_local" -eq 1 ]; then |
| printf 'free-claude-code[voice,voice_local] @ %s' "$REPO_ARCHIVE_URL" |
| elif [ "$include_nim" -eq 1 ]; then |
| printf 'free-claude-code[voice] @ %s' "$REPO_ARCHIVE_URL" |
| elif [ "$include_local" -eq 1 ]; then |
| printf 'free-claude-code[voice_local] @ %s' "$REPO_ARCHIVE_URL" |
| else |
| printf 'free-claude-code @ %s' "$REPO_ARCHIVE_URL" |
| fi |
| } |
|
|
| install_free_claude_code() { |
| assert_no_fcc_processes_running |
| spec=$(package_spec) |
|
|
| if [ -n "$torch_backend" ]; then |
| run uv tool install --force --refresh-package free-claude-code --python "$PYTHON_VERSION" --torch-backend "$torch_backend" "$spec" |
| else |
| run uv tool install --force --refresh-package free-claude-code --python "$PYTHON_VERSION" "$spec" |
| fi |
| } |
|
|
| configure_and_verify_free_claude_code() { |
| run uv tool update-shell |
|
|
| if [ "$dry_run" -eq 1 ]; then |
| print_command uv tool dir --bin |
| printf '+ verify fcc-desktop, fcc-server, fcc-claude, fcc-codex, and fcc-pi in the uv tool bin directory\n' |
| print_command fcc-server --version |
| return 0 |
| fi |
|
|
| print_command uv tool dir --bin |
| if tool_bin=$(uv tool dir --bin); then |
| : |
| else |
| status=$? |
| fail "Could not determine the uv tool bin directory (exit code $status)." |
| fi |
| [ -n "$tool_bin" ] || fail "uv returned an empty tool bin directory." |
|
|
| add_path_entry "$tool_bin" |
| export PATH |
| hash -r 2>/dev/null || true |
|
|
| for command_name in fcc-desktop fcc-server fcc-claude fcc-codex fcc-pi; do |
| [ -x "$tool_bin/$command_name" ] || fail "Free Claude Code installation did not create $tool_bin/$command_name." |
| done |
|
|
| run "$tool_bin/fcc-server" --version |
| } |
|
|
| shell_quote() { |
| escaped=$(printf '%s' "$1" | sed "s/'/'\\\\''/g") |
| printf "'%s'" "$escaped" |
| } |
|
|
| macos_app_is_fcc_owned() { |
| app_dir=$1 |
| owner_file="$app_dir/Contents/$FCC_MACOS_OWNER_FILE" |
| [ -d "$app_dir" ] && |
| [ ! -L "$app_dir" ] && |
| [ -f "$owner_file" ] && |
| [ "$(cat "$owner_file")" = "$FCC_MACOS_BUNDLE_ID" ] |
| } |
|
|
| install_macos_desktop_app() { |
| [ "$(uname -s)" = "Darwin" ] || return 0 |
|
|
| app_dir="$HOME/Applications/Free Claude Code.app" |
| contents_dir="$app_dir/Contents" |
| owner_file="$contents_dir/$FCC_MACOS_OWNER_FILE" |
| executable_dir="$contents_dir/MacOS" |
| executable_path="$executable_dir/fcc-desktop" |
| resources_dir="$contents_dir/Resources" |
| icon_path="$resources_dir/AppIcon.icns" |
| desktop_dir="$HOME/Desktop" |
| desktop_link="$desktop_dir/Free Claude Code.app" |
|
|
| if [ -e "$app_dir" ] || [ -L "$app_dir" ]; then |
| macos_app_is_fcc_owned "$app_dir" || |
| fail "An app not managed by Free Claude Code already exists at $app_dir. Move it, then rerun the installer." |
| fi |
|
|
| if [ "$dry_run" -eq 1 ]; then |
| print_command mkdir -p "$executable_dir" "$resources_dir" "$desktop_dir" |
| print_command fcc-desktop --export-icon "$icon_path" |
| printf '+ write %s, %s, and %s\n' "$owner_file" "$contents_dir/Info.plist" "$executable_path" |
| print_command ln -s "$app_dir" "$desktop_link" |
| return 0 |
| fi |
|
|
| mkdir -p "$executable_dir" "$resources_dir" "$desktop_dir" |
| run "$tool_bin/fcc-desktop" --export-icon "$icon_path" |
| [ -f "$icon_path" ] || fail "Free Claude Code did not export its macOS app icon to $icon_path." |
| printf '%s\n' "$FCC_MACOS_BUNDLE_ID" > "$owner_file" |
| cat > "$contents_dir/Info.plist" <<'PLIST' |
| <?xml version="1.0" encoding="UTF-8"?> |
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
| <plist version="1.0"> |
| <dict> |
| <key>CFBundleDisplayName</key> |
| <string>Free Claude Code</string> |
| <key>CFBundleExecutable</key> |
| <string>fcc-desktop</string> |
| <key>CFBundleIdentifier</key> |
| <string>io.github.alishahryar1.free-claude-code</string> |
| <key>CFBundleIconFile</key> |
| <string>AppIcon</string> |
| <key>CFBundleName</key> |
| <string>Free Claude Code</string> |
| <key>CFBundlePackageType</key> |
| <string>APPL</string> |
| <key>LSMultipleInstancesProhibited</key> |
| <true/> |
| <key>LSUIElement</key> |
| <true/> |
| </dict> |
| </plist> |
| PLIST |
| desktop_command=$(shell_quote "$tool_bin/fcc-desktop") |
| { |
| printf '%s\n' '#!/bin/sh' |
| printf 'exec %s\n' "$desktop_command" |
| } > "$executable_path" |
| chmod +x "$executable_path" |
|
|
| if [ -L "$desktop_link" ]; then |
| if [ "$(readlink "$desktop_link")" = "$app_dir" ]; then |
| rm -f "$desktop_link" |
| else |
| printf 'A non-FCC link already exists at %s; leaving it unchanged.\n' "$desktop_link" |
| return 0 |
| fi |
| elif [ -e "$desktop_link" ]; then |
| printf 'A non-FCC item already exists at %s; leaving it unchanged.\n' "$desktop_link" |
| return 0 |
| fi |
| ln -s "$app_dir" "$desktop_link" |
| } |
|
|
| parse_args "$@" |
| validate_args |
| add_known_bin_directories |
|
|
| step "Checking for running Free Claude Code processes" |
| assert_no_fcc_processes_running |
|
|
| if installer_is_interactive; then |
| step "Choosing coding agents" |
| choose_coding_agents /dev/tty /dev/tty |
| fi |
|
|
| step "Checking installation prerequisites" |
| require_command curl |
| if [ "$install_claude" -eq 1 ]; then |
| require_command bash |
| fi |
| require_command sh |
| require_command mktemp |
|
|
| ensure_selected_coding_agents |
|
|
| step "Ensuring uv $MIN_UV_VERSION or newer is installed" |
| ensure_uv |
|
|
| step "Installing or updating Free Claude Code" |
| install_free_claude_code |
|
|
| step "Configuring PATH and verifying Free Claude Code" |
| configure_and_verify_free_claude_code |
|
|
| if [ "$(uname -s)" = "Darwin" ]; then |
| step "Installing the Free Claude Code desktop launcher" |
| install_macos_desktop_app |
| fi |
|
|
| if [ "$dry_run" -eq 1 ]; then |
| printf '\nDry run complete. No changes were made.\n' |
| else |
| if [ "$(uname -s)" = "Darwin" ]; then |
| printf '\nFree Claude Code is installed and verified. Open Free Claude Code from Applications or the desktop to run it in the background.\n' |
| printf 'For terminal use, start the proxy with: fcc-server\n' |
| else |
| printf '\nFree Claude Code is installed and verified. Start the proxy with: fcc-server\n' |
| fi |
| if [ "$install_claude" -eq 1 ]; then |
| printf 'Run Claude Code with: fcc-claude\n' |
| fi |
| if [ "$install_codex" -eq 1 ]; then |
| printf 'Run Codex with: fcc-codex\n' |
| fi |
| if [ "$pi_available" -eq 1 ]; then |
| printf 'Run Pi with: fcc-pi\n' |
| fi |
| fi |
|
|