diff --git a/website/public/install b/website/public/install new file mode 100644 index 0000000..866d486 --- /dev/null +++ b/website/public/install @@ -0,0 +1,261 @@ +#!/bin/sh + +set -eu + +VERSION="${1:-latest}" +INSTALL_BIN_DIR="${FEYNMAN_INSTALL_BIN_DIR:-$HOME/.local/bin}" +INSTALL_APP_DIR="${FEYNMAN_INSTALL_APP_DIR:-$HOME/.local/share/feynman}" +SKIP_PATH_UPDATE="${FEYNMAN_INSTALL_SKIP_PATH_UPDATE:-0}" +path_action="already" +path_profile="" + +step() { + printf '==> %s\n' "$1" +} + +run_with_spinner() { + label="$1" + shift + + if [ ! -t 2 ]; then + step "$label" + "$@" + return + fi + + "$@" & + pid=$! + frame=0 + + set +e + while kill -0 "$pid" 2>/dev/null; do + case "$frame" in + 0) spinner='|' ;; + 1) spinner='/' ;; + 2) spinner='-' ;; + *) spinner='\\' ;; + esac + printf '\r==> %s %s' "$label" "$spinner" >&2 + frame=$(( (frame + 1) % 4 )) + sleep 0.1 + done + wait "$pid" + status=$? + set -e + + printf '\r\033[2K' >&2 + if [ "$status" -ne 0 ]; then + printf '==> %s failed\n' "$label" >&2 + return "$status" + fi + + step "$label" +} + +normalize_version() { + case "$1" in + "" | latest) + printf 'latest\n' + ;; + v*) + printf '%s\n' "${1#v}" + ;; + *) + printf '%s\n' "$1" + ;; + esac +} + +download_file() { + url="$1" + output="$2" + + if command -v curl >/dev/null 2>&1; then + if [ -t 2 ]; then + curl -fL --progress-bar "$url" -o "$output" + else + curl -fsSL "$url" -o "$output" + fi + return + fi + + if command -v wget >/dev/null 2>&1; then + if [ -t 2 ]; then + wget --show-progress -O "$output" "$url" + else + wget -q -O "$output" "$url" + fi + return + fi + + echo "curl or wget is required to install Feynman." >&2 + exit 1 +} + +download_text() { + url="$1" + + if command -v curl >/dev/null 2>&1; then + curl -fsSL "$url" + return + fi + + if command -v wget >/dev/null 2>&1; then + wget -q -O - "$url" + return + fi + + echo "curl or wget is required to install Feynman." >&2 + exit 1 +} + +add_to_path() { + path_action="already" + path_profile="" + + case ":$PATH:" in + *":$INSTALL_BIN_DIR:"*) + return + ;; + esac + + if [ "$SKIP_PATH_UPDATE" = "1" ]; then + path_action="skipped" + return + fi + + profile="${FEYNMAN_INSTALL_SHELL_PROFILE:-$HOME/.profile}" + if [ -z "${FEYNMAN_INSTALL_SHELL_PROFILE:-}" ]; then + case "${SHELL:-}" in + */zsh) + profile="$HOME/.zshrc" + ;; + */bash) + profile="$HOME/.bashrc" + ;; + esac + fi + + path_profile="$profile" + path_line="export PATH=\"$INSTALL_BIN_DIR:\$PATH\"" + if [ -f "$profile" ] && grep -F "$path_line" "$profile" >/dev/null 2>&1; then + path_action="configured" + return + fi + + { + printf '\n# Added by Feynman installer\n' + printf '%s\n' "$path_line" + } >>"$profile" + path_action="added" +} + +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "$1 is required to install Feynman." >&2 + exit 1 + fi +} + +resolve_version() { + normalized_version="$(normalize_version "$VERSION")" + + if [ "$normalized_version" != "latest" ]; then + printf '%s\n' "$normalized_version" + return + fi + + release_json="$(download_text "https://api.github.com/repos/getcompanion-ai/feynman/releases/latest")" + resolved="$(printf '%s\n' "$release_json" | sed -n 's/.*"tag_name":[[:space:]]*"v\([^"]*\)".*/\1/p' | head -n 1)" + + if [ -z "$resolved" ]; then + echo "Failed to resolve the latest Feynman release version." >&2 + exit 1 + fi + + printf '%s\n' "$resolved" +} + +case "$(uname -s)" in + Darwin) + os="darwin" + ;; + Linux) + os="linux" + ;; + *) + echo "install.sh supports macOS and Linux. Use install.ps1 on Windows." >&2 + exit 1 + ;; +esac + +case "$(uname -m)" in + x86_64 | amd64) + arch="x64" + ;; + arm64 | aarch64) + arch="arm64" + ;; + *) + echo "Unsupported architecture: $(uname -m)" >&2 + exit 1 + ;; +esac + +require_command mktemp +require_command tar + +resolved_version="$(resolve_version)" +asset_target="$os-$arch" +bundle_name="feynman-${resolved_version}-${asset_target}" +archive_name="${bundle_name}.tar.gz" +base_url="${FEYNMAN_INSTALL_BASE_URL:-https://github.com/getcompanion-ai/feynman/releases/download/v${resolved_version}}" +download_url="${base_url}/${archive_name}" + +step "Installing Feynman ${resolved_version} for ${asset_target}" + +tmp_dir="$(mktemp -d)" +cleanup() { + rm -rf "$tmp_dir" +} +trap cleanup EXIT INT TERM + +archive_path="$tmp_dir/$archive_name" +step "Downloading ${archive_name}" +download_file "$download_url" "$archive_path" + +mkdir -p "$INSTALL_APP_DIR" +rm -rf "$INSTALL_APP_DIR/$bundle_name" +run_with_spinner "Extracting ${archive_name}" tar -xzf "$archive_path" -C "$INSTALL_APP_DIR" + +mkdir -p "$INSTALL_BIN_DIR" +step "Linking feynman into $INSTALL_BIN_DIR" +cat >"$INSTALL_BIN_DIR/feynman" < Downloading $archiveName" + Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath + + New-Item -ItemType Directory -Path $installRoot -Force | Out-Null + if (Test-Path $bundleDir) { + Remove-Item -Recurse -Force $bundleDir + } + + Write-Host "==> Extracting $archiveName" + Expand-Archive -LiteralPath $archivePath -DestinationPath $installRoot -Force + + New-Item -ItemType Directory -Path $installBinDir -Force | Out-Null + + $shimPath = Join-Path $installBinDir "feynman.cmd" + Write-Host "==> Linking feynman into $installBinDir" + @" +@echo off +"$bundleDir\feynman.cmd" %* +"@ | Set-Content -Path $shimPath -Encoding ASCII + + $currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User") + if (-not $currentUserPath.Split(';').Contains($installBinDir)) { + $updatedPath = if ([string]::IsNullOrWhiteSpace($currentUserPath)) { + $installBinDir + } else { + "$currentUserPath;$installBinDir" + } + [Environment]::SetEnvironmentVariable("Path", $updatedPath, "User") + Write-Host "Updated user PATH. Open a new shell to run feynman." + } else { + Write-Host "$installBinDir is already on PATH." + } + + Write-Host "Feynman $resolvedVersion installed successfully." +} finally { + if (Test-Path $tmpDir) { + Remove-Item -Recurse -Force $tmpDir + } +}