#!/bin/sh
# Plugin launcher for Claude Code and Codex. Downloads the pinned release
# binaries on first use, verifies their published sha256, caches them per
# version, then execs the server. stdout is left to the MCP stream; every
# message goes to stderr.
set -eu

version=0.7.27

say() {
  echo "computer-use-linux plugin: $*" >&2
}

if [ -n "${COMPUTER_USE_LINUX_BIN:-}" ]; then
  exec "$COMPUTER_USE_LINUX_BIN" "$@"
fi

case "$(uname -s)" in
  Linux) ;;
  *)
    say "unsupported platform: $(uname -s). This plugin only supports Linux."
    exit 64
    ;;
esac

case "$(uname -m)" in
  x86_64 | amd64) target=x86_64-unknown-linux-gnu ;;
  aarch64 | arm64) target=aarch64-unknown-linux-gnu ;;
  *)
    say "unsupported CPU architecture: $(uname -m). Supported: x86_64, aarch64."
    exit 64
    ;;
esac

cache_root="${XDG_CACHE_HOME:-$HOME/.cache}/computer-use-linux/plugin"
cache_dir="$cache_root/v$version"
binary="$cache_dir/computer-use-linux"
helper="$cache_dir/computer-use-linux-cosmic"
indicator="$cache_dir/computer-use-linux-indicator"

fetch() {
  if command -v curl >/dev/null 2>&1; then
    curl -fsSL --retry 2 -o "$2" "$1"
  elif command -v wget >/dev/null 2>&1; then
    wget -q -O "$2" "$1"
  else
    say "curl or wget is required to download the server binary"
    return 1
  fi
}

# Only a missing release asset is optional; network and checksum errors fail.
fetch_optional() {
  case "$1" in
    file://*) [ -e "${1#file://}" ] || return 10 ;;
  esac
  if command -v curl >/dev/null 2>&1; then
    code=$(curl -fsSL --retry 2 -o "$2" -w '%{http_code}' "$1") && return 0
    [ "$code" = "404" ] && return 10
  elif command -v wget >/dev/null 2>&1; then
    out=$(wget -S -O "$2" "$1" 2>&1) && return 0
    echo "$out" >&2
    echo "$out" | grep -q ' 404 ' && return 10
  else
    say "curl or wget is required to download the server binary"
  fi
  return 1
}

sha256() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | cut -d' ' -f1
  else
    shasum -a 256 "$1" | cut -d' ' -f1
  fi
}

install_release() {
  base="${COMPUTER_USE_LINUX_DOWNLOAD_BASE:-https://github.com/agent-sh/computer-use-linux/releases/download/v$version}"
  mkdir -p "$cache_root"
  tmp=$(mktemp -d "$cache_root/.download.XXXXXX")
  trap 'rm -rf "$tmp"' EXIT
  trap 'exit 130' INT
  trap 'exit 143' TERM

  for name in computer-use-linux computer-use-linux-cosmic computer-use-linux-indicator; do
    # A cache made by an older launcher can already contain the main binaries.
    [ ! -x "$cache_dir/$name" ] || continue
    asset="$name-$target"
    say "downloading $asset v$version"
    if [ "$name" = computer-use-linux-indicator ]; then
      status=0
      fetch_optional "$base/$asset" "$tmp/$name" || status=$?
      if [ "$status" -eq 10 ]; then
        rm -f "$tmp/$name"
        say "release v$version has no indicator; running without the overlay"
        : >"$tmp/.no-indicator"
        continue
      fi
      [ "$status" -eq 0 ] || return "$status"
    else
      fetch "$base/$asset" "$tmp/$name"
    fi
    fetch "$base/$asset.sha256" "$tmp/$name.sha256"
    expected=$(grep -o -E '[0-9a-fA-F]{64}' "$tmp/$name.sha256" | head -n 1 | tr 'A-F' 'a-f')
    actual=$(sha256 "$tmp/$name")
    if [ -z "$expected" ] || [ "$expected" != "$actual" ]; then
      say "sha256 mismatch for $asset: expected ${expected:-<none>}, got $actual"
      exit 1
    fi
    chmod 0755 "$tmp/$name"
  done

  # Concurrent first starts each stage their own copy; rename keeps the swap
  # atomic, and the helpers land before the main binary that gates reuse.
  mkdir -p "$cache_dir"
  for name in computer-use-linux-cosmic computer-use-linux-indicator computer-use-linux; do
    if [ -f "$tmp/$name" ]; then
      mv -f "$tmp/$name" "$cache_dir/$name"
    fi
  done
  if [ -f "$tmp/.no-indicator" ]; then
    mv -f "$tmp/.no-indicator" "$cache_dir/.no-indicator"
  elif [ -x "$indicator" ]; then
    rm -f "$cache_dir/.no-indicator"
  fi
  rm -rf "$tmp"
  trap - EXIT INT TERM
}

if [ ! -x "$binary" ] || [ ! -x "$helper" ] || { [ ! -x "$indicator" ] && [ ! -f "$cache_dir/.no-indicator" ]; }; then
  install_release
fi

# Claude Code and Codex update plugins independently, so another version may
# still be in use. Mark this one live and drop versions unused for 30 days,
# plus staging dirs left by a killed download.
touch "$cache_dir"
find "$cache_root" -mindepth 1 -maxdepth 1 \
  \( \( -name 'v*' ! -path "$cache_dir" -mtime +30 \) -o \( -name '.download.*' -mmin +60 \) \) \
  -exec rm -rf {} + 2>/dev/null || true

if [ -z "${COMPUTER_USE_LINUX_COSMIC_HELPER:-}" ]; then
  COMPUTER_USE_LINUX_COSMIC_HELPER="$helper"
  export COMPUTER_USE_LINUX_COSMIC_HELPER
fi
if [ -z "${COMPUTER_USE_LINUX_INDICATOR_BIN:-}" ] && [ -x "$indicator" ]; then
  COMPUTER_USE_LINUX_INDICATOR_BIN="$indicator"
  export COMPUTER_USE_LINUX_INDICATOR_BIN
fi

exec "$binary" "$@"
