#!/usr/bin/env bash
# TogetherLink private beta channel.
export TOGETHERLINK_ORIGIN="https://gateway.togetherlink.dev"
export TOGETHERLINK_MANIFEST_URL="https://gateway.togetherlink.dev/latest.json"
export TOGETHERLINK_CHANNEL="beta"
export TOGETHERLINK_GATEWAY_URL="https://gateway.togetherlink.dev"
BETA_INSTALL_DIR="${TOGETHERLINK_HOME:-$HOME/.togetherlink}"
BETA_BACKUP_DIR="$BETA_INSTALL_DIR/backups"
if [ -f "$BETA_INSTALL_DIR/bin/togetherlink.js" ] && [ ! -f "$BETA_BACKUP_DIR/pre-beta-togetherlink.js" ]; then
  mkdir -p "$BETA_BACKUP_DIR"
  cp "$BETA_INSTALL_DIR/bin/togetherlink.js" "$BETA_BACKUP_DIR/pre-beta-togetherlink.js"
fi
# togetherlink installer.
#
#   curl -fsSL "$TOGETHERLINK_RAILWAY_ORIGIN/install.sh" | bash
#
# Installs the togetherlink CLI as a Bun-target JS bundle at
# ~/.togetherlink/bin/togetherlink.js, with a `togetherlink` wrapper script on
# PATH that runs it with `bun`. Installs Bun for the user if `bun` isn't on
# PATH. Also installs the `tlink`, `tcodex`, `tclaude`, and `tclaude-desktop` convenience wrappers.
#
# When a terminal is attached, the installer finishes by starting the installed
# CLI, so the first run needs no PATH setup in the invoking shell. Set
# TOGETHERLINK_NO_LAUNCH=1 to skip that. The CLI prompts once for a Together API
# key on first use and then keeps itself updated from the same release channel.

set -euo pipefail

ORIGIN="${TOGETHERLINK_ORIGIN:?Install TogetherLink from the Railway release endpoint}"
INSTALL_DIR="${TOGETHERLINK_HOME:-$HOME/.togetherlink}"
BIN_DIR="$INSTALL_DIR/bin"
PUBLIC_BIN_DIR="$HOME/.local/bin"
UPDATE_CHANNEL_FILE="$INSTALL_DIR/.update-manifest-url"
GATEWAY_URL_FILE="$INSTALL_DIR/.gateway-url"

white="" green="" gray="" orange="" red="" stdout_reset="" stderr_reset=""
if [ -t 1 ] && [ "${TERM:-}" != "dumb" ] && [ -z "${NO_COLOR:-}" ]; then
  white=$'\033[1;97m'
  green=$'\033[32m'
  gray=$'\033[90m'
  orange=$'\033[38;5;208m'
  stdout_reset=$'\033[0m'
fi
if [ -t 2 ] && [ "${TERM:-}" != "dumb" ] && [ -z "${NO_COLOR:-}" ]; then
  red=$'\033[31m'
  stderr_reset=$'\033[0m'
fi

heading() { printf "%b%s%b\n" "$white" "$1" "$stdout_reset"; }
info() { printf "%s\n" "$1"; }
success() { printf "%b✔ %s%b\n" "$green" "$1" "$stdout_reset"; }
metadata() { printf "%b%s:%b %b%s%b\n" "$gray" "$1" "$stdout_reset" "$white" "$2" "$stdout_reset"; }
next_step() { printf "%bNext:%b %b%s%b\n" "$orange" "$stdout_reset" "$white" "$1" "$stdout_reset"; }
err() { printf "%b✗ %s%b\n" "$red" "$1" "$stderr_reset" >&2; }

heading "Setting up TogetherLink..."

# --- 1. Ensure Bun is present (install it for the user if not) ----------------
if command -v bun >/dev/null 2>&1; then
  :
else
  if ! command -v unzip >/dev/null 2>&1; then
    err "Need unzip to install Bun. Install unzip, then re-run the TogetherLink installer."
    err "On Ubuntu/Debian: sudo apt-get update && sudo apt-get install -y unzip"
    exit 1
  fi
  info "Installing Bun…"
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL https://bun.sh/install | bash >/dev/null
  elif command -v fetch >/dev/null 2>&1; then
    fetch -o - https://bun.sh/install | bash >/dev/null
  else
    err "Need curl to install Bun. Please install curl and re-run."
    exit 1
  fi
  # bun.sh writes to ~/.bun; add to PATH for this script's later bun calls.
  export BUN_INSTALL="$HOME/.bun"
  export PATH="$BUN_INSTALL/bin:$PATH"
  if ! command -v bun >/dev/null 2>&1; then
    err "Bun install finished but bun isn't on PATH. Open a new shell and re-run."
    exit 1
  fi
fi
BUN_BIN="$(command -v bun)"
# PATH can contain relative directories; the installed wrapper must also work
# after the user changes directories. Keep the binary symlink itself intact.
BUN_BIN="$(cd "$(dirname "$BUN_BIN")" && pwd)/$(basename "$BUN_BIN")"
# The wrapper runs Bun with --no-env-file (Bun 1.3.3+) so a project's .env in
# the launch directory never reaches the harness or the gateway.
# Bun also runs the launch directory's bunfig.toml preloads, so every Bun call
# here and in the wrapper reads an empty config instead.
# Older Bun accepts unknown flags without disabling dotenv, so a successful
# empty invocation does not establish support. Check the running version too.
if ! "$BUN_BIN" --config=/dev/null --no-env-file -e '
  const version = /^(\d+)\.(\d+)\.(\d+)$/.exec(Bun.version);
  if (!version) process.exit(1);
  const [major, minor, patch] = version.slice(1).map(Number);
  if (!(major > 1 || (major === 1 && (minor > 3 || (minor === 3 && patch >= 3))))) process.exit(1);
' >/dev/null 2>&1; then
  err "TogetherLink needs Bun 1.3.3 or newer. Run 'bun upgrade' and re-run this installer."
  exit 1
fi

# --- 2. Remove retired local proxy configuration ------------------------------
# Releases before the cloud-only CLI could leave either a portable daemon or a
# user-level supervisor running after the bundle was upgraded. Stop only a PID
# whose command points at this installation's old bundle, then remove the
# retired TogetherLink-owned service definitions, local proxy state, and the
# user-supplied Exa key that the hosted gateway has replaced.
remove_retired_local_state() {
  legacy_removed=0
  launchd_plist="$HOME/Library/LaunchAgents/com.togetherlink.daemon.plist"
  systemd_unit="$HOME/.config/systemd/user/togetherlink-daemon.service"

  if [ -f "$launchd_plist" ]; then
    if command -v launchctl >/dev/null 2>&1; then
      launchctl bootout "gui/$(id -u)" "$launchd_plist" >/dev/null 2>&1 || true
    fi
    rm -f "$launchd_plist"
    legacy_removed=1
  fi

  if [ -f "$systemd_unit" ]; then
    if command -v systemctl >/dev/null 2>&1; then
      systemctl --user stop togetherlink-daemon.service >/dev/null 2>&1 || true
      systemctl --user disable togetherlink-daemon.service >/dev/null 2>&1 || true
    fi
    rm -f "$systemd_unit"
    if command -v systemctl >/dev/null 2>&1; then
      systemctl --user daemon-reload >/dev/null 2>&1 || true
    fi
    legacy_removed=1
  fi

  legacy_pid=""
  if [ -f "$INSTALL_DIR/daemon.pid" ]; then
    IFS= read -r legacy_pid < "$INSTALL_DIR/daemon.pid" || true
  fi
  case "$legacy_pid" in
    ""|*[!0-9]*) ;;
    *)
      legacy_command="$(ps -p "$legacy_pid" -o command= 2>/dev/null || true)"
      case "$legacy_command" in
        *"$BIN_DIR/togetherlink.js"*--daemon*|*"$BIN_DIR/togetherlink"*daemon*)
          kill "$legacy_pid" >/dev/null 2>&1 || true
          for _ in {1..20}; do
            kill -0 "$legacy_pid" >/dev/null 2>&1 || break
            sleep 0.1
          done
          if kill -0 "$legacy_pid" >/dev/null 2>&1; then
            kill -KILL "$legacy_pid" >/dev/null 2>&1 || true
          fi
          legacy_removed=1
          ;;
      esac
      ;;
  esac

  for legacy_path in \
    "$INSTALL_DIR/daemon.pid" \
    "$INSTALL_DIR/daemon-sessions.json" \
    "$INSTALL_DIR/daemon.sqlite" \
    "$INSTALL_DIR/daemon.sqlite-shm" \
    "$INSTALL_DIR/daemon.sqlite-wal" \
    "$INSTALL_DIR/local-proxy-token" \
    "$INSTALL_DIR/launchd-auto-installed" \
    "$INSTALL_DIR/launchd-supervision-v2-installed" \
    "$INSTALL_DIR/launchd-supervision-v3-installed" \
    "$INSTALL_DIR/launchd-supervision-v4-installed" \
    "$INSTALL_DIR/systemd-auto-installed" \
    "$INSTALL_DIR/systemd-supervision-v2-installed" \
    "$INSTALL_DIR/systemd-supervision-v3-installed" \
    "$INSTALL_DIR/systemd-supervision-v4-installed" \
    "$INSTALL_DIR/logs/daemon.log"; do
    if [ -e "$legacy_path" ]; then
      legacy_removed=1
      rm -f "$legacy_path"
    fi
  done
  rm -f "$INSTALL_DIR"/daemon-sessions.json.*.tmp

  legacy_config="$INSTALL_DIR/config.json"
  if [ -f "$legacy_config" ]; then
    exa_removed="$("$BUN_BIN" --config=/dev/null --no-env-file -e '
      const fs = require("node:fs");
      const configPath = process.argv[1];
      try {
        const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
        if (
          config &&
          typeof config === "object" &&
          !Array.isArray(config) &&
          Object.prototype.hasOwnProperty.call(config, "exaApiKey")
        ) {
          delete config.exaApiKey;
          const temporaryPath = `${configPath}.${process.pid}.tmp`;
          fs.writeFileSync(temporaryPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
          fs.renameSync(temporaryPath, configPath);
          process.stdout.write("removed");
        }
      } catch {}
    ' "$legacy_config" 2>/dev/null || true)"
    if [ "$exa_removed" = "removed" ]; then
      legacy_removed=1
    fi
  fi

}

# --- 3. Download the latest bundle + manifest --------------------------------
mkdir -p "$BIN_DIR"

download_dir="$(mktemp -d "$BIN_DIR/.install.XXXXXX")"
download_bundle="$download_dir/togetherlink.js"
cleanup_download() {
  rm -f "$download_bundle"
  rmdir "$download_dir" 2>/dev/null || true
}
trap cleanup_download EXIT

download_bundle_file() {
  if [ ! -t 1 ] || [ "${TERM:-}" = "dumb" ] || [ -n "${NO_COLOR:-}" ]; then
    curl -fsSL "$ORIGIN/togetherlink.js" -o "$download_bundle"
    return
  fi

  curl -fsSL "$ORIGIN/togetherlink.js" -o "$download_bundle" &
  download_pid=$!
  spinner_frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
  spinner_index=0
  while kill -0 "$download_pid" 2>/dev/null; do
    printf "\r%b%s%b Downloading TogetherLink..." \
      "$orange" "${spinner_frames[$spinner_index]}" "$stdout_reset"
    spinner_index=$(((spinner_index + 1) % ${#spinner_frames[@]}))
    sleep 0.08
  done
  download_status=0
  wait "$download_pid" || download_status=$?
  printf "\r\033[2K"
  return "$download_status"
}

if ! download_bundle_file; then
  err "Failed to download $ORIGIN/togetherlink.js"
  exit 1
fi
if ! downloaded_version="$(HOME="$download_dir" TOGETHERLINK_HOME="$download_dir" "$BUN_BIN" --config=/dev/null --no-env-file "$download_bundle" --version 2>/dev/null)" || \
   [[ ! "$downloaded_version" =~ ^togetherlink\ v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
  err "Downloaded bundle failed its version check. Existing installation was preserved; please retry."
  exit 1
fi

remove_retired_local_state
mv -f "$download_bundle" "$BIN_DIR/togetherlink.js"

# Keep installs on the Railway-supplied update manifest.
if [ -n "${TOGETHERLINK_MANIFEST_URL:-}" ]; then
  printf "%s\n" "$TOGETHERLINK_MANIFEST_URL" > "$UPDATE_CHANNEL_FILE"
  chmod 600 "$UPDATE_CHANNEL_FILE"
else
  rm -f "$UPDATE_CHANNEL_FILE"
fi

# Railway injects the cloud gateway at install time. The endpoint stays out of
# the CLI source and bundle.
if [ -n "${TOGETHERLINK_GATEWAY_URL:-}" ]; then
  printf "%s\n" "$TOGETHERLINK_GATEWAY_URL" > "$GATEWAY_URL_FILE"
  chmod 600 "$GATEWAY_URL_FILE"
else
  rm -f "$GATEWAY_URL_FILE"
fi

# --- 4. Write the `togetherlink` wrapper that runs the bundle with bun --------
cat > "$BIN_DIR/togetherlink" <<EOF
#!/usr/bin/env sh
# togetherlink launcher — runs the installed Bun-target JS bundle.
if [ -f "$GATEWAY_URL_FILE" ]; then
  # A shell builtin, so no subprocess runs on every command.
  IFS= read -r TOGETHERLINK_GATEWAY_URL < "$GATEWAY_URL_FILE"
  export TOGETHERLINK_GATEWAY_URL
fi
exec "$BUN_BIN" --config=/dev/null --no-env-file "$BIN_DIR/togetherlink.js" "\$@"
EOF
chmod +x "$BIN_DIR/togetherlink"

cat > "$BIN_DIR/tlink" <<EOF
#!/usr/bin/env sh
exec "$BIN_DIR/togetherlink" "\$@"
EOF
chmod +x "$BIN_DIR/tlink"

cat > "$BIN_DIR/tcodex" <<EOF
#!/usr/bin/env sh
exec "$BIN_DIR/togetherlink" codex "\$@"
EOF
chmod +x "$BIN_DIR/tcodex"

cat > "$BIN_DIR/tclaude" <<EOF
#!/usr/bin/env sh
exec "$BIN_DIR/togetherlink" claude "\$@"
EOF
chmod +x "$BIN_DIR/tclaude"

cat > "$BIN_DIR/tclaude-desktop" <<EOF
#!/usr/bin/env sh
exec "$BIN_DIR/togetherlink" claude-desktop "\$@"
EOF
chmod +x "$BIN_DIR/tclaude-desktop"

for wrapper in topencode:opencode tpi:pi; do
  name="${wrapper%%:*}"
  harness="${wrapper##*:}"
  cat > "$BIN_DIR/$name" <<EOF
#!/usr/bin/env sh
exec "$BIN_DIR/togetherlink" $harness "\$@"
EOF
  chmod +x "$BIN_DIR/$name"
done

is_exact_generated_wrapper() {
  wrapper_path="$1"
  shift
  actual="$(cat "$wrapper_path"; printf '__TOGETHERLINK_EOF__')"
  for shebang in '#!/usr/bin/env sh' '#!/bin/sh'; do
    for launcher in "$@"; do
      expected="$(printf '%s\n%s\n__TOGETHERLINK_EOF__' "$shebang" "$launcher")"
      [ "$actual" = "$expected" ] && return 0
    done
  done
  return 1
}

for retired in tdeepseek tgrok thermes tprime; do
  path="$BIN_DIR/$retired"
  case "$retired" in
    tdeepseek) harness="deepseek" ;;
    tgrok) harness="grok" ;;
    thermes) harness="hermes" ;;
    tprime) harness="prime" ;;
  esac
  if [ -f "$path" ] && is_exact_generated_wrapper "$path" \
    "exec \"$BIN_DIR/togetherlink\" $harness \"\$@\"" \
    "exec '$BIN_DIR/togetherlink' $harness \"\$@\"" \
    "exec bun \"$BIN_DIR/togetherlink.js\" $harness \"\$@\"" \
    "exec bun '$BIN_DIR/togetherlink.js' $harness \"\$@\""; then
    rm -f "$path"
  fi
done

# Remove the retired ChatGPT Desktop wrapper only when it is the old
# TogetherLink-owned launcher.
if [ -f "$BIN_DIR/tchatgpt" ] && is_exact_generated_wrapper "$BIN_DIR/tchatgpt" \
  "exec \"$BIN_DIR/togetherlink\" chatgpt \"\$@\"" \
  "exec '$BIN_DIR/togetherlink' chatgpt \"\$@\"" \
  "exec bun \"$BIN_DIR/togetherlink.js\" chatgpt \"\$@\"" \
  "exec bun '$BIN_DIR/togetherlink.js' chatgpt \"\$@\""; then
  rm -f "$BIN_DIR/tchatgpt"
fi

# Remove old togetherlink-owned wrappers that used the upstream agent names.
# Current installs must never shadow `claude`, `codex`, or `opencode`; users
# should get the real CLIs unless they explicitly run a togetherlink wrapper.
remove_legacy_shadow_wrapper() {
  name="$1"
  path="$BIN_DIR/$name"

  [ -e "$path" ] || [ -L "$path" ] || return 0

  if [ -L "$path" ]; then
    target="$(readlink "$path" 2>/dev/null || true)"
    case "$target" in
      "$BIN_DIR/tclaude"|"$BIN_DIR/tcodex"|"$BIN_DIR/tdeepseek"|"$BIN_DIR/topencode"|"$BIN_DIR/tgrok"|"$BIN_DIR/thermes"|"$BIN_DIR/tpi"|"$BIN_DIR/tprime"|"$BIN_DIR/togetherlink"|"$BIN_DIR/togetherlink.js")
        rm -f "$path"
        ;;
    esac
    return 0
  fi

  if [ -f "$path" ] && is_exact_generated_wrapper "$path" \
    "exec bun \"$BIN_DIR/togetherlink.js\" $name \"\$@\"" \
    "exec bun '$BIN_DIR/togetherlink.js' $name \"\$@\"" \
    "exec \"$BIN_DIR/togetherlink\" $name \"\$@\"" \
    "exec '$BIN_DIR/togetherlink' $name \"\$@\""; then
    rm -f "$path"
  fi
}

remove_legacy_shadow_wrapper claude
remove_legacy_shadow_wrapper codex
remove_legacy_shadow_wrapper opencode
remove_legacy_shadow_wrapper grok

# Previous installers linked TogetherLink commands into the first writable
# directory on PATH. Keep active links working for existing users; remove only
# retired TogetherLink-owned shortcuts and leave unrelated commands untouched.
remove_legacy_external_links() {
  old_ifs="$IFS"
  IFS=:
  for dir in $PATH; do
    IFS="$old_ifs"
    [ -n "$dir" ] || continue
    [ "$dir" != "$BIN_DIR" ] || continue
    [ -d "$dir" ] && [ -w "$dir" ] || continue

    for name in togetherlink tlink tcodex tclaude tclaude-desktop topencode tdeepseek tgrok thermes tpi tprime tchatgpt; do
      path="$dir/$name"
      [ -L "$path" ] || continue
      target="$(readlink "$path" 2>/dev/null || true)"
      case "$target" in
        "$BIN_DIR/$name"|*/.togetherlink/bin/"$name")
          case "$name" in
            togetherlink|tlink|tcodex|tclaude|tclaude-desktop|topencode|tpi)
              # Keep working old shortcuts, but clear broken ones that shadow
              # the public launcher created below.
              [ ! -e "$path" ] || continue
              ;;
          esac
          rm -f "$path"
          ;;
      esac
    done
    IFS=:
  done
  IFS="$old_ifs"
}

remove_legacy_external_links

# Keep the bundle and managed wrappers together, while exposing commands from
# the conventional user bin directory used by Claude Code and Codex. Preserve
# unrelated commands already installed there.
repair_dangling_public_link() {
  name="$1"
  public_path="$PUBLIC_BIN_DIR/$name"
  [ -L "$public_path" ] && [ ! -e "$public_path" ] || return 0
  target="$(readlink "$public_path" 2>/dev/null || true)"
  case "$target" in
    "$BIN_DIR/$name"|*/.togetherlink/bin/"$name") rm -f "$public_path" ;;
  esac
}

command_bin_dir="$BIN_DIR"
if { [ -d "$PUBLIC_BIN_DIR" ] || mkdir -p "$PUBLIC_BIN_DIR" 2>/dev/null; } && \
   [ -d "$PUBLIC_BIN_DIR" ] && [ -w "$PUBLIC_BIN_DIR" ]; then
  repair_dangling_public_link togetherlink
  if [ ! -e "$PUBLIC_BIN_DIR/togetherlink" ] && [ ! -L "$PUBLIC_BIN_DIR/togetherlink" ]; then
    ln -s "$BIN_DIR/togetherlink" "$PUBLIC_BIN_DIR/togetherlink" 2>/dev/null || true
  fi
  if [ -L "$PUBLIC_BIN_DIR/togetherlink" ] && \
     [ "$(readlink "$PUBLIC_BIN_DIR/togetherlink")" = "$BIN_DIR/togetherlink" ]; then
    command_bin_dir="$PUBLIC_BIN_DIR"
    for name in tlink tcodex tclaude tclaude-desktop topencode tpi; do
      repair_dangling_public_link "$name"
      public_path="$PUBLIC_BIN_DIR/$name"
      if [ ! -e "$public_path" ] && [ ! -L "$public_path" ]; then
        ln -s "$BIN_DIR/$name" "$public_path" 2>/dev/null || true
      fi
    done
    # Retired wrappers once linked into the public bin leave dangling links
    # when the managed launcher is removed. Clear them the same way, since
    # remove_legacy_external_links only covers directories on PATH.
    for retired in tchatgpt tdeepseek thermes tgrok tprime; do
      repair_dangling_public_link "$retired"
    done
  fi
fi

# A piped installer cannot change its parent shell's PATH. Report the command
# as ready only when PATH actually resolves to this installation's launcher.
commands_ready=0
resolved_command="$(command -v togetherlink 2>/dev/null || true)"
if [ "$resolved_command" = "$command_bin_dir/togetherlink" ] || \
   [ "$resolved_command" = "$BIN_DIR/togetherlink" ]; then
  commands_ready=1
elif [ -L "$resolved_command" ] && \
     [ "$(readlink "$resolved_command")" = "$BIN_DIR/togetherlink" ]; then
  # An existing install may have linked the launcher into another PATH dir.
  commands_ready=1
elif [ -L "$resolved_command" ] && command -v realpath >/dev/null 2>&1; then
  resolved_real="$(realpath "$resolved_command" 2>/dev/null || true)"
  managed_real="$(realpath "$BIN_DIR/togetherlink" 2>/dev/null || true)"
  if [ -n "$resolved_real" ] && [ "$resolved_real" = "$managed_real" ]; then
    commands_ready=1
  fi
fi

# --- 5. Help the user get it on PATH permanently -----------------------------
path_line="export PATH=\"$command_bin_dir:\$PATH\""

detect_shell_rc() {
  case "${SHELL:-}" in
    */zsh)
      case "${ZDOTDIR:-}" in
        /*) printf "%s/.zshrc" "$ZDOTDIR" ;;
        *)  printf "%s/.zshrc" "$HOME" ;;
      esac
      ;;
    */bash) printf "%s/.bashrc" "$HOME" ;;
    *)      printf "%s/.profile" "$HOME" ;;
  esac
}

configure_shell_path() {
    SHELL_RC="$1"
    mkdir -p "$(dirname "$SHELL_RC")"
    touch "$SHELL_RC"

    if grep -Fqs "$path_line" "$SHELL_RC"; then
      :
    else
      {
        printf "\n# togetherlink\n"
        printf "%s\n" "$path_line"
      } >> "$SHELL_RC"
    fi
}

# Persist startup configuration even when this terminal already has a
# temporary PATH export from an earlier install.
shell_path_persisted=0
case "${SHELL:-}" in
  */zsh|*/bash|*/sh|*/dash|*/ksh|*/ash|"")
    configure_shell_path "$(detect_shell_rc)"
    shell_path_persisted=1
    ;;
esac
# Login Bash reads only the first existing login profile, not .bashrc.
# Preserve that choice rather than creating a file that shadows it.
case "${SHELL:-}" in
  */bash)
    login_rc="$HOME/.bash_profile"
    for candidate in "$HOME/.bash_profile" "$HOME/.bash_login" "$HOME/.profile"; do
      if [ -f "$candidate" ]; then
        login_rc="$candidate"
        break
      fi
    done
    configure_shell_path "$login_rc"
    ;;
esac

# Verify the install works right now if already on PATH, else with explicit PATH.
if ! installed_version="$(PATH="$BIN_DIR:$PATH" togetherlink --version 2>/dev/null)" || \
   [ "$installed_version" != "$downloaded_version" ]; then
  err "Installed launcher failed its version check. Re-run the installer."
  exit 1
fi
printf "\n"
installed_release="${downloaded_version#togetherlink }"
case "$command_bin_dir/togetherlink" in
  "$HOME"/*) location_display="~/${command_bin_dir#"$HOME"/}/togetherlink" ;;
  *) location_display="$command_bin_dir/togetherlink" ;;
esac
# A piped installer cannot add the launcher to its parent shell's PATH, and the
# printed export is easy to miss. When a terminal is attached, start the
# installed CLI right after the receipt so the first run needs no PATH setup.
# Redirected output, CI, and TOGETHERLINK_NO_LAUNCH=1 keep the installer quiet.
launch_now=0
if [ -z "${TOGETHERLINK_NO_LAUNCH:-}" ] && [ -z "${CI:-}" ] && \
   [ -t 1 ] && [ "${TERM:-}" != "dumb" ] && { : < /dev/tty; } 2>/dev/null; then
  launch_now=1
fi

success "TogetherLink successfully installed!"
printf "\n"
metadata "Version" "${installed_release#v}"
metadata "Location" "$location_display"
if [ "$launch_now" -eq 1 ]; then
  next_step "Starting TogetherLink"
  if [ "$commands_ready" -eq 1 ]; then
    info "      Later: run togetherlink"
  elif [ "$shell_path_persisted" -eq 1 ]; then
    info "      New terminal: run togetherlink (PATH added to $SHELL_RC)"
    info "      This terminal: run $path_line first"
  else
    case "${SHELL:-}" in
      */fish) info "      Later: run fish_add_path \"$command_bin_dir\"; then run togetherlink" ;;
      *) info "      Later: add $command_bin_dir to your shell's PATH, then run togetherlink" ;;
    esac
  fi
elif [ "$commands_ready" -eq 1 ]; then
  next_step "Run togetherlink to get started"
elif [ "$shell_path_persisted" -eq 1 ]; then
  next_step "Run $path_line && togetherlink"
else
  case "${SHELL:-}" in
    */fish) next_step "Run fish_add_path \"$command_bin_dir\"; then run togetherlink" ;;
    *) next_step "Add $command_bin_dir to your shell's PATH, then run togetherlink" ;;
  esac
fi
if [ "$launch_now" -eq 0 ] && [ "$shell_path_persisted" -eq 1 ]; then
  info "      New terminal: run togetherlink (PATH added to $SHELL_RC)"
fi
printf "\n"
printf "%b✅ Installation complete!%b\n\n" "$green" "$stdout_reset"

if [ "$launch_now" -eq 1 ]; then
  # Bash does not run the EXIT trap across exec; release the download
  # directory here. A piped installer's stdin is the script itself, so the CLI
  # gets the terminal through the inherited stdout descriptor: Bun ignores
  # keyboard input on a separately opened /dev/tty, but reads a dup of the
  # terminal's own descriptor.
  cleanup_download
  trap - EXIT
  export PATH="$command_bin_dir:$PATH"
  exec "$command_bin_dir/togetherlink" 0<&1
fi
