#!/usr/bin/env bash
# Run an agent made in the Sheaf builder inside OpenSheaf on this machine.
#
#   export SHEAF_KEY=sk_pod_...        # your Sheaf key (the builder shows this line with your agent's id)
#   curl -sSf https://sheaf.one/opensheaf/run-agent.sh | bash -s -- <agent-id> "the task"
#
# OpenSheaf is Sheaf's own build of the open-source OpenShell sandbox runtime, with the Sheaf check
# inside it. What this script does, on your machine:
#   1. fetches the agent's program from Sheaf (the same program the builder shows under "The program");
#   2. writes a sandbox policy where api.sheaf.one is the only address the program can reach, and the
#      Sheaf check (sheaf/gate) reads every tool call before it leaves the sandbox;
#   3. starts a local OpenSheaf gateway if none is running, and creates the sandbox under it;
#   4. runs the program there with your task. Harmless tools run. A tool call that needs a person is
#      held inside the sandbox and never leaves; you release or refuse it in the Sheaf console.
# The sandbox is deleted when the run ends. Every step is on the run's record in the console.
#
# Needs: Docker, curl, python3, openssl, and a build of OpenSheaf (see the message below if it is missing).
set -euo pipefail
AGENT_ID="${1:?usage: run-agent.sh <agent-id> \"the task\"}"; shift || true
TASK="${*:-}"
API="${SHEAF_URL:-https://api.sheaf.one}"
SHEAF_HOST="$(printf '%s' "$API" | sed -E 's#^https?://##; s#/.*$##')"
KEY="${SHEAF_KEY:-${SHEAF_API_KEY:-}}"
CONF="${XDG_CONFIG_HOME:-$HOME/.config}/opensheaf"
GATEWAY_PORT="${OPENSHEAF_PORT:-18100}"
HEALTH_PORT="${OPENSHEAF_HEALTH_PORT:-18101}"
GATEWAY="${OPENSHEAF_GATEWAY:-http://127.0.0.1:$GATEWAY_PORT}"
SUPERVISOR_IMAGE="${OPENSHEAF_SUPERVISOR_IMAGE:-opensheaf/supervisor:dev}"
say(){ printf '\n\033[1m%s\033[0m\n' "$*"; }

# ── where OpenSheaf is ──
HOME_DIR="${OPENSHEAF_HOME:-}"
if [ -z "$HOME_DIR" ]; then
  for d in "$HOME/opensheaf" "$HOME/IdeaProjects/opensheaf" "$HOME/src/opensheaf"; do
    [ -x "$d/target/debug/openshell" ] || [ -x "$d/target/release/openshell" ] && { HOME_DIR="$d"; break; }
  done
fi
find_bin(){ # $1 = name
  if [ -n "$HOME_DIR" ]; then
    for b in "$HOME_DIR/target/release/$1" "$HOME_DIR/target/debug/$1"; do [ -x "$b" ] && { echo "$b"; return; }; done
  fi
  command -v "$1" 2>/dev/null || true
}
CLI_BIN="$(find_bin openshell)"; GATEWAY_BIN="$(find_bin openshell-gateway)"
if [ -z "$CLI_BIN" ] || ! docker image inspect "$SUPERVISOR_IMAGE" >/dev/null 2>&1; then
  cat <<MSG
OpenSheaf is not built on this machine yet. Until the one-line installer ships, build it from the
repository (Linux with Docker; Rust via rustup):
  git clone <the OpenSheaf repository> ~/opensheaf && cd ~/opensheaf
  cargo build -p openshell-gateway -p openshell-cli
  cargo build --release -p openshell-supervisor
  mkdir -p deploy/docker/.build/prebuilt-binaries/amd64 && cp target/release/openshell-supervisor deploy/docker/.build/prebuilt-binaries/amd64/
  docker build -f examples/sheaf-gate/Dockerfile.supervisor-local -t opensheaf/supervisor:dev .
Then run this again. (OPENSHEAF_HOME=<dir> if you cloned it somewhere else.)
Without OpenSheaf, the same agent runs on Sheaf: press "Run it now" in the builder.
MSG
  exit 1
fi
for c in docker curl python3 openssl; do command -v "$c" >/dev/null 2>&1 || { echo "missing: $c"; exit 1; }; done
if [ -z "$KEY" ]; then
  if [ -t 0 ] || [ -r /dev/tty ]; then
    printf 'Your Sheaf key (sk_pod_...): '; read -rs KEY </dev/tty; echo
  fi
  [ -n "$KEY" ] || { echo "Set SHEAF_KEY to your Sheaf key first: export SHEAF_KEY=sk_pod_..."; exit 1; }
fi
if [ -z "$TASK" ]; then
  printf 'The task for this run: '; read -r TASK </dev/tty
fi

WORK="$(mktemp -d "${TMPDIR:-/tmp}/opensheaf-agent.XXXXXX")"
GATEWAY_PID=""
cleanup(){
  local status=$?
  trap - EXIT
  [ -n "${SANDBOX:-}" ] && [ "${KEEP:-0}" != "1" ] && "${CLI[@]}" sandbox delete "$SANDBOX" >/dev/null 2>&1 || true
  if [ -n "$GATEWAY_PID" ] && [ "${KEEP:-0}" != "1" ]; then kill "$GATEWAY_PID" 2>/dev/null || true; wait "$GATEWAY_PID" 2>/dev/null || true; fi
  [ "${KEEP:-0}" = "1" ] && echo "kept: gateway $GATEWAY, sandbox ${SANDBOX:-}, files in $WORK" || rm -rf "$WORK"
  exit $status
}
trap cleanup EXIT

say "1. The agent's program, from Sheaf"
HTTP=$(curl -sS -m 60 -o "$WORK/agent.py" -w '%{http_code}' "$API/api/v1/os/agents/$AGENT_ID/code" -H "Authorization: Bearer $KEY")
if [ "$HTTP" != "200" ]; then
  echo "Sheaf did not hand over the program (HTTP $HTTP): $(head -c 300 "$WORK/agent.py")"; exit 1
fi
NAME=$(sed -n '2s/^"""\([^ ]*\) - generated.*/\1/p' "$WORK/agent.py" | tr -c 'A-Za-z0-9._\n-' '-' | cut -c1-40)
NAME="${NAME:-agent}"
echo "agent.py: $(wc -l <"$WORK/agent.py") lines, the same program the builder shows."

say "2. The sandbox policy: Sheaf is the only door, and the Sheaf check reads every tool call"
curl -sSf -m 30 -o "$WORK/policy.tmpl" https://sheaf.one/opensheaf/policy.yaml
sed -e "s#__SHEAF_KEY__#$KEY#g" -e "s#__SHEAF_BASE__#$API#g" -e "s#__SHEAF_HOST__#$SHEAF_HOST#g" "$WORK/policy.tmpl" > "$WORK/policy.yaml"
echo "policy.yaml: $SHEAF_HOST only; middleware sheaf/gate on every request to it."

say "3. The OpenSheaf gateway"
CLI=(env -u OPENSHELL_SANDBOX_POLICY "$CLI_BIN" --gateway-endpoint "$GATEWAY")
if curl -fsS -m 2 "http://127.0.0.1:$HEALTH_PORT/healthz" >/dev/null 2>&1; then
  echo "using the gateway already running at $GATEWAY"
else
  [ -x "$GATEWAY_BIN" ] || { echo "no OpenSheaf gateway at $GATEWAY and no gateway binary to start one"; exit 1; }
  mkdir -p "$CONF/jwt"; chmod 700 "$CONF"
  if [ ! -s "$CONF/jwt/signing.pem" ]; then
    openssl genpkey -algorithm ed25519 -out "$CONF/jwt/signing.pem" >/dev/null 2>&1
    openssl pkey -in "$CONF/jwt/signing.pem" -pubout -out "$CONF/jwt/public.pem" >/dev/null 2>&1
    echo opensheaf-local > "$CONF/jwt/kid"
  fi
  cat > "$CONF/gateway.toml" <<TOML
[openshell]
version = 2

[openshell.gateway.auth]
allow_unauthenticated_users = true

[openshell.gateway.gateway_jwt]
signing_key_path = "$CONF/jwt/signing.pem"
public_key_path = "$CONF/jwt/public.pem"
kid_path = "$CONF/jwt/kid"
gateway_id = "opensheaf-local"

[openshell.drivers.docker]
# Our own label namespace, so an OpenShell gateway on the same host leaves these sandboxes alone.
sandbox_label = "opensheaf"
supervisor_image = "$SUPERVISOR_IMAGE"
TOML
  env -u OPENSHELL_DRIVERS -u OPENSHELL_COMPUTE_DRIVER "$GATEWAY_BIN" --compute-driver docker --config "$CONF/gateway.toml" \
    --bind-address 127.0.0.1 --port "$GATEWAY_PORT" --health-port "$HEALTH_PORT" --metrics-port 0 \
    --log-level "${LOG_LEVEL:-warn}" --disable-tls --db-url "sqlite://$CONF/gateway.db" > "$CONF/gateway.log" 2>&1 &
  GATEWAY_PID=$!
  for _ in $(seq 1 60); do
    kill -0 "$GATEWAY_PID" 2>/dev/null || { echo "the gateway exited; see $CONF/gateway.log"; exit 1; }
    curl -fsS "http://127.0.0.1:$HEALTH_PORT/healthz" >/dev/null 2>&1 && break
    sleep 1
  done
  echo "gateway up at $GATEWAY (supervisor image $SUPERVISOR_IMAGE, state in $CONF)"
fi

# OpenShell allows 19 characters in a sandbox name.
SANDBOX="sheaf-$(echo "$NAME" | tr 'A-Z' 'a-z' | cut -c1-8 | sed 's/-*$//')-$(date +%s | tail -c 4)"
say "4. Running $NAME in sandbox $SANDBOX"
echo "task: $TASK"
echo
"${CLI[@]}" sandbox delete "$SANDBOX" >/dev/null 2>&1 || true
RC=0
# The key goes in as an environment variable: the program sends it to Sheaf, and Sheaf is the only
# place it can send anything. The sandbox starts empty, so the program is copied in over exec.
"${CLI[@]}" sandbox create --name "$SANDBOX" --from python:3.12-slim --policy "$WORK/policy.yaml" \
  --env "SHEAF_KEY=$KEY" --no-credential-warnings --no-auto-providers --no-tty --detach -- sleep infinity >/dev/null || RC=$?
if [ "$RC" = "0" ]; then
  for _ in 1 2 3 4 5; do
    if "${CLI[@]}" sandbox exec --name "$SANDBOX" --no-tty -- sh -c 'cat > /sandbox/agent.py' < "$WORK/agent.py" 2>/dev/null; then break; fi
    sleep 2
  done
fi
if [ "$RC" = "0" ]; then
  "${CLI[@]}" sandbox exec --name "$SANDBOX" --no-tty -- python3 /sandbox/agent.py "$TASK" </dev/null || RC=$?
fi
if [ "$RC" != "0" ]; then
  say "The sandbox did not run to the end (exit $RC)."
  echo "If OpenSheaf printed an error above, that is the cause. The run, if it started, is under https://sheaf.one/console.html"
  exit "$RC"
fi

say "Done."
echo "OpenSheaf kept the agent in its room: it could reach Sheaf and nothing else, and every tool call was"
echo "checked before it left the room. Anything held is waiting for you under Held in the Sheaf console."
echo "The run, step by step: https://sheaf.one/console.html"
