Ctrl K
ComunidadGuías…

darkmoon.sh

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
set -euo pipefail

SERVICE="opencode"
APP_BIN="opencode"

# ------------------------------------------------------------
# Détection docker compose (plugin vs legacy)
# ------------------------------------------------------------
# Prefer the Compose v2 plugin (`docker compose`): the modern docker-compose.yml
# uses Compose-Spec features (e.g. the top-level `name:` key) that the legacy
# Python `docker-compose` v1 rejects ("'name' does not match any of the regexes").
# Only fall back to the legacy binary when the v2 plugin is genuinely unavailable.
if docker compose version >/dev/null 2>&1; then
  DC=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then
  DC=(docker-compose)
else
  echo "❌ Neither 'docker compose' (v2 plugin) nor 'docker-compose' (legacy) is available." >&2
  echo "   Install the Docker Compose v2 plugin: https://docs.docker.com/compose/install/" >&2
  exit 1
fi

# ------------------------------------------------------------
# TTY detection (pipe-safe)
# ------------------------------------------------------------
if [[ -t 0 ]]; then
  TTY_FLAGS=(-it)
else
  TTY_FLAGS=(-T)
fi

# ------------------------------------------------------------
# --log mode
# ------------------------------------------------------------
if [[ "${1:-}" == "--log" ]]; then
  if [[ $# -lt 2 ]]; then
    echo "Usage: $0 --log <session_id>"
    exit 1
  fi

  SESSION_ID="$2"

  exec "${DC[@]}" exec "${TTY_FLAGS[@]}" "$SERVICE" \
    bash -lc 'exec "$1" "$2"' bash "darkmoon-cli" "$SESSION_ID"
fi

# ------------------------------------------------------------
# Preflight: fail fast with a clear message if the configured
# on-prem LLM endpoint is unreachable from INSIDE the container.
# Without this, `opencode run` hangs silently — no output, no
# error, no logs — when ANTHROPIC_BASE_URL / OPENCODE_LOCAL_BASE_URL
# cannot be reached (see issue #28).
# ------------------------------------------------------------
preflight_provider_check() {
  local res
  res="$("${DC[@]}" exec -T "$SERVICE" bash -c '
    url="${ANTHROPIC_BASE_URL:-}"; mode="Anthropic (install.sh option 3)"
    if [ -z "$url" ]; then url="${OPENCODE_LOCAL_BASE_URL:-}"; mode="Local (install.sh option 2)"; fi
    [ -z "$url" ] && exit 0                         # cloud provider or none -> nothing to probe
    proto="${url%%://*}"; rest="${url#*://}"; hostport="${rest%%/*}"
    host="${hostport%%:*}"; port="${hostport##*:}"
    [ "$host" = "$port" ] && port=""                # no explicit port in URL
    [ -z "$port" ] && { [ "$proto" = https ] && port=443 || port=80; }
    # Pure-bash TCP connect (no curl in the image); detects the silent-hang case.
    if timeout 5 bash -c "exec 3<>/dev/tcp/$host/$port" 2>/dev/null; then exit 0; fi
    printf "UNREACHABLE|%s|%s" "$mode" "$url"
    exit 3
  ' 2>/dev/null)" || true

  case "${res:-}" in
    UNREACHABLE\|*)
      local rest="${res#UNREACHABLE|}"
      local url="${rest#*|}"
      local mode="${rest%%|*}"
      {
        echo ""
        echo "❌ LLM endpoint not reachable from inside the Darkmoon container:"
        echo "      ${url}"
        echo "      (provider: ${mode})"
        echo ""
        echo "   The agent cannot run and would otherwise produce no output. Check:"
        echo "   • Network: the URL must be reachable FROM THE CONTAINER, not just your host."
        echo "     'localhost' / '127.0.0.1' point to the container itself — use the host LAN IP"
        echo "     or 'host.docker.internal', and confirm the server is actually running."
        echo "   • Protocol: option [3] expects an Anthropic Messages API endpoint (/v1/messages)."
        echo "     If your endpoint is OpenAI-compatible (e.g. blackbox, LiteLLM, Ollama), re-run"
        echo "     install.sh and choose [2] Local instead."
        echo ""
      } >&2
      exit 3
      ;;
  esac
}
preflight_provider_check

# ------------------------------------------------------------
# Default behaviour
# ------------------------------------------------------------
if [[ $# -eq 0 ]]; then
  # Mode interactif → TUI
  exec "${DC[@]}" exec "${TTY_FLAGS[@]}" "$SERVICE" \
    bash -lc 'exec "$1"' bash "$APP_BIN"
elif [[ "${1:0:1}" == "-" ]]; then
  # Le 1er argument est un flag (court ou long : -s, -c, -m,
  # --help, --version, --session, …). On passe la main directement
  # à `opencode` SANS la sous-commande `run`. C'est indispensable
  # pour -s/--session (continuer une session) et -c/--continue :
  # `opencode run` exigerait en plus un message positionnel et
  # échouerait avec "You must provide a message or a command".
  # Pour relancer une session en mode one-shot, mettre le message
  # en 1er : ./darkmoon.sh "mon prompt" -s <session_id>
  exec "${DC[@]}" exec "${TTY_FLAGS[@]}" "$SERVICE" \
    bash -lc 'app="$1"; shift; exec "$app" "$@"' bash "$APP_BIN" "$@"
else
  # Arguments positionnels = prompt one-shot. opencode exige la
  # sous-commande `run`, sinon il interprète la chaîne comme un cwd
  # et échoue avec "Failed to change directory to /<prompt>".
  #
  # Guard (ASCIT31/Dark-Moon-Front-API#39): a head-less one-shot run cannot use
  # opencode's free tier (opencode/big-pickle), the no-provider fallback written
  # by apply-settings.sh. opencode refuses it with "OpenCode's free tier can only
  # be used from within OpenCode", which reads as a cryptic failure. Detect that
  # model up front and say what to configure instead.
  _dm_model="$("${DC[@]}" exec -T "$SERVICE" python3 -c 'import json,os
p="/root/.config/opencode/opencode.json"
if not os.path.exists(p): p=os.path.expanduser("~/.config/opencode/opencode.json")
try:
    print(json.load(open(p)).get("model",""))
except Exception:
    print("")' 2>/dev/null | tr -d "[:space:]")"
  case "$_dm_model" in
    opencode/*)
      {
        echo ""
        echo "❌ No LLM provider is configured, so Darkmoon fell back to opencode's free"
        echo "   tier (model: ${_dm_model}). That tier CANNOT run head-less: opencode refuses"
        echo "   it with \"OpenCode's free tier can only be used from within OpenCode\"."
        echo ""
        echo "   Configure a real model, then re-run your campaign:"
        echo "   • Interactive:   ./install.sh --init"
        echo "   • Cloud  (e.g.): sudo ./install.sh --provider anthropic --model claude-opus-4-6 --api-key sk-ant-..."
        echo "   • Local  (e.g.): sudo ./install.sh --local --local-engine ollama --local-url http://HOST:11434/v1 --local-model <model>"
        echo ""
      } >&2
      exit 3
      ;;
  esac
  exec "${DC[@]}" exec "${TTY_FLAGS[@]}" "$SERVICE" \
    bash -lc 'app="$1"; shift; exec "$app" run "$@"' bash "$APP_BIN" "$@"
fi