external-agents

Único servidor MCP que enruta a través de múltiples proveedores de LLM para menor costo, menos límites de tasa y más tokens disponibles.

Documentación

@mrrlin-dev/external-agents — Servidor MCP Multi-LLM

Install to Claude Code Install to Codex One-command install npm

Dale a tu agente de codificación un grupo de 40+ modelos más baratos a los que delegar trabajo. Reduce tu factura 10-100×.

Registro de cambios: CHANGELOG.md

architecture: primary agent → external-agents → six provider buckets, one pool of tokens

El problema que esto resuelve

Usas Claude Code o Codex todo el día. La mayor parte de lo que hace — leer archivos para responder una pregunta, ejecutar una suite de pruebas, renombrar un símbolo en 30 archivos, resumir un diff — no necesita un modelo de frontera. Pero todo se factura a precios de frontera, en una sola cuenta, contra un solo límite de tasa.

Mientras tanto, probablemente ya tienes un puñado de cuotas separadas y mayormente gratuitas sin usar: una clave de Google AI Studio, una clave de Groq, los modelos :free de OpenRouter, cualquier CLI agéntico en el que hayas iniciado sesión.

external-agents convierte todo eso en un solo grupo al que tu agente principal puede despachar. Elige un proveedor saludable por llamada, hace round-robin entre los buckets, y cuando uno devuelve 429 cambia a un proveedor diferente y respeta el tiempo real de reinicio de ese proveedor.

También es un sustrato limpio para paneles estilo LLM-Council: una llamada a pick_agents te da N elecciones de N proveedores distintos, para que un jurado de modelos no sea secretamente el mismo modelo cuatro veces.


Para qué está optimizado esto

Cada decisión de diseño aquí responde a cinco objetivos. Son la razón por la que existe el proyecto, y son medibles — cuando un cambio empeora uno de estos números, eso es una regresión, sin importar qué más haya mejorado.

  1. Un asiento que se entrega está vivo. pick devolviendo un agente es una afirmación de que un despacho hacia él puede tener éxito ahora mismo. Un agente que nunca ha respondido no debe ofrecerse como si pudiera.
  2. Un prompt que se envía cabe. El techo real del asiento elegido — ventana de contexto, tokens por minuto, lo que quede de la ventana actual — tiene que contener todo el prompt antes de que salga. Un HTTP 413 o un 429 por límite de tokens es un error de enrutamiento, no mala suerte.
  3. Más éxitos, menos fallos. Un despacho fallido es una ronda de trabajo tirada a la basura, y dentro de un panel de consenso es una voz perdida — la ejecución obtiene un veredicto más pobre, no solo uno más lento.
  4. La carga se distribuye entre los modelos vivos de un nivel. Ninguna clave carga con todo un nivel mientras sus hermanas están inactivas, y un agente roto no debe reofrecerse más rápido que uno funcional solo porque fallar es rápido.
  5. Los límites del proveedor se gastan, no se admiran. Un nivel gratuito que se reinicia sin usar cada noche es tokens tirados a la basura. El grupo debería acercarse al techo de cada bucket en lugar de quedarse al uno por ciento de él.

Los objetivos 1, 2 y 5 necesitan todos lo mismo, y vale la pena decirlo claramente: el proveedor te da la respuesta en cada respuesta individual. x-ratelimit-limit-tokens, x-ratelimit-remaining-tokens, x-ratelimit-reset-* — el techo real para tu clave y cuánto queda de él, tanto en éxito como en fallo. Una entrada de registro es una suposición sobre eso; un encabezado de respuesta es una medición. Así que la regla que sigue este código base es: la observación vence a la declaración, y un límite descubierto al ser rechazado es un límite que se registró demasiado tarde.

Estos números se observan en lugar de afirmarse — ver Observando el grupo para detectar regresiones.


🚀 Configuración en 2 minutos

curl -fsSL https://raw.githubusercontent.com/mrrlin-dev/external-agents/main/install.sh | bash

El script instala el paquete, registra el servidor MCP con Claude Code y/o Codex (el que encuentre), y abre un panel local donde pegas las claves de los proveedores en línea:

paste-and-save walkthrough — banner → password input → Save → confirmation

Luego reinicia tu cliente MCP. Tu agente ahora tiene las herramientas.

O conéctalo manualmente (tres comandos)
npm install -g @mrrlin-dev/external-agents

# Register with whichever host(s) you use
claude mcp add external-agents external-agents-mcp
codex  mcp add external-agents -- external-agents-mcp

# Set up keys
external-agents ui        # opens http://127.0.0.1:4711

Requiere Node ≥ 20. Funciona en macOS y Linux; Windows mediante WSL.

¿Cuánto tengo que configurar antes de que esto sea útil?

Nada, si ya has iniciado sesión en una CLI agéntica. Las entradas respaldadas por una suscripción que ya tienes — claude, codex, cursor-agent, ollama, opencode, kiro-cli, agy — no necesitan ninguna clave API; son utilizables en el momento en que el binario está en tu PATH y has iniciado sesión.

Todo lo demás es incremental. Cada clave que pegas enciende más del grupo, y ninguna de ellas es obligatoria:

Pega estoObtienesCosto
GEMINI_API_KEY (Google AI Studio)Gemini FlashNivel gratuito, sin tarjeta
GROQ_API_KEYLlama 3.3 70B, gpt-oss 120B/20B, Llama 3.1 8BNivel gratuito, sin tarjeta
OPENROUTER_API_KEY5 modelos :free incl. Nemotron UltraNivel gratuito, sin tarjeta
DEEPSEEK_API_KEYDeepSeek v4 flash + v4 pro (razonador)Prepago, necesita una recarga pequeña

Registrarse en cada uno toma alrededor de un minuto. El panel enlaza directamente a la página correcta y tiene un cuadro de pegado junto a ella.


Lo que obtiene tu agente

Dos herramientas MCP, disponibles automáticamente después de la configuración:

  • dispatch(agent_id, prompt) — ejecuta un prompt en un miembro específico del grupo. Reintenta automáticamente en un proveedor diferente si el primero está limitado por tasa, y respeta el tiempo de reinicio real del proveedor en lugar de un valor predeterminado inventado de 1 hora.

    Pasa cwd (un directorio existente — un worktree de git, por ejemplo) y una CLI directa inspeccionará y editará archivos en el lugar. cwd no otorga acceso al sistema de archivos a modelos basados en HTTP; dales contexto con files en su lugar. Cuando cwd es un repositorio git, la lista de files que regresa es el conjunto cambiado por git, no todo el árbol.

    Un cwd que es un repositorio git también recibe un encabezado de procedencia corto antepuesto al prompt — rama, commit y asunto, desviación respecto a upstream, si el worktree está sucio — y los mismos hechos regresan a ti como repo. Esto es lo que evita que un trabajador apuntado a un checkout obsoleto produzca un informe preciso sobre código que ya no está y que se lea como una alucinación. Es de solo lectura y nunca hace fetch. Si quieres que eso sea una condición previa estricta en lugar de una nota, external-agents dispatch --require-base origin/main se niega a despachar por completo cuando el checkout no contiene ese ref — salida 6 para un checkout incorrecto, 2 para un error de uso. Estar adelante del ref está bien; la base es un piso, no una verificación de igualdad.

  • pick_agents(n, min_distinct_providers) — pide N agentes saludables de N proveedores diferentes. Este es el primitivo para fan-out: revisión tipo jurado, verificaciones de autoconsistencia, tu propio bucle de consenso.

Ambas herramientas llevan la guía de enrutamiento a continuación en sus descripciones, para que cualquier modelo que lea el esquema en tiempo de ejecución adopte el mismo sesgo.

Todo también está disponible desde la terminal — external-agents pick, dispatch, status, stats, audit — si prefieres scriptearlo en lugar de pasar por MCP. Ejecuta external-agents sin argumentos para la lista completa.


Qué hay en el grupo

28 entradas incluidas, 25 habilitadas de fábrica. El resto son mejoras de pago que permanecen apagadas hasta que optes por ellas.

ProveedorEntradasLo que necesitas
Google AI StudioGemini 3.6 Flash; Gemini 3.1 Pro (apagado — sin nivel gratuito)GEMINI_API_KEY, nivel gratuito
Groqgpt-oss 120B, gpt-oss 20B, Qwen3.6 27BGROQ_API_KEY, nivel gratuito
OpenRouter5 modelos :free incl. Nemotron Ultra y Super, Gemma 4, gpt-oss 20BOPENROUTER_API_KEY, nivel gratuito
AntigravityGemini Flash/Pro, Claude Sonnet 4.6, Claude Opus 4.6, gpt-oss 120BCLI de agy, con sesión iniciada
AnthropicClaude Opus 4.8, Sonnet 5, Haiku 4.5Suscripción CLI de claude
CodexGPT-5.4 (predeterminado de CLI) y GPT-5.4-miniSuscripción CLI de codex
Ollama Cloudgpt-oss 20B, gpt-oss 120BCLI de ollama
DeepSeekv4-flash, v4-pro (ambos apagados hasta que agregues una clave)DEEPSEEK_API_KEY, prepago
cursor-agent / opencode / kiro-cliun revisor CLI agéntico cada unola CLI respectiva

¿Tienes un segundo proyecto de Google? Google AI Studio puede limitar por tasa un proyecto completo a la vez, separadamente del límite por minuto de cada modelo — así que una segunda clave de la misma cuenta es un bucket genuinamente independiente, no un reintento del primero. El botón "+ Agregar otra clave" del panel clona los modelos del proveedor bajo un nuevo slug (google → google2 → google3…) y lo almacena en tu overlay local, donde permanece removible. Lo mismo aplica a cualquier proveedor basado en claves aquí.

El modelo de nivel fuerte de Google es la única entrada incluida que está apagada por defecto: Gemini 3.1 Pro tiene una asignación de nivel gratuito de cero, así que alcanzarlo requiere facturación habilitada. Permanece incluido para que puedas activarlo si eso es lo que quieres. Si quieres un modelo fuerte gratis en su lugar, el grupo tiene nueve — Nemotron Ultra y Super en OpenRouter, gpt-oss 120B en Groq y Ollama, y Claude Opus / Gemini Pro a través de Antigravity.

DeepSeek se envía deshabilitado porque su API es prepaga — sin clave y sin saldo no puede responder nada, así que permanece fuera de tu grupo hasta que agregues DEEPSEEK_API_KEY, momento en el cual ambas entradas se encienden solas.

Cerebras (eliminado en 0.13.0) y Z.ai (eliminado en 0.22.0) ya no están incluidos — ambos necesitan configuración de proveedor de pago. Agréguelos localmente con add-model si tienes un plan.

¿Falta un proveedor? Sugiérelo — el panel tiene un formulario que abre un issue prellenado.


Manteniendo el grupo honesto

Los proveedores deprecen modelos, los niveles gratuitos rotan, las claves expiran. El registro incluido te dice qué existe; solo una llamada real te dice qué tu cuenta aún puede alcanzar.

external-agents audit                 # every enabled entry with an HTTP transport
external-agents audit --provider google   # just one bucket
external-agents audit --include-disabled  # include switched-off entries too

Un round-trip por entrada, concurrente por proveedor para no disparar límites de tasa, y los veredictos se escriben en state.json — para que el panel y el despacho reflejen inmediatamente la verdad del terreno:

  • ✓ healthy — la clave funciona, el modelo existe
  • ⚠ needs_auth — 401/403, pega o refresca la clave
  • ✗ model_unavailable — la clave está bien, este modelo no está en tu nivel
  • ⏳ rate_limited — alcanzaste el límite actual, se recuperará
  • ? errored_transient — algo salió mal una vez; expira solo después de 15 minutos
  • ! probe_error — el comando de sondeo no pudo ejecutarse aquí en absoluto (generalmente PATH). No dice nada sobre el agente, así que no se escribe nada

audit también barre los directorios temporales de este paquete una vez que termina, reportando lo que se fue. Esos directorios contienen el generated.md de cada despacho — la respuesta completa del modelo, en texto plano — y el sistema operativo solo los reclama después de aproximadamente un mes. La ventana predeterminada es de 3 días; EXTERNAL_AGENTS_TEMP_RETENTION_DAYS la cambia, y un valor negativo apaga el barrido. Nada fuera de los prefijos propios de este paquete se toca jamás, los enlaces simbólicos se omiten en lugar de seguirse, cualquier cosa en un sistema de archivos diferente (un punto de montaje) se deja en paz, y nada modificado en los últimos 15 minutos se elimina sin importar la ventana — así que un despacho en ejecución ahora mismo no puede perder su workdir incluso si la ventana está en cero.

Las entradas apagadas se omiten por defecto: no se pueden despachar de todos modos, y para un proveedor prepago auditar una gasta dinero real para no aprender nada. external-agents status muestra una columna de use para que un healthy verde junto a una entrada apagada no pueda malinterpretarse como "disponible".

Día a día, external-agents ui es la misma información que una página: estado del proveedor en vivo, uso, y un cuadro de pegado por proveedor. Se vincula solo a loopback. Las entradas individuales tienen un interruptor de encendido/apagado (external-agents toggle <id> --disabled) si quieres sacar una de rotación sin eliminar nada.

Cuando algo falla y quieres saber por qué

external-agents stats mantiene una vista previa de 400 caracteres del último error por agente — suficiente para el panel, rara vez suficiente para arreglar algo. La vista previa es una cola, así que una CLI que imprime un banner y luego lanza una excepción obtiene el banner recortado y la excepción recortada.

El registro de fallos sidecar es la otra mitad. Está apagado por defecto y no registra nada hasta que lo enciendas:

external-agents failures on

A partir de entonces, cada intento fallido se añade completo a ~/.local/state/external-agents/failures.jsonl — un objeto JSON por línea:

  • dispatch — stdout completo, stderr completo, el argv exacto, el cwd, la solicitud HTTP y el cuerpo de respuesta sin truncar del proveedor
  • audit y credential verify — la salida cruda de la sonda que la pista recorta a 200 caracteres
  • sonda de solo lectura — incluyendo el caso donde un comando declarado de solo lectura escribió en el canary
  • rechazos previos al dispatch — agente desconocido, agente deshabilitado, discrepancia de --require-base, sin candidato de escalada. Estos nunca llegan al registro de dispatch en absoluto, y son los más difíciles de reconstruir después: no se generó nada, así que no hay código de salida que encontrar.

Cada fila también lleva la clasificación derivada de esa salida (needs_auth, quota_exhausted, model_unavailable, harness_failure), para que un modelo que lea el archivo pueda distinguir "tu clave está mal" de "este modelo ya no existe" de "tu PATH está roto" sin tener que volver a derivarlo.

Ese es el uso previsto. El registro está escrito para ser pegado:

external-agents failures tail 50      # raw JSONL — hand it to a model and ask what to fix
external-agents failures status       # is it on, how big, which agents fail most
external-agents failures off
external-agents failures clear

El interruptor vive en ~/.local/state/external-agents/config.json, no en el paquete — así que npm i -g @mrrlin-dev/external-agents@latest no puede silenciosamente volver a apagarlo. EXTERNAL_AGENTS_FAILURE_LOG=1 (o =0) anula el archivo para una sola ejecución; EXTERNAL_AGENTS_FAILURE_LOG_FILE apunta el sumidero a otro lugar.

Todo permanece en tu disco — el archivo es 0600 y nada se transmite a ningún sitio. Los secretos se eliminan en el camino: cada valor de entorno con forma de clave que este proceso sostiene se borra por coincidencia exacta (también en su forma escapada, para la pasada que recorre la línea serializada), más una pasada de patrones para tokens que nunca sostuvo, más una pasada de forma para una contraseña incrustada en una cadena de conexión, más una pasada final sobre la línea serializada. Qué nombres cuentan como con forma de clave es una lista, y una lista solo es tan completa como las convenciones que alguien pensó — KEY, TOKEN, SECRET, AUTH, PAT, PSK y sus vecinos están en ella.

La herramienta no escribe tu prompt — prompt_text se descarta y el posicional del prompt en el argv se convierte en un conteo de bytes; --with-prompts opta por volver a incluirlo. Eso no es lo mismo que una promesa de que no hay texto del prompt en el archivo: muchos CLIs devuelven el prompt en stdout, y raw.stdout se captura completo, que es el punto del sumidero. Lee el archivo antes de pegarlo en algún lugar donde no pegarías el prompt.

El otro registro: dispatch-log.jsonl

Junto a él hay un segundo archivo, mucho más pequeño — una fila de ~300 bytes por dispatch, sin texto de prompt, sin flujos crudos, escrito tanto si la llamada tuvo éxito como si no. Ese está siempre activado, y así se mantiene: es donde get_stats, doctor y el libro mayor de límites observados obtienen sus números, y cada defecto que este pool ha corregido en esa área se encontró leyéndolo en lugar de leyendo código. No hay interruptor, porque un pool que ha dejado silenciosamente de medirse a sí mismo se ve exactamente como uno saludable.

Lo que no se le permite es crecer para siempre en tu disco:

  • La retención es de 30 días, y se mide en días en lugar de bytes a propósito. Cada pregunta que alguien le hace a este archivo es una pregunta sobre tiempo — --since 24h, la ventana de asignación medida de doctor — y un límite de bytes responde a esas solo por coincidencia de lo ocupado que estuviste: un mes tranquilo mantiene un año de filas muertas, una semana ocupada deja caer el extremo lejano de una ventana sobre la que aún estabas preguntando. Nada da error en ninguna dirección, que es lo que hace que los bytes sean el eje equivocado. EXTERNAL_AGENTS_DISPATCH_LOG_RETENTION_DAYS cambia la ventana; EXTERNAL_AGENTS_DISPATCH_LOG_MAX_BYTES es un respaldo de 32 MiB para una ráfaga que supera la regla de edad dentro de una ventana, y dice en stderr cuando recorta.

    El recorte ocurre cuando la fila más antigua tiene aproximadamente una quinta parte de una ventana de retraso, no en el momento en que cruza la línea — así que el archivo se asienta entre 30 y 36 días y se reescribe cada pocos días en lugar de en cada dispatch. Solo un proceso recorta a la vez. Un bloqueo abandonado se reclama verificando si su titular sigue en ejecución — nunca por lo viejo que parece, porque la edad de un bloqueo no puede distinguir una poda abandonada de una lenta. En el único caso donde la vivacidad se equivoca (un pid reciclado), la herramienta te lo dice, con el comando para limpiarlo, en lugar de adivinar.

  • EXTERNAL_AGENTS_DISPATCH_LOG_FILE lo apunta a otro lugar — la misma anulación que tiene failures.jsonl.

  • El archivo es 0600 (reverificado en cada escritura, no solo en la creación), y el único campo de texto libre en una fila — la vista previa de error de 400 caracteres que se mantiene en fallos — pasa por la misma redacción que el sidecar.

Añadir tu propio modelo

Un endpoint interno, un modelo beta, cualquier cosa no incluida:

external-agents add-model \
  --id kimi-k2-instruct \
  --provider groq \
  --model moonshotai/kimi-k2-instruct \
  --url https://api.groq.com/openai/v1/chat/completions \
  --env GROQ_API_KEY \
  --tags free,fast

Eso escribe en ~/.local/state/external-agents/agents.local.yaml, superpuesto sobre el registro incluido — el mismo id reemplaza, el nuevo id añade. Las actualizaciones de paquetes nunca lo sobrescriben. Tutorial completo: docs/adding-a-provider.md.


Vigilando el pool por regresiones

Los cinco objetivos anteriores se verifican, no se asumen:

external-agents doctor                # last 24h
external-agents doctor --since 7d     # a wider window
external-agents doctor --json         # machine-readable, same checks

Una verificación por objetivo, cada una con la evidencia que te permite verificarla o descartarla y el comando que la corrige. El código de salida es 1 solo en un hallazgo de alta severidad y 0 en caso contrario, así que es seguro ejecutarlo sin supervisión y solo grita cuando algo realmente se rompió.

VerificaciónObjetivoMedio
oversized_dispatch2Ocurrió un HTTP 413. Con techos medidos esto debería ser inalcanzable.
unmeasured_seat2Un asiento HTTP habilitado no tiene techo, declarado u observado — nada puede protegerlo.
never_answered1Un agente fue despachado repetidamente y nunca tuvo éxito una sola vez.
success_rate3La ventana cayó por debajo del mínimo.
tier_imbalance4Un asiento está tomando mucho más de su parte de un nivel.
idle_bucket5Una asignación conocida no se está gastando, y nada dice que la familia esté limitada en otro lugar.

Todos los días, sin que se lo pidan

Apúntale un programador. doctor es la mitad probada — umbrales, evidencia, un remedio por hallazgo, un código de salida — y lo que lo ejecuta en un temporizador es la otra mitad. Una tarea programada de Claude Code funciona bien, porque la parte interesante de una verificación diaria no es ejecutar el comando sino decidir qué en su salida vale la pena despertar a alguien:

Run `external-agents audit` then `external-agents doctor --since 24h --json`.
Report only findings with severity "high", plus anything that changed since
yesterday. If nothing is high and nothing changed, reply with one line.

Ejecuta audit antes que doctor, y ese orden es el diseño: audit es un ping de max_tokens: 1 por entrada HTTP, y la respuesta de la sonda lleva el techo real de límite de tasa del proveedor — así que la pasada de medición repara el hallazgo más común en lugar de solo informarlo. Un perro guardián que arregla lo que puede vale la pena mantenerlo; uno que solo se queja termina silenciado.


Filosofía de enrutamiento — sé inteligente, no derrochador

pick_agents por defecto es tier: "weak" a propósito. La mayoría de las tareas no necesitan un modelo frontera.

Ediciones de un solo archivo, refactorizaciones, código de pegamento, resúmenes, conversiones de formato, correcciones de errores bien delimitadas, docstrings, casos de prueba — un Gemini Flash, Groq gpt-oss, DeepSeek, o un modelo :free de OpenRouter te da la misma respuesta correcta que Claude Opus o Codex Pro, más rápido y por una fracción del costo.

Alcanza el nivel fuerte (Claude Opus, Codex, DeepSeek Reasoner, Nemotron Ultra) cuando la tarea es genuinamente una de estas:

  • Depuración de múltiples pasos con una causa raíz poco clara
  • Decisiones de arquitectura o forma de API
  • Algoritmos novedosos, transformaciones con mucha matemática
  • Requisitos ambiguos que el modelo tiene que desambiguar

Si un agente de nivel débil se equivoca, el primer movimiento es afinar la especificación, no escalar el nivel. escalate_to_pro es una palanca de reintento, no un valor predeterminado — alcanzar un modelo más grande oculta fallos de ingeniería de prompts detrás de cómputo caro, y pagarás por ello en cada llamada posterior también.

Relacionado: --effort <level> controla la profundidad de razonamiento donde el proveedor lo soporta. Usa high para planificación, diseño y revisión; déjalo apagado para ediciones mecánicas. Consulta docs/effort.md para la tabla verificada por agente.


FAQ

¿Envías mis claves API a algún lugar?

No. Las claves viven en ~/.local/state/external-agents/keys.env (modo 0600) y se leen en el entorno del servidor MCP. El panel que las acepta se vincula solo a loopback, nunca a una interfaz de red. Los tokens de suscripción permanecen donde su propio CLI los puso (codex login, claude login) — este paquete nunca los lee ni los mueve. Nada se transmite a ningún sitio excepto al proveedor al que estás despachando.

¿Puedo usar esto sin claves API en absoluto?

Sí, si has iniciado sesión en al menos un CLI agéntico — claude, codex, cursor-agent, ollama, opencode, kiro-cli, o agy. Esas entradas están respaldadas por suscripción y no necesitan configuración de claves. Los proveedores de API de nivel gratuito se apilan encima cuando quieras añadirlos.

Añadí una clave pero el panel aún dice "no establecida".

Recarga la página — desde 0.39.0 el panel y el servidor MCP releen el almacén de claves en cada solicitud, así que una clave añadida desde una terminal aparece en la siguiente encuesta. Si persiste, el valor probablemente está siendo ensombrecido por la misma variable exportada en tu propio shell, que siempre gana sobre la almacenada.

¿Cómo maneja los 429?

Cada llamada real actualiza el estado desde los encabezados de respuesta y el cuerpo del error. El enfriamiento usa el propio tiempo de reinicio del proveedor, parseado de x-ratelimit-reset-*, Retry-After, y los payloads de error. Si Google dice que la cuota se reinicia en 42 horas, espera 42 horas en lugar de adivinar una hora y golpear una pared.

¿Cómo encuentra claude mcp add a external-agents-mcp?

npm i -g enlaza simbólicamente external-agents-mcp en tu directorio bin global (generalmente /opt/homebrew/bin en macOS, /usr/local/bin en Linux), que está en tu PATH. claude mcp add escribe esa cadena literal en ~/.claude.json, y Claude Code lo genera como un proceso hijo — resolución ordinaria de PATH. Sin hosting, sin daemon, sin búsqueda de registro.

Licencia

MIT. Problemas y solicitudes de extracción bienvenidos.