#!/usr/bin/env bash set -euo pipefail APP=flycode COMMAND=flycode LEGACY_COMMAND=opencode INSTALL_URL="${FLYCODE_INSTALL_URL:-https://flycode100.com/install}" RELEASES_URL="${FLYCODE_RELEASES_URL:-https://flycode100.com/releases}" LATEST_URL="${FLYCODE_LATEST_URL:-$RELEASES_URL/latest.json}" MUTED='\033[0;2m' RED='\033[0;31m' ORANGE='\033[38;5;214m' NC='\033[0m' usage() { cat < Install a specific version (e.g. 1.2.10) -b, --binary Install from a local binary instead of downloading --no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.) Examples: curl -fsSL ${INSTALL_URL} | bash curl -fsSL ${INSTALL_URL} | bash -s -- --version 1.2.10 ./install --binary /path/to/flycode EOF } requested_version="${VERSION:-}" no_modify_path=false binary_path="" specific_version="" url="" filename="" os="" arch="" archive_ext="" 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_ROOT="${FLYCODE_HOME:-$HOME/.flycode}" INSTALL_DIR="${FLYCODE_INSTALL_DIR:-${OPENCODE_INSTALL_DIR:-${XDG_BIN_DIR:-$INSTALL_ROOT/bin}}}" LEGACY_INSTALL_DIR="${OPENCODE_INSTALL_DIR:-${OPENCODE_HOME:-$HOME/.opencode}/bin}" mkdir -p "$INSTALL_DIR" print_message() { local level="$1" local message="$2" local color="$NC" if [[ "$level" == "error" ]]; then color="$RED" fi echo -e "${color}${message}${NC}" } json_get_string() { local key="$1" local json="$2" printf "%s" "$json" | tr -d '\n' | sed -n "s/.*\"$key\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n 1 } json_get_asset() { local key="$1" local json="$2" printf "%s" "$json" | tr -d '\n' | sed -n "s/.*\"assets\"[[:space:]]*:[[:space:]]*{.*\"$key\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n 1 } command_filename() { local name="$1" if [[ "$os" == "windows" ]]; then printf "%s.exe\n" "$name" return fi printf "%s\n" "$name" } detect_platform() { local raw_os raw_os=$(uname -s) case "$raw_os" in Darwin*) os="darwin" ;; Linux*) os="linux" ;; MINGW*|MSYS*|CYGWIN*) os="windows" ;; *) echo -e "${RED}Unsupported OS: ${raw_os}${NC}" exit 1 ;; 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 local rosetta_flag rosetta_flag=$(sysctl -n sysctl.proc_translated 2>/dev/null || echo 0) if [[ "$rosetta_flag" == "1" ]]; then arch="arm64" fi fi case "$os-$arch" in linux-x64|linux-arm64|darwin-x64|darwin-arm64|windows-x64) ;; *) echo -e "${RED}Unsupported OS/Arch: ${os}/${arch}${NC}" exit 1 ;; esac } detect_archive_ext() { if [[ "$os" == "linux" ]]; then archive_ext=".tar.gz" return fi archive_ext=".zip" } detect_musl() { if [[ "$os" != "linux" ]]; then return 1 fi if [[ -f /etc/alpine-release ]]; then return 0 fi if command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then return 0 fi return 1 } detect_baseline() { if [[ "$arch" != "x64" ]]; then return 1 fi if [[ "$os" == "linux" ]]; then if grep -qwi avx2 /proc/cpuinfo 2>/dev/null; then return 1 fi return 0 fi if [[ "$os" == "darwin" ]]; then local avx2 avx2=$(sysctl -n hw.optional.avx2_0 2>/dev/null || echo 0) if [[ "$avx2" == "1" ]]; then return 1 fi return 0 fi if [[ "$os" == "windows" ]]; then local ps ps='(Add-Type -MemberDefinition "[DllImport(""kernel32.dll"")] public static extern bool IsProcessorFeaturePresent(int ProcessorFeature);" -Name Kernel32 -Namespace Win32 -PassThru)::IsProcessorFeaturePresent(40)' local 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 return 1 fi return 0 fi return 1 } resolve_target() { local target="${os}-${arch}" if detect_baseline; then target="${target}-baseline" fi if detect_musl; then target="${target}-musl" fi printf "%s\n" "$target" } require_tools() { if ! command -v curl >/dev/null 2>&1; then echo -e "${RED}Error: 'curl' is required but not installed.${NC}" exit 1 fi 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 return fi if ! command -v unzip >/dev/null 2>&1; then echo -e "${RED}Error: 'unzip' is required but not installed.${NC}" exit 1 fi } prepare_download() { detect_platform detect_archive_ext require_tools local target target=$(resolve_target) local fallback="${APP}-${target}${archive_ext}" if [[ -z "$requested_version" ]]; then local metadata metadata=$(curl -fsSL "$LATEST_URL") specific_version=$(json_get_string "version" "$metadata") local base_url base_url=$(json_get_string "base_url" "$metadata") local asset asset=$(json_get_asset "$target" "$metadata") if [[ -z "$specific_version" ]]; then echo -e "${RED}Failed to parse version metadata from ${LATEST_URL}${NC}" exit 1 fi if [[ -z "$asset" ]]; then asset="$fallback" fi if [[ -z "$base_url" ]]; then base_url="${RELEASES_URL}/v${specific_version}" fi filename="$asset" url="${base_url}/${asset}" return fi specific_version="${requested_version#v}" filename="$fallback" url="${RELEASES_URL}/v${specific_version}/${filename}" } check_version() { local installed="" if command -v "$COMMAND" >/dev/null 2>&1; then installed=$("$COMMAND" --version 2>/dev/null || true) elif command -v "$LEGACY_COMMAND" >/dev/null 2>&1; then installed=$("$LEGACY_COMMAND" --version 2>/dev/null || true) fi if [[ -z "$installed" ]]; then return fi if [[ "$installed" == "$specific_version" ]]; then print_message info "${MUTED}Version ${NC}${specific_version}${MUTED} already installed${NC}" exit 0 fi print_message info "${MUTED}Installed version:${NC} ${installed}" } download_archive() { local archive_url="$1" local output="$2" curl -fL --progress-bar -o "$output" "$archive_url" } extract_archive() { local archive="$1" local dir="$2" if [[ "$os" == "linux" ]]; then tar -xzf "$archive" -C "$dir" return fi unzip -q "$archive" -d "$dir" } find_extracted_binary() { local dir="$1" local candidate="" for candidate in \ "$(command_filename "$COMMAND")" \ "$COMMAND" \ "$(command_filename "$LEGACY_COMMAND")" \ "$LEGACY_COMMAND" do if [[ -f "$dir/$candidate" ]]; then printf "%s\n" "$dir/$candidate" return 0 fi done return 1 } create_alias() { local source="$1" local dir="$2" local name="$3" local target="$dir/$(command_filename "$name")" if [[ "$target" == "$source" ]]; then return fi mkdir -p "$dir" rm -f "$target" if [[ "$os" == "windows" ]]; then cp "$source" "$target" return fi ln -sf "$source" "$target" } install_binary() { local source="$1" local destination="$INSTALL_DIR/$(command_filename "$COMMAND")" cp "$source" "$destination" chmod 755 "$destination" create_alias "$destination" "$INSTALL_DIR" "$LEGACY_COMMAND" create_alias "$destination" "$LEGACY_INSTALL_DIR" "$LEGACY_COMMAND" } download_and_install() { print_message info "\n${MUTED}Installing ${NC}${APP}${MUTED} version:${NC} ${specific_version}" local tmp_dir tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/${APP}_install_XXXXXX") trap "rm -rf \"$tmp_dir\"" EXIT download_archive "$url" "$tmp_dir/$filename" extract_archive "$tmp_dir/$filename" "$tmp_dir" local extracted extracted=$(find_extracted_binary "$tmp_dir") || { echo -e "${RED}Failed to locate extracted binary in ${filename}${NC}" exit 1 } install_binary "$extracted" trap - EXIT } install_from_binary() { if [[ ! -f "$binary_path" ]]; then echo -e "${RED}Error: Binary not found at ${binary_path}${NC}" exit 1 fi detect_platform print_message info "\n${MUTED}Installing ${NC}${APP}${MUTED} from:${NC} ${binary_path}" install_binary "$binary_path" specific_version="local" } add_to_path() { local config_file="$1" local command="$2" if grep -Fxq "$command" "$config_file"; then print_message info "${MUTED}PATH entry already exists in ${NC}${config_file}" return fi if [[ -w "$config_file" ]]; then echo -e "\n# flycode" >> "$config_file" echo "$command" >> "$config_file" print_message info "${MUTED}Added ${NC}${INSTALL_DIR}${MUTED} to \$PATH in ${NC}${config_file}" return fi print_message warning "Manually add the directory to ${config_file} (or similar):" print_message info " ${command}" } update_path() { local xdg_config_home="${XDG_CONFIG_HOME:-$HOME/.config}" local current_shell current_shell=$(basename "${SHELL:-bash}") local config_files="" 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|sh) config_files="$HOME/.ashrc $HOME/.profile /etc/profile" ;; *) config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile $xdg_config_home/bash/.bashrc $xdg_config_home/bash/.bash_profile" ;; esac local config_file="" local file="" for file in $config_files; do if [[ -f "$file" ]]; then config_file="$file" break fi done if [[ -z "$config_file" ]]; then print_message warning "No shell config file found for ${current_shell}. Add this manually:" print_message info " export PATH=\"${INSTALL_DIR}:\$PATH\"" return fi if [[ ":$PATH:" == *":$INSTALL_DIR:"* ]]; then return fi case "$current_shell" in fish) add_to_path "$config_file" "fish_add_path $INSTALL_DIR" ;; *) add_to_path "$config_file" "export PATH=\"$INSTALL_DIR:\$PATH\"" ;; esac } if [[ -n "$binary_path" ]]; then install_from_binary else prepare_download check_version download_and_install fi if [[ "$no_modify_path" != "true" ]]; then update_path fi if [[ -n "${GITHUB_ACTIONS:-}" && "${GITHUB_ACTIONS}" == "true" ]]; then echo "$INSTALL_DIR" >> "$GITHUB_PATH" print_message info "Added ${INSTALL_DIR} to \$GITHUB_PATH" fi echo print_message info "CLI installed to ${INSTALL_DIR}/$(command_filename "$COMMAND")" print_message info "${MUTED}Restart your terminal and run:${NC}" print_message info " ${COMMAND}" echo