#!/usr/bin/env bash set -euo pipefail APP=tfcode GITEA_HOST="${TFCODE_GITEA_HOST:-gitea.toothfairyai.com}" GITEA_REPO="${TFCODE_GITEA_REPO:-ToothFairyAI/tfcode}" MUTED='\033[0;2m' RED='\033[0;31m' ORANGE='\033[38;5;214m' GREEN='\033[0;32m' NC='\033[0m' usage() { cat < Install a specific version (e.g., 1.0.0) --no-modify-path Don't modify shell config files Examples: curl -fsSL https://toothfairyai.com/install/tfcode | bash curl -fsSL https://toothfairyai.com/install/tfcode | bash -s -- --version 1.0.0 EOF } requested_version=${VERSION:-} no_modify_path=false 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 ;; --no-modify-path) no_modify_path=true shift ;; *) echo -e "${ORANGE}Warning: Unknown option '$1'${NC}" >&2 shift ;; esac done INSTALL_DIR=$HOME/.tfcode/bin mkdir -p "$INSTALL_DIR" # Detect platform 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 # Check Rosetta on macOS 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 # Check AVX2 on x64 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 fi # Check musl on Linux 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 # Build target target="$os-$arch" if [ "$needs_baseline" = "true" ]; then target="$target-baseline"; fi if [ "$is_musl" = "true" ]; then target="$target-musl"; fi archive_ext=".zip" if [ "$os" = "linux" ]; then archive_ext=".tar.gz"; fi filename="$APP-$target$archive_ext" # Check tools 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 # Get version if [ -z "$requested_version" ]; then # Get latest version from Gitea API api_url="https://$GITEA_HOST/api/v1/repos/$GITEA_REPO/releases/latest" specific_version=$(curl -s "$api_url" | 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 url="https://$GITEA_HOST/$GITEA_REPO/releases/download/v${specific_version}/$filename" else requested_version="${requested_version#v}" url="https://$GITEA_HOST/$GITEA_REPO/releases/download/v${requested_version}/$filename" specific_version=$requested_version fi # Download echo -e "${MUTED}Installing ${NC}tfcode ${MUTED}version: ${NC}$specific_version" tmp_dir="${TMPDIR:-/tmp}/tfcode_install_$$" mkdir -p "$tmp_dir" echo -e "${MUTED}Downloading ${NC}$filename" curl -# -L -o "$tmp_dir/$filename" "$url" # Extract echo -e "${MUTED}Extracting...${NC}" if [ "$os" = "linux" ]; then tar -xzf "$tmp_dir/$filename" -C "$tmp_dir" else unzip -q "$tmp_dir/$filename" -d "$tmp_dir" fi # Install mv "$tmp_dir/tfcode" "$INSTALL_DIR" chmod 755 "${INSTALL_DIR}/tfcode" rm -rf "$tmp_dir" # Add to PATH add_to_path() { local config_file=$1 local command=$2 if grep -Fxq "$command" "$config_file" 2>/dev/null; then return elif [[ -w $config_file ]]; then echo -e "\n# tfcode" >> "$config_file" echo "$command" >> "$config_file" echo -e "${MUTED}Added tfcode to \$PATH in ${NC}$config_file" fi } if [[ "$no_modify_path" != "true" ]]; then current_shell=$(basename "$SHELL") case $current_shell in fish) config_files="$HOME/.config/fish/config.fish" ;; zsh) config_files="$HOME/.zshrc $HOME/.zshenv" ;; bash) config_files="$HOME/.bashrc $HOME/.bash_profile $HOME/.profile" ;; *) config_files="$HOME/.profile" ;; esac if [[ ":$PATH:" != *":$INSTALL_DIR:"* ]]; then for file in $config_files; do if [[ -f $file ]]; then add_to_path "$file" "export PATH=$INSTALL_DIR:\$PATH" break fi done fi fi # Install Python SDK echo "" if command -v python3 >/dev/null 2>&1; then echo -e "${MUTED}Installing ToothFairyAI Python SDK...${NC}" python3 -m pip install --user --break-system-packages toothfairyai pydantic httpx rich 2>/dev/null || \ python3 -m pip install --user toothfairyai pydantic httpx rich 2>/dev/null || \ echo -e "${ORANGE}Could not install Python SDK. Run manually:${NC}" echo -e " ${MUTED}pip install toothfairyai pydantic httpx rich${NC}" fi echo "" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${GREEN}✓ tfcode installed successfully!${NC}" echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" echo -e "${MUTED}Quick Start:${NC}" echo "" echo -e " ${ORANGE}tfcode setup${NC} ${MUTED}Interactive credential setup${NC}" echo -e " ${ORANGE}tfcode validate${NC} ${MUTED}Test your credentials${NC}" echo -e " ${ORANGE}tfcode sync${NC} ${MUTED}Sync tools from workspace${NC}" echo -e " ${ORANGE}tfcode${NC} ${MUTED}Start coding assistant${NC}" echo "" echo -e "${MUTED}Documentation: https://toothfairyai.com/developers/tfcode${NC}" echo ""