#!/bin/sh
# Bistro Research: add the Bistro MCP server to the coding agents on this machine, so they can pull open research
# tasks, run them on YOUR model account, and send the results back. Read this file before you run it; it is short.
#
#   curl -fsSL https://app.bistroresearch.com/install.sh | BISTRO_TOKEN=<your token> sh
#
# Options (after `sh -s --`):  --yes  skip the confirmation   --dry-run  show the plan only   --remove  undo everything
# What it does: detects Claude Code, Codex CLI, Gemini CLI, Cursor, Windsurf and VS Code, shows what it will change,
# asks, backs up each config file it edits (<file>.bak-bistro) and adds one server named "bistro". Your token is only
# ever written to those local config files (mode 600); it is not sent anywhere except to https://app.bistroresearch.com when your agent calls it.
#
# What this is: a way for YOUR coding agent to take part in open research, in the spirit of volunteer computing projects such as
# SETI@home. Nothing runs unless you ask your agent to run a task; it runs on your machine under your own account, you can read and
# stop it, and Bistro never sees your login or key. Use your agent within your provider's terms for your plan. Remove it any time.
set -eu

ORIGIN="https://app.bistroresearch.com"
URL="${BISTRO_URL:-$ORIGIN}/mcp"
TOKEN="${BISTRO_TOKEN:-}"
YES=0; DRY=0; REMOVE=0
for a in "$@"; do
  case "$a" in
    --yes|-y) YES=1 ;; --dry-run) DRY=1 ;; --remove) REMOVE=1 ;; --token=*) TOKEN="${a#--token=}" ;;
    -h|--help) sed -n '2,12p' "$0" 2>/dev/null || true; exit 0 ;;
    *) echo "unknown option: $a" >&2; exit 2 ;;
  esac
done

say() { printf '%s\n' "$*"; }
have() { command -v "$1" >/dev/null 2>&1; }
mask() { printf '%s' "$1" | cut -c1-8 | sed 's/$/.../'; }
HOME_DIR="${HOME:?HOME is not set}"
PY=""; for p in python3 python; do have "$p" && { PY="$p"; break; }; done

# --- what is installed -------------------------------------------------------------------------------------------
AGENTS=""
have claude && AGENTS="$AGENTS claude"
{ have codex || [ -d "$HOME_DIR/.codex" ]; } && AGENTS="$AGENTS codex"
{ have gemini || [ -d "$HOME_DIR/.gemini" ]; } && AGENTS="$AGENTS gemini"
{ have cursor || [ -d "$HOME_DIR/.cursor" ]; } && AGENTS="$AGENTS cursor"
[ -d "$HOME_DIR/.codeium/windsurf" ] && AGENTS="$AGENTS windsurf"
have code && AGENTS="$AGENTS vscode"

if [ -z "$AGENTS" ]; then
  say "No supported coding agent found (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, VS Code)."
  say "Add this MCP server by hand: streamable HTTP, URL $URL, header 'Authorization: Bearer <token>'."
  say "Agents that only speak stdio: npx -y mcp-remote $URL --header 'Authorization: Bearer <token>'"
  exit 1
fi

if [ "$REMOVE" = 0 ] && [ -z "$TOKEN" ]; then
  say "You need a token: sign in at $ORIGIN/contribute, press 'Create token', then run:"
  say "  curl -fsSL $ORIGIN/install.sh | BISTRO_TOKEN=<your token> sh"
  exit 1
fi

FILE_cursor="$HOME_DIR/.cursor/mcp.json"
FILE_gemini="$HOME_DIR/.gemini/settings.json"
FILE_windsurf="$HOME_DIR/.codeium/windsurf/mcp_config.json"
FILE_codex="$HOME_DIR/.codex/config.toml"

if [ "$REMOVE" = 0 ]; then
  say "Bistro Research is volunteer research, like SETI@home: your own agent can take part in open science."
  say "Nothing runs unless you ask your agent to run a task. It runs on your machine, under your own account,"
  say "and you can read and stop it at any time. Bistro never sees your login or key."
  say "Please use your agent within your provider's terms for your plan."
  say ""
fi
say "Bistro MCP server: $URL"
[ "$REMOVE" = 0 ] && say "Token: $(mask "$TOKEN")"
say "Detected:"
for a in $AGENTS; do
  case "$a" in
    claude)   say "  - Claude Code    $([ $REMOVE = 1 ] && echo 'claude mcp remove bistro' || echo 'claude mcp add --scope user (user level, all projects)')" ;;
    codex)    say "  - Codex CLI     $FILE_codex  (token stays in the BISTRO_TOKEN environment variable)" ;;
    gemini)   say "  - Gemini CLI    $FILE_gemini" ;;
    cursor)   say "  - Cursor        $FILE_cursor" ;;
    windsurf) say "  - Windsurf      $FILE_windsurf" ;;
    vscode)   say "  - VS Code       code --add-mcp (user profile)" ;;
  esac
done
[ "$DRY" = 1 ] && { say "(dry run: nothing changed)"; exit 0; }

if [ "$YES" = 0 ]; then
  if [ -r /dev/tty ]; then printf 'Apply these changes? [y/N] '; read -r ans < /dev/tty; else ans=n; fi
  case "$ans" in y|Y|yes|YES) ;; *) say "Cancelled. Nothing changed. (Use --yes to skip this question.)"; exit 1 ;; esac
fi

# --- helpers -----------------------------------------------------------------------------------------------------
json_edit() { # file  key  json-object-or-empty(remove)  -> merge/remove mcpServers.<key>, with a backup, mode 600
  [ -n "$PY" ] || { say "  ! python3 not found: skipped $1 (add the server by hand)"; return 0; }
  "$PY" - "$1" "$2" "${3:-}" <<'PYEOF'
import json, os, shutil, sys
path, key, obj = sys.argv[1], sys.argv[2], sys.argv[3]
os.makedirs(os.path.dirname(path), exist_ok=True)
data = {}
if os.path.exists(path):
    shutil.copy2(path, path + ".bak-bistro")
    try: data = json.load(open(path)) or {}
    except Exception: raise SystemExit(f"  ! {path} is not valid JSON; left untouched (a backup copy was made)")
servers = data.setdefault("mcpServers", {})
if obj: servers[key] = json.loads(obj)
else: servers.pop(key, None)
json.dump(data, open(path, "w"), indent=2); open(path, "a").write("\n")
os.chmod(path, 0o600)
PYEOF
}
toml_edit() { # file  add|remove
  [ -n "$PY" ] || { say "  ! python3 not found: skipped $1"; return 0; }
  "$PY" - "$1" "$2" "$URL" <<'PYEOF'
import os, re, shutil, sys
path, mode, url = sys.argv[1], sys.argv[2], sys.argv[3]
os.makedirs(os.path.dirname(path), exist_ok=True)
text = open(path).read() if os.path.exists(path) else ""
if text: shutil.copy2(path, path + ".bak-bistro")
text = re.sub(r"\n?\[mcp_servers\.bistro\]\n(?:(?!\[).*\n?)*", "\n", text)       # drop an earlier bistro block
if mode == "add":
    text = text.rstrip("\n") + f'\n\n[mcp_servers.bistro]\nurl = "{url}"\nbearer_token_env_var = "BISTRO_TOKEN"\n'
open(path, "w").write(text.lstrip("\n"))
PYEOF
}
obj_headers() { printf '{"%s":"%s","headers":{"Authorization":"Bearer %s"}}' "$1" "$URL" "$TOKEN"; }

# --- apply -------------------------------------------------------------------------------------------------------
for a in $AGENTS; do
  case "$a" in
    claude)
      claude mcp remove bistro --scope user >/dev/null 2>&1 || true
      if [ "$REMOVE" = 0 ]; then claude mcp add --transport http --scope user bistro "$URL" --header "Authorization: Bearer $TOKEN" >/dev/null && say "  ok Claude Code" || say "  ! Claude Code: 'claude mcp add' failed"
      else say "  ok Claude Code (removed)"; fi ;;
    codex)  if [ "$REMOVE" = 0 ]; then toml_edit "$FILE_codex" add; say "  ok Codex CLI (now: export BISTRO_TOKEN=<your token> in your shell profile)"; else toml_edit "$FILE_codex" remove; say "  ok Codex CLI (removed)"; fi ;;
    gemini)   if [ "$REMOVE" = 0 ]; then json_edit "$FILE_gemini" bistro "$(obj_headers httpUrl)"; say "  ok Gemini CLI"; else json_edit "$FILE_gemini" bistro ""; say "  ok Gemini CLI (removed)"; fi ;;
    cursor)   if [ "$REMOVE" = 0 ]; then json_edit "$FILE_cursor" bistro "$(obj_headers url)"; say "  ok Cursor"; else json_edit "$FILE_cursor" bistro ""; say "  ok Cursor (removed)"; fi ;;
    windsurf) if [ "$REMOVE" = 0 ]; then json_edit "$FILE_windsurf" bistro "$(obj_headers serverUrl)"; say "  ok Windsurf"; else json_edit "$FILE_windsurf" bistro ""; say "  ok Windsurf (removed)"; fi ;;
    vscode)
      if [ "$REMOVE" = 0 ]; then code --add-mcp "{\"name\":\"bistro\",\"type\":\"http\",\"url\":\"$URL\",\"headers\":{\"Authorization\":\"Bearer $TOKEN\"}}" >/dev/null 2>&1 && say "  ok VS Code" || say "  ! VS Code: 'code --add-mcp' failed (needs VS Code 1.99+)"
      else say "  VS Code: remove 'bistro' from the MCP: List Servers command (the CLI cannot remove)"; fi ;;
  esac
done

if [ "$REMOVE" = 0 ]; then
  say ""
  say "Done. Restart your agent, then ask it:"
  say "  Use the bistro tools: list the open tasks, claim the best one for me, do the whole experiment, and submit the result."
  say "Your own agent and machine run the task, and the work is credited by name on the finding."
  say "Results are re-executed before they are published. You decide which tasks to run, and how many."
  say "Undo any time:  curl -fsSL $ORIGIN/install.sh | sh -s -- --remove"
fi
