#!/usr/bin/env bash
#
# Pareto installer for macOS.
#
# Run locally with:
#   bash "Install Pareto.sh"
#
# The installer keeps Pareto isolated from existing Pi, Claude, and Codex
# settings. It stores the API key in macOS Keychain and creates a Pareto
# launcher in ~/Applications.

set -Eeuo pipefail

PARETO_API_BASE="${PARETO_API_BASE:-https://api.unbiased.ai/v1}"
PARETO_MODEL="${PARETO_MODEL:-pareto}"
PARETO_ROOT="${PARETO_ROOT:-$HOME/.local/share/pareto}"
PARETO_BIN_DIR="${PARETO_BIN_DIR:-$HOME/.local/bin}"
PARETO_APP_DIR="${PARETO_APP_DIR:-$HOME/Applications}"
PARETO_KEYCHAIN_SERVICE="${PARETO_KEYCHAIN_SERVICE:-ai.unbiased.pareto}"
ATXP_API_BASE="${ATXP_API_BASE:-https://llm.atxp.ai/v1}"
ATXP_KEYCHAIN_SERVICE="${ATXP_KEYCHAIN_SERVICE:-codex-atxp-connection-string}"
ATXP_LUNA_MODEL="${ATXP_LUNA_MODEL:-openai/gpt-5.6-luna}"
ATXP_TERRA_MODEL="${ATXP_TERRA_MODEL:-openai/gpt-5.6-terra}"
ATXP_SOL_MODEL="${ATXP_SOL_MODEL:-openai/gpt-5.6-sol}"
NODE_VERSION="${NODE_VERSION:-22.19.0}"
PI_PACKAGE="${PI_PACKAGE:-@earendil-works/pi-coding-agent@0.81.1}"

PI_ROOT="$PARETO_ROOT/runtime"
PI_BIN="$PI_ROOT/node_modules/.bin/pi"
PI_AGENT_DIR="$PARETO_ROOT/agent"
NODE_ROOT="$PARETO_ROOT/node"
PARETO_BIN="$PARETO_BIN_DIR/pareto"
PARETO_DOCTOR_BIN="$PARETO_BIN_DIR/pareto-doctor"
PARETO_LAUNCHER="$PARETO_APP_DIR/Pareto.command"

PLATFORM_SIGNUP_URL="https://platform.unbiased.ai/sign-up?utm_source=unbiased.ai&utm_medium=installer&utm_campaign=installer_setup"

RESPONSE_FILE=""
CHECK_ERROR=""
KEY_SOURCE=""
KEY_VERIFIED=0
API_KEY=""
ATXP_CREDENTIAL=""
ATXP_SOURCE=""
ATXP_ENABLED=0

cleanup() {
  if [[ -n "$RESPONSE_FILE" && -f "$RESPONSE_FILE" ]]; then
    rm -f "$RESPONSE_FILE"
  fi
}
trap cleanup EXIT

say() {
  printf '%s\n' "$*"
}

fail() {
  printf '\nPareto was not installed.\n%s\n' "$*" >&2
  exit 1
}

require_macos() {
  [[ "$(uname -s)" == "Darwin" ]] || fail "This installer currently supports macOS."
  command -v curl >/dev/null 2>&1 || fail "curl is required."
}

read_keychain_key() {
  if [[ "${PARETO_SKIP_KEYCHAIN:-0}" == "1" ]]; then
    return 1
  fi

  security find-generic-password \
    -a "$USER" \
    -s "$PARETO_KEYCHAIN_SERVICE" \
    -w 2>/dev/null
}

read_atxp_keychain() {
  if [[ "${PARETO_SKIP_KEYCHAIN:-0}" == "1" ]]; then
    return 1
  fi

  security find-generic-password \
    -a "$USER" \
    -s "$ATXP_KEYCHAIN_SERVICE" \
    -w 2>/dev/null
}

detect_atxp() {
  local credential=""

  if [[ -n "${ATXP_CONNECTION_STRING:-}" ]]; then
    ATXP_CREDENTIAL="$ATXP_CONNECTION_STRING"
    ATXP_SOURCE="environment"
    ATXP_ENABLED=1
    return
  fi

  if credential="$(read_atxp_keychain)" && [[ -n "$credential" ]]; then
    ATXP_CREDENTIAL="$credential"
    ATXP_SOURCE="keychain"
    ATXP_ENABLED=1
  fi
}

ask_for_key() {
  # One prompt, GUI or terminal. An empty answer means "no key yet" and is
  # a valid outcome — the install proceeds either way.
  local key=""

  if command -v osascript >/dev/null 2>&1 && [[ "${PARETO_NO_GUI:-0}" != "1" ]]; then
    key="$(
      osascript <<'APPLESCRIPT'
try
  text returned of (display dialog "Paste your Pareto API key. Leave it empty if you do not have one yet - Pareto asks again on first run." default answer "" with hidden answer buttons {"I don't have one yet", "Continue"} default button "Continue" with title "Connect Pareto")
on error number -128
  return ""
end try
APPLESCRIPT
    )"
  elif [[ -t 0 ]]; then
    printf 'Paste your Pareto API key (press Return to skip for now): '
    IFS= read -r -s key
    printf '\n'
  fi

  printf '%s' "$key"
}

resolve_key() {
  local key=""

  if [[ -n "${UNBIASED_API_KEY:-}" ]]; then
    API_KEY="$UNBIASED_API_KEY"
    KEY_SOURCE="environment"
    return
  fi

  if key="$(read_keychain_key)" && [[ -n "$key" ]]; then
    API_KEY="$key"
    KEY_SOURCE="keychain"
    return
  fi

  key="$(ask_for_key)"
  if [[ -n "$key" ]]; then
    API_KEY="$key"
    KEY_SOURCE="prompt"
  else
    API_KEY=""
    KEY_SOURCE="none"
    say "No key yet - installing anyway. Pareto asks for one at first run."
  fi
}

verify_key() {
  # Confirms the key against the live service. A rejected key gets fresh
  # prompts instead of an abort; nothing here ever blocks the install.
  local attempts=0
  local key=""

  KEY_VERIFIED=0
  [[ -n "$API_KEY" ]] || return 0

  while :; do
    if check_endpoint; then
      KEY_VERIFIED=1
      return 0
    fi

    if [[ "$CHECK_ERROR" != *"rejected"* ]]; then
      say "Warning: $CHECK_ERROR"
      say "Installing anyway. Run 'pareto doctor' once the service is reachable."
      return 0
    fi

    attempts=$((attempts + 1))
    if (( attempts >= 3 )); then
      break
    fi

    if [[ "$KEY_SOURCE" == "keychain" ]]; then
      say "The saved Pareto key was rejected. Paste a current key to replace it, or leave it empty to finish without one."
    else
      say "That key was rejected. Paste a current key, or leave it empty to finish without one."
    fi

    key="$(ask_for_key)"
    if [[ -z "$key" ]]; then
      break
    fi
    API_KEY="$key"
    KEY_SOURCE="prompt"
  done

  API_KEY=""
  KEY_SOURCE="none"
}

check_endpoint() {
  local http_status
  local request_body

  CHECK_ERROR=""
  RESPONSE_FILE="$(mktemp)"
  request_body="$(
    printf '{"model":"%s","messages":[{"role":"user","content":"Reply exactly: PARETO_OK"}],"max_tokens":16}' \
      "$PARETO_MODEL"
  )"

  say "Checking the Pareto service..."

  if ! http_status="$(
    curl \
      --silent \
      --show-error \
      --connect-timeout 10 \
      --max-time 60 \
      --output "$RESPONSE_FILE" \
      --write-out '%{http_code}' \
      "${PARETO_API_BASE%/}/chat/completions" \
      -H "Authorization: Bearer $API_KEY" \
      -H "content-type: application/json" \
      --data "$request_body"
  )"; then
    CHECK_ERROR="Pareto could not be reached. Check this Mac's internet or VPN, then retry. If those are working, the service may be temporarily unavailable."
    return 1
  fi

  case "$http_status" in
    2??)
      ;;
    401|403)
      CHECK_ERROR="The Pareto API key was rejected."
      return 1
      ;;
    429)
      CHECK_ERROR="Pareto is temporarily busy."
      return 1
      ;;
    5??)
      CHECK_ERROR="Pareto is temporarily unavailable (HTTP $http_status)."
      return 1
      ;;
    *)
      CHECK_ERROR="Pareto returned an unexpected HTTP $http_status response."
      return 1
      ;;
  esac

  grep -q '"choices"' "$RESPONSE_FILE" || {
    CHECK_ERROR="Pareto returned an unexpected response."
    return 1
  }
}

ensure_node() {
  local archive
  local archive_name
  local arch
  local expected
  local node_url
  local shasums
  local temp_dir

  if [[ -x "$NODE_ROOT/bin/node" && -x "$NODE_ROOT/bin/npm" ]] &&
    "$NODE_ROOT/bin/node" -e 'const [a,b]=process.versions.node.split(".").map(Number); process.exit(a>22 || (a===22 && b>=19) ? 0 : 1)'; then
    export PATH="$NODE_ROOT/bin:$PATH"
    return
  fi

  if command -v node >/dev/null 2>&1 &&
    command -v npm >/dev/null 2>&1 &&
    node -e 'const [a,b]=process.versions.node.split(".").map(Number); process.exit(a>22 || (a===22 && b>=19) ? 0 : 1)'; then
    return
  fi

  case "$(uname -m)" in
    arm64)
      arch="arm64"
      ;;
    x86_64)
      arch="x64"
      ;;
    *)
      fail "This Mac's processor is not supported by the automatic Node.js setup."
      ;;
  esac

  archive_name="node-v${NODE_VERSION}-darwin-${arch}.tar.gz"
  node_url="https://nodejs.org/dist/v${NODE_VERSION}"
  temp_dir="$(mktemp -d)"
  archive="$temp_dir/$archive_name"
  shasums="$temp_dir/SHASUMS256.txt"

  say "Preparing the private Pareto runtime..."

  if ! curl --fail --silent --show-error --location \
    --connect-timeout 10 --max-time 180 \
    "$node_url/$archive_name" -o "$archive"; then
    rm -rf "$temp_dir"
    fail "The private Pareto runtime could not be downloaded."
  fi

  if ! curl --fail --silent --show-error --location \
    --connect-timeout 10 --max-time 30 \
    "$node_url/SHASUMS256.txt" -o "$shasums"; then
    rm -rf "$temp_dir"
    fail "The private Pareto runtime checksum could not be downloaded."
  fi

  expected="$(awk -v file="$archive_name" '$2 == file { print $1 }' "$shasums")"
  [[ -n "$expected" ]] || {
    rm -rf "$temp_dir"
    fail "The private Pareto runtime checksum was not found."
  }

  [[ "$(shasum -a 256 "$archive" | awk '{ print $1 }')" == "$expected" ]] || {
    rm -rf "$temp_dir"
    fail "The private Pareto runtime download failed checksum verification."
  }

  rm -rf "$NODE_ROOT"
  mkdir -p "$NODE_ROOT"
  tar -xzf "$archive" --strip-components 1 -C "$NODE_ROOT"
  rm -rf "$temp_dir"

  [[ -x "$NODE_ROOT/bin/node" && -x "$NODE_ROOT/bin/npm" ]] ||
    fail "The private Pareto runtime could not be installed."

  export PATH="$NODE_ROOT/bin:$PATH"
}

store_credentials() {
  if [[ "${PARETO_SKIP_KEYCHAIN:-0}" == "1" || -z "$API_KEY" ]]; then
    return
  fi

  security add-generic-password \
    -U \
    -a "$USER" \
    -s "$PARETO_KEYCHAIN_SERVICE" \
    -w "$API_KEY" >/dev/null

  if [[ "$ATXP_ENABLED" == "1" && "$ATXP_SOURCE" == "environment" ]]; then
    security add-generic-password \
      -U \
      -a "$USER" \
      -s "$ATXP_KEYCHAIN_SERVICE" \
      -w "$ATXP_CREDENTIAL" >/dev/null
  fi
}

install_pi() {
  local npm_log

  say "Installing the Pareto client..."
  mkdir -p "$PI_ROOT"
  npm_log="$(mktemp)"

  if ! npm install \
    --prefix "$PI_ROOT" \
    --omit=dev \
    --no-audit \
    --no-fund \
    "$PI_PACKAGE" >"$npm_log" 2>&1; then
    cat "$npm_log" >&2
    rm -f "$npm_log"
    fail "The Pareto client could not be installed."
  fi

  rm -f "$npm_log"

  [[ -x "$PI_BIN" ]] || fail "Pi installed, but its launcher was not found."
}

write_pi_config() {
  local api_key_config
  local atxp_key_config

  mkdir -p "$PI_AGENT_DIR"

  if [[ "${PARETO_SKIP_KEYCHAIN:-0}" == "1" ]]; then
    api_key_config='$UNBIASED_API_KEY'
    atxp_key_config='$ATXP_CONNECTION_STRING'
  else
    api_key_config="!security find-generic-password -a \\\"$USER\\\" -s \\\"$PARETO_KEYCHAIN_SERVICE\\\" -w"
    atxp_key_config="!security find-generic-password -a \\\"$USER\\\" -s \\\"$ATXP_KEYCHAIN_SERVICE\\\" -w"
  fi

  if [[ "$ATXP_ENABLED" == "1" ]]; then
    # pi requires cacheWrite; zero is an estimate placeholder, not a published rate.
    printf '%s\n' "Cache-write pricing is unpublished; this client's required zero placeholder excludes cache-write charges from its estimate. Actual billing may differ."
    cat >"$PI_AGENT_DIR/models.json" <<EOF
{
  "providers": {
    "unbiased": {
      "baseUrl": "$PARETO_API_BASE",
      "api": "openai-completions",
      "apiKey": "$api_key_config",
      "compat": {
        "supportsDeveloperRole": false,
        "supportsReasoningEffort": false,
        "maxTokensField": "max_tokens"
      },
      "models": [
        {
          "id": "$PARETO_MODEL",
          "name": "Pareto",
          "reasoning": false,
          "input": ["text"],
          "contextWindow": 128000,
          "maxTokens": 32768,
          "cost": {
            "input": 2.5,
            "output": 7.5,
            "cacheRead": 0.25,
            "cacheWrite": 0
          }
        }
      ]
    },
    "atxp": {
      "baseUrl": "$ATXP_API_BASE",
      "api": "openai-responses",
      "apiKey": "$atxp_key_config",
      "models": [
        {
          "id": "$ATXP_LUNA_MODEL",
          "name": "GPT-5.6 Luna (ATXP)",
          "reasoning": true,
          "input": ["text", "image"],
          "contextWindow": 272000,
          "maxTokens": 32768,
          "cost": {
            "input": 1,
            "output": 6,
            "cacheRead": 0,
            "cacheWrite": 0
          }
        },
        {
          "id": "$ATXP_TERRA_MODEL",
          "name": "GPT-5.6 Terra (ATXP)",
          "reasoning": true,
          "input": ["text", "image"],
          "contextWindow": 272000,
          "maxTokens": 32768,
          "cost": {
            "input": 1,
            "output": 6,
            "cacheRead": 0,
            "cacheWrite": 0
          }
        },
        {
          "id": "$ATXP_SOL_MODEL",
          "name": "GPT-5.6 Sol (ATXP)",
          "reasoning": true,
          "input": ["text", "image"],
          "contextWindow": 272000,
          "maxTokens": 32768,
          "cost": {
            "input": 1,
            "output": 6,
            "cacheRead": 0,
            "cacheWrite": 0
          }
        }
      ]
    }
  }
}
EOF

    cat >"$PI_AGENT_DIR/settings.json" <<EOF
{
  "defaultProvider": "unbiased",
  "defaultModel": "$PARETO_MODEL",
  "quietStartup": true
}
EOF
  else
    # pi requires cacheWrite; zero is an estimate placeholder, not a published rate.
    printf '%s\n' "Cache-write pricing is unpublished; this client's required zero placeholder excludes cache-write charges from its estimate. Actual billing may differ."
    cat >"$PI_AGENT_DIR/models.json" <<EOF
{
  "providers": {
    "unbiased": {
      "baseUrl": "$PARETO_API_BASE",
      "api": "openai-completions",
      "apiKey": "$api_key_config",
      "compat": {
        "supportsDeveloperRole": false,
        "supportsReasoningEffort": false,
        "maxTokensField": "max_tokens"
      },
      "models": [
        {
          "id": "$PARETO_MODEL",
          "name": "Pareto",
          "reasoning": false,
          "input": ["text"],
          "contextWindow": 128000,
          "maxTokens": 32768,
          "cost": {
            "input": 2.5,
            "output": 7.5,
            "cacheRead": 0.25,
            "cacheWrite": 0
          }
        }
      ]
    }
  }
}
EOF

    cat >"$PI_AGENT_DIR/settings.json" <<EOF
{
  "defaultProvider": "unbiased",
  "defaultModel": "$PARETO_MODEL",
  "quietStartup": true
}
EOF
  fi

  if [[ ! -f "$PI_AGENT_DIR/auth.json" ]]; then
    printf '{}\n' >"$PI_AGENT_DIR/auth.json"
  fi

  chmod 600 \
    "$PI_AGENT_DIR/models.json" \
    "$PI_AGENT_DIR/settings.json" \
    "$PI_AGENT_DIR/auth.json"
}

write_launchers() {
  local keychain_lane=1
  if [[ "${PARETO_SKIP_KEYCHAIN:-0}" == "1" ]]; then
    keychain_lane=0
  fi

  mkdir -p "$PARETO_BIN_DIR" "$PARETO_APP_DIR"

  cat >"$PARETO_DOCTOR_BIN" <<EOF
#!/usr/bin/env bash
set -Eeuo pipefail

export PI_CODING_AGENT_DIR="$PI_AGENT_DIR"
export PI_OFFLINE=1
export PATH="$NODE_ROOT/bin:\$PATH"

PI_BIN="$PI_BIN"
PARETO_MODEL="$PARETO_MODEL"
ATXP_ENABLED="$ATXP_ENABLED"
ATXP_MODEL="$ATXP_LUNA_MODEL"

mode="\${1:-all}"

check_provider() {
  local provider="\$1"
  local model="\$2"
  local expected="\$3"
  local marker_value="\$4"
  local text_output
  local tool_output
  local marker

  text_output="\$(
    "\$PI_BIN" \
      --provider "\$provider" \
      --model "\$model" \
      --no-session \
      --no-context-files \
      --no-skills \
      --no-extensions \
      --no-tools \
      --print \
      "Reply exactly: \${expected}_OK"
  )"

  grep -q "\${expected}_OK" <<<"\$text_output" || {
    printf '%s text check failed. Received: %s\n' "\$provider" "\$text_output" >&2
    return 1
  }

  marker="\$(mktemp)"
  tool_output="\$(
    "\$PI_BIN" \
      --provider "\$provider" \
      --model "\$model" \
      --no-session \
      --no-context-files \
      --no-skills \
      --no-extensions \
      --print \
      "RUN_TOOL_TEST: Use the bash tool to run exactly: printf \$marker_value > '\$marker'. Then reply exactly: \${expected}_TOOL_OK"
  )"

  if [[ ! -f "\$marker" || "\$(cat "\$marker")" != "\$marker_value" ]]; then
    rm -f "\$marker"
    printf '%s tool check failed. Received: %s\n' "\$provider" "\$tool_output" >&2
    return 1
  fi

  rm -f "\$marker"
  printf '%s text and tool checks passed.\n' "\$provider"
}

if [[ "\$mode" == "all" || "\$mode" == "pareto" ]]; then
  check_provider unbiased "\$PARETO_MODEL" PARETO pareto-tool-ok
fi

if [[ "\$mode" == "all" || "\$mode" == "atxp" ]]; then
  if [[ "\$ATXP_ENABLED" != "1" ]]; then
    [[ "\$mode" == "all" ]] || {
      printf 'ATXP is not configured on this Mac.\n' >&2
      exit 1
    }
  else
    check_provider atxp "\$ATXP_MODEL" ATXP atxp-tool-ok
  fi
fi
EOF

  cat >"$PARETO_BIN" <<EOF
#!/usr/bin/env bash
set -Eeuo pipefail

if [[ "\${1:-}" == "doctor" ]]; then
  shift
  exec "$PARETO_DOCTOR_BIN" "\$@"
fi

export PI_CODING_AGENT_DIR="$PI_AGENT_DIR"
export PI_OFFLINE=1
export PATH="$NODE_ROOT/bin:\$PATH"

KEYCHAIN_LANE="$keychain_lane"

have_saved_key() {
  security find-generic-password -a "$USER" -s "$PARETO_KEYCHAIN_SERVICE" -w >/dev/null 2>&1
}

save_key() {
  security add-generic-password -U -a "$USER" -s "$PARETO_KEYCHAIN_SERVICE" -w "\$1" >/dev/null
}

if [[ "\${1:-}" == "key" && "\$KEYCHAIN_LANE" == "1" ]]; then
  pasted_key=""
  if [[ -t 0 ]]; then
    printf 'Paste your Pareto API key: '
    IFS= read -r -s pasted_key
    printf '\n'
  else
    pasted_key="\$(osascript -e 'try' -e 'text returned of (display dialog "Paste your Pareto API key." default answer "" with hidden answer buttons {"Cancel", "Save"} default button "Save" with title "Connect Pareto")' -e 'on error number -128' -e 'return ""' -e 'end try')"
  fi
  if [[ -z "\$pasted_key" ]]; then
    printf 'No key entered. Create your account: %s\n' "$PLATFORM_SIGNUP_URL" >&2
    exit 1
  fi
  save_key "\$pasted_key"
  printf 'Key saved. Verifying...\n'
  exec "$PARETO_DOCTOR_BIN" pareto
fi

if [[ "\${1:-}" == "atxp" ]]; then
  shift

  if [[ "$ATXP_ENABLED" != "1" ]]; then
    printf 'ATXP was not detected during installation. Pareto is still ready.\n' >&2
    exit 1
  fi

  lane="\${1:-luna}"
  case "\$lane" in
    luna)
      model="$ATXP_LUNA_MODEL"
      ;;
    terra)
      model="$ATXP_TERRA_MODEL"
      shift
      ;;
    sol)
      model="$ATXP_SOL_MODEL"
      shift
      ;;
    *)
      model="$ATXP_LUNA_MODEL"
      ;;
  esac

  [[ "\$lane" == "luna" ]] && shift || true

  exec "$PI_BIN" \
    --provider atxp \
    --model "\$model" \
    "\$@"
fi

if [[ "\$KEYCHAIN_LANE" == "1" ]] && ! have_saved_key; then
  printf 'No Pareto API key is saved yet.\n'
  printf 'Create your account: %s\n' "$PLATFORM_SIGNUP_URL"
  printf 'Your key is issued on the platform once access is approved.\n'
  if [[ -t 0 ]]; then
    printf 'Paste it now, or press Return to exit: '
    IFS= read -r -s pasted_key
    printf '\n'
    if [[ -z "\$pasted_key" ]]; then
      printf 'Once you have it, run: pareto key\n'
      exit 1
    fi
    save_key "\$pasted_key"
  else
    printf 'Once you have it, run: pareto key\n'
    exit 1
  fi
fi

exec "$PI_BIN" \
  --provider unbiased \
  --model "$PARETO_MODEL" \
  "\$@"
EOF

  cat >"$PARETO_LAUNCHER" <<EOF
#!/usr/bin/env bash
clear
"$PARETO_BIN"
status=\$?
if [[ \$status -ne 0 ]]; then
  printf '\nPareto exited with status %s. Press Return to close.\n' "\$status"
  read -r
fi
exit "\$status"
EOF

  chmod 700 "$PARETO_BIN" "$PARETO_DOCTOR_BIN" "$PARETO_LAUNCHER"
}

run_doctor() {
  say "Testing text and tool use..."

  if [[ "${PARETO_SKIP_KEYCHAIN:-0}" == "1" ]]; then
    UNBIASED_API_KEY="$API_KEY" "$PARETO_DOCTOR_BIN" pareto
  else
    "$PARETO_DOCTOR_BIN" pareto
  fi

  if [[ "$ATXP_ENABLED" == "1" ]]; then
    say "Testing the optional ATXP connection..."
    if [[ "${PARETO_SKIP_KEYCHAIN:-0}" == "1" ]]; then
      if ! UNBIASED_API_KEY="$API_KEY" ATXP_CONNECTION_STRING="$ATXP_CREDENTIAL" "$PARETO_DOCTOR_BIN" atxp; then
        say "Warning: Pareto is ready, but the detected ATXP connection did not pass."
      fi
    elif ! "$PARETO_DOCTOR_BIN" atxp; then
      say "Warning: Pareto is ready, but the detected ATXP connection did not pass."
    fi
  fi
}

main() {
  require_macos
  resolve_key
  detect_atxp
  verify_key

  ensure_node
  store_credentials
  install_pi
  write_pi_config
  write_launchers

  if [[ "$KEY_VERIFIED" == "1" ]]; then
    run_doctor
    printf '\nPareto is ready.\n'
  elif [[ -n "$API_KEY" ]]; then
    printf '\nPareto is installed. Your key is saved but could not be verified yet.\n'
    printf 'Run this once the service is reachable: pareto doctor\n'
  else
    printf '\nPareto is installed. One step left: your API key.\n'
    printf 'Create your account: %s\n' "$PLATFORM_SIGNUP_URL"
    printf 'Pick up your key on the platform, then save it by running: pareto key\n'
  fi
  printf 'Open: %s\n' "$PARETO_LAUNCHER"
  printf 'Terminal: %s\n' "$PARETO_BIN"
  if [[ "$ATXP_ENABLED" == "1" ]]; then
    printf 'ATXP: %s atxp [luna|terra|sol]\n' "$PARETO_BIN"
  else
    printf 'ATXP: not detected; skipped without affecting Pareto.\n'
  fi
}

main "$@"
