tf_code/scripts/install-tfcode.sh
2026-03-24 15:06:34 +11:00

222 lines
6.6 KiB
Bash

#!/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 <<EOF
tfcode Installer - ToothFairyAI's official coding agent
Usage: install.sh [options]
Options:
-h, --help Display this help message
-v, --version <version> 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 ""