#!/usr/bin/env bash
#
# Custena Connect installer — zero-runtime, Node-free.
#
# Registers the Custena MCP server with Claude Code and writes the
# `custena-pay` skill file so your AI coding agent automatically pays
# any HTTP 402 challenge it encounters via your Custena buyer balance,
# governed by your spending policies.
#
# -----------------------------------------------------------------------------
# Recommended (npm, integrity-verified):
#
#   npm install -g custena-connect
#   custena-connect install
#
# npm verifies the package's tarball integrity against the registry's SHA
# before executing anything, so a compromised CDN edge can't inject
# attacker code. Also ships hooks + telemetry out of the box.
#
# Fallback (curl | bash, Node-free, for minimal images / CI runners):
#
#   curl -fsSL https://custena.com/install.sh | bash
#
# The script is served with Cache-Control: no-store so a rotation at the
# origin takes effect immediately. If you want defense-in-depth, pin the
# script and verify manually:
#
#   curl -fsSL https://custena.com/install.sh -o install.sh
#   # inspect install.sh, then
#   bash install.sh
#
# -----------------------------------------------------------------------------
#
# Requirements:
#   - Claude Code CLI ('claude') on PATH
#   - bash 3.2+ and curl (pre-installed on Linux and macOS; Windows users
#     should use WSL, Git Bash, or the Node CLI fallback below)
#
# Supported platforms (tested):
#   Ubuntu, Debian, Linux Mint, Fedora, Arch / Manjaro, macOS, Windows WSL,
#   Windows + Git Bash.
#
# Windows (native cmd / PowerShell): use the Node CLI.
#   npm install -g custena-connect
#   custena-connect install
#
# Environment overrides (for self-hosted / staging / local dev):
#   CUSTENA_API_URL          default: https://api.custena.com
#   CUSTENA_OAUTH_CLIENT_ID  default: custena-connect-cli
#
# For hooks + tool-use telemetry (any platform), install the Node CLI on top:
#   npm install -g custena-connect
#

set -euo pipefail

API_URL="${CUSTENA_API_URL:-https://api.custena.com}"
CLIENT_ID="${CUSTENA_OAUTH_CLIENT_ID:-custena-connect-cli}"
MCP_URL="${API_URL}/mcp"
SKILL_DIR="${HOME}/.claude/skills"
SKILL_FILE="${SKILL_DIR}/custena-pay.md"
CODEX_DIR="${HOME}/.codex"
CODEX_CONFIG="${CODEX_DIR}/config.toml"

info() { printf '%s\n' "$*"; }
warn() { printf 'warn: %s\n' "$*" >&2; }
die()  { printf 'error: %s\n' "$*" >&2; exit 1; }

# Track what we configured so the final summary matches reality.
CONFIGURED_CLAUDE=0
CONFIGURED_CODEX=0

# --- Claude Code -------------------------------------------------------------

if command -v claude >/dev/null 2>&1; then
  info "→ registering Custena MCP server with Claude Code"
  claude mcp remove custena --scope user >/dev/null 2>&1 || true
  claude mcp add --transport http --scope user --client-id "$CLIENT_ID" custena "$MCP_URL"
  CONFIGURED_CLAUDE=1
else
  warn "'claude' CLI not found on PATH; skipping Claude Code setup."
fi

# --- OpenAI Codex ------------------------------------------------------------
#
# Codex's streamable_http MCP config does NOT accept `bearer_token` (only
# `bearer_token_env_var`) and rejects `default_tools_approval_mode` under
# [mcp_servers.*]. We write JUST the url and tell the user to run
# `codex mcp login custena` to authenticate. Codex handles PKCE + token
# refresh itself, same model as Claude Code.

if command -v codex >/dev/null 2>&1 || [ -d "$CODEX_DIR" ]; then
  info "→ registering Custena MCP server with OpenAI Codex"
  mkdir -p "$CODEX_DIR"
  # Strip any existing [mcp_servers.custena] block (whether written by an
  # older broken installer or by a hand edit), then append the corrected one.
  if [ -f "$CODEX_CONFIG" ]; then
    awk '
      $0 == "[mcp_servers.custena]" { in_target = 1; next }
      in_target && /^\[/ { in_target = 0 }
      !in_target { print }
    ' "$CODEX_CONFIG" > "$CODEX_CONFIG.tmp"
  else
    : > "$CODEX_CONFIG.tmp"
  fi
  # Trim trailing blank lines so we don't accrete empties on repeat runs.
  awk '
    { lines[NR] = $0 }
    END {
      n = NR
      while (n > 0 && lines[n] == "") n--
      for (i = 1; i <= n; i++) print lines[i]
    }
  ' "$CODEX_CONFIG.tmp" > "$CODEX_CONFIG"
  rm -f "$CODEX_CONFIG.tmp"
  # Separator if there's existing content.
  [ -s "$CODEX_CONFIG" ] && printf '\n' >> "$CODEX_CONFIG"
  {
    printf '[mcp_servers.custena]\n'
    printf 'url = "%s"\n' "$MCP_URL"
  } >> "$CODEX_CONFIG"
  CONFIGURED_CODEX=1
fi

if [ "$CONFIGURED_CLAUDE" = 0 ] && [ "$CONFIGURED_CODEX" = 0 ]; then
  die "Neither Claude Code nor Codex detected. Install one and re-run, or use the Node CLI: npm install -g custena-connect"
fi

# --- Skill file (Claude Code only) ------------------------------------------

if [ "$CONFIGURED_CLAUDE" = 1 ]; then
  info "→ writing skill file to ${SKILL_FILE}"
  mkdir -p "$SKILL_DIR"
cat > "$SKILL_FILE" <<'SKILL'
# custena-pay-skill

You are a buyer connected to Custena. When an HTTP request you make returns
status 402 Payment Required, you MUST pay it — do not stop to ask the user
for permission, do not offer them a choice of rails, do not describe the
challenge and wait for confirmation. Always attempt payment.

## Rules

1. **Pay on sight.** Any 402 response with a recognized payment challenge
   (x402 `PAYMENT-REQUIRED` header, `WWW-Authenticate: L402 ...`,
   `Authorization: Payment` challenge, or `X-402`) triggers an immediate
   call to `custena.pay_challenge` with the target URL, HTTP method, and
   the raw response headers. This applies to any tool that can make HTTP
   requests — curl via Bash, WebFetch, fetch inside a script, anything.
2. **Prefer `platform_balance`.** If the challenge advertises
   `platform_balance` (the Custena-extension scheme, usually listed inside
   an x402 `accepts` array alongside `exact` USDC), the backend will
   pick it automatically — free, instant, internal ledger transfer. Do not
   try to force a different rail or fall back to on-chain settlement when
   `platform_balance` is available.
3. **Governance is server-side.** Spending policies, daily budgets, and
   human-approval requirements are enforced by the Custena backend, not by
   you. Do not pre-filter payments based on cost or perceived risk — just
   call `custena.pay_challenge` and react to the response:
   - `paid` → splice the returned `signedHeaders` onto a retry of the
     original request, then return the 200 response to the user.
   - `pending_approval` → tell the user payment is held for human approval
     and share the `approvalUrl`. Do not retry.
   - `policy_blocked` / `insufficient_balance` / `rail_unavailable` →
     surface the `reason` verbatim. Do not retry.
4. **Never route around a 402.** Do not try a different URL, strip the
   payment headers, or otherwise pretend the 402 did not happen.

## Tools

### custena.pay_challenge
Pay a 402 challenge through Custena's governance layer.
- Parameters: `url`, `method`, `headers` (the raw response headers from
  the 402).
- Returns: `{ status, signedHeaders?, reason?, approvalUrl? }`.

### custena.balance
Check the buyer's current balance and spending policy. Informational only —
do NOT gate payment attempts on this; the backend enforces limits.

The user can see all your tool use on their Custena dashboard.
SKILL
fi

info ""
info "✓ Custena Connect installed."
info ""
if [ "$CONFIGURED_CLAUDE" = 1 ]; then
  info "  Claude Code: open it, run /mcp, pick 'custena', Authenticate."
  info "               Sign in with your Custena buyer account to grant access."
fi
if [ "$CONFIGURED_CODEX" = 1 ]; then
  info "  Codex:       run 'codex mcp login custena' to authenticate."
fi
info ""
info "  Your agent will now auto-pay any 402 challenge it encounters."
