Construí aicodebox como una abstracción, una sola imagen base, un contrato AgentAdapter, enchufas el CLI de cualquier agente de código y te llevas gratis la superficie de API/MCP/Telegram/cron. Bonita teoría. Las teorías no valen una mierda hasta que has cableado de verdad un segundo binario de agente, completamente distinto, al contrato y has mirado por dónde sangra. Así que agarré pi-coding-agent, un CLI que no escribí y no controlo, y lo forcé a pasar por el adaptador. Eso es pibox. No es una imagen insignia llena de funciones, es la prueba de que la abstracción no es mentira, y ahora es la referencia de la que se copia cualquier otra imagen hija, incluida la de Claude Code.
Si ya leíste el post de aicodebox conoces el discurso: modos, adaptadores, REST, compatibilidad con OpenAI, MCP, Telegram, cron, todo en la capa base, todo gratis una vez que escribes un adaptador. No lo voy a reescribir aquí. Este post va de lo que costó de verdad atornillar un binario de agente de terceros a ese contrato sin hacer trampas, y de lo pequeña que acabó siendo la superficie una vez que dejé de añadir mierdas que no sostenían nada.
Lo Que pi No Te Da
pi-coding-agent es un CLI perfectamente decente. También está, como todos los CLI de agente del planeta, hecho para un humano sentado en una terminal, no para un proceso de servidor que necesita metadatos estructurados de vuelta. Cablearlo en aicodebox significó rodear cada una de las suposiciones muy razonables y muy de terminal de pi:
- Dos modos de salida, uno inútil para una API. pi tiene
--mode texty--mode json. El modo texto te da las palabras del asistente y absolutamente nada más, sin id de sesión, sin consumo, sin eventos por turno. Vale para un humano, inútil para una ruta que tiene que facturar tokens y reanudar sesiones. - Cero validación nativa de esquema JSON. pi no tiene flag
--schema, no tiene modo de salida estructurada. Solo habla. - Sin soporte nativo de
ANTHROPIC_BASE_URL. La propia documentación de pi dice «usa models.json», es decir, no va a leer la variable de entorno por la que un proxy compatible con Anthropic (Z.AI, OpenRouter, tu propio gateway) espera funcionar. - Un proveedor integrado que te secuestra el enrutado en silencio. El proveedor
zaide pi se apropia automáticamente de cualquier nombre de modeloglm-*, lo que significa que una petición que debía pasar por tu sobreescritura deANTHROPIC_BASE_URLpuede acabar redirigida calladamente a un proveedor que no pediste. - Sin soporte de primera clase de MCP desde la config del workspace como lo tiene Claude Code, o sea, sin recogida automática de un
.mcp.jsondel workspace.
Nada de eso es culpa de pi, es un CLI, hace cosas de CLI. Pero «no es culpa del agente» no te da una API. Alguien tiene que traducir. Ese alguien es una sola clase de Python.
Tres Variables de Entorno Son Todo el Anclaje
Aquí está la parte que de verdad le gana a pibox el título de «referencia». Desnuda el Dockerfile hasta lo que pone de específico para pibox y te salen exactamente tres variables de entorno:
ENV AICODEBOX_ADAPTER=pibox.adapter:PiAdapter \
AICODEBOX_AGENT_BINARY=pi \
PIBOX_IMAGE_VARIANT=minimalYa está. Esa es toda la superficie declarativa que necesita una imagen hija: apunta AICODEBOX_ADAPTER a un module:ClassName importable, dile a la base a qué nombre de binario tirar, y toda la maquinaria de modos, REST, compatibilidad con OpenAI, MCP, Telegram, cron, cobra vida a su alrededor. PIBOX_IMAGE_VARIANT no forma parte de ese contrato en absoluto, solo etiqueta en cuál de las dos imágenes publicadas estás metido, la mínima o la full. Todo lo demás en el Dockerfile o instala el binario del agente, o instala el paquete Python que implementa el adaptador, o es branding. Si estás construyendo tu propia imagen hija y el Dockerfile necesita una tercera variable de entorno para que el agente arranque, estás haciendo algo que el contrato no pretendía obligarte a hacer, vete a releer la clase AgentAdapter de la base.
El resto de la build es aburrida a propósito: npm install -g --ignore-scripts @earendil-works/[email protected], fijado, no @latest, porque «funciona hoy» y «funciona dentro de seis meses» no son la misma afirmación, y luego uv pip install --system --break-system-packages --no-deps /opt/pibox para el paquete de adaptador en sí (--no-deps porque aicodebox ya está en la imagen base y volver a resolverlo es trabajo tirado).
El Adaptador: build_argv Es Obligatorio, Todo lo Demás Es Una Elección
La clase AgentAdapter de la base tiene exactamente un método que lanza NotImplementedError si no lo sobreescribes: build_argv. Todos los demás ganchos, validate, build_env, translate_auth, parse_output, post_validate_json, parse_events, parse_stream_event, interactive_argv, passthrough_argv, auth_paths, traen un valor por defecto que funciona. PiAdapter los sobreescribe todos menos build_env y post_validate_json, porque el CLI de pi es lo bastante raro como para que los valores por defecto produzcan basura. Esos dos no los toca por algo: build_env ya mete en el entorno lo que devuelva translate_auth, y el runner parsea él solito la salida con schema. Esto es lo que te compra cada uno de verdad, verificado contra pibox/pibox/adapter.py:
build_argv(obligatorio): invoca siemprepi -p --mode json, nunca--mode text, precisamente para que el adaptador reciba el flujo completo de eventos de sesión en lugar de prosa pelada. El manejo de sesión se ramifica en tres:--session <id>al reanudar,--no-sessionpara efímero,--continueen el resto.validate(opcional, la base solo compruebaoutput_format): rechaza un valor dethinkingfuera deoff/minimal/low/medium/high/xhigh, y rechazatools_allowlistcombinado conno_toolscomo tontería mutuamente excluyente.translate_auth(opcional, el valor por defecto de la base no hace nada): sobreescrito igualmente, sigue sin devolver nada, porque pi lee de forma nativaANTHROPIC_API_KEY/OPENAI_API_KEY/OPENROUTER_API_KEY/GEMINI_API_KEY/ZAI_API_KEY. La sobreescritura existe para documentar ese hecho en el código, no para cambiar el comportamiento.parse_output(opcional, el valor por defecto de la base solo limpia stdout como texto plano): recorre el NDJSON de pi, sacandosessionpara el id de sesión,message_endpara el texto del asistente y el consumo, yturn_endcomo recurso para el consumo. Aquí es también donde se rellenaprovider_error: cuando un turno de asistente llevastopReason=errormás unerrorMessage, la manera que tiene pi de reportar un rechazo aguas arriba, un rate limit o un fallo de auth, el adaptador lo captura y lo reenvía enRunResult.provider_errorpara que la ruta de OpenAI pueda devolver un400de verdad en vez de un200con texto vacío.parse_events(opcional, el valor por defecto de la base devuelve una lista vacía): decodifica como JSON cada línea NDJSON para el modo de salidajson-verbose; las líneas malformadas se tiran con un aviso, no con un crash. Solo que ese gancho ya es código muerto: desde aicodebox v0.16.0 el runner no lo llama nunca, porqueeventMode: fullguarda él solito cada línea cruda de stdout, sin ningún filtrado del adaptador. La sobreescritura se queda ahí de adorno hasta la próxima versión mayor del contrato de adaptador.parse_stream_event(opcional, el valor por defecto de la base trata cada línea como un delta de texto crudo): decodifica el flujomessage_update.assistantMessageEventde pi, reenvía al cable solotext_delta, y se traga en silencio los deltasthinking_*y los de uso de herramientas para que el razonamiento interno del modelo no se filtre nunca al campocontentcompatible con OpenAI.auth_paths(opcional, el valor por defecto de la base no devuelve nada que persistir): lista el estado real de pi,~/.pi/agent/auth.json,settings.json,models.json, el directoriosessionsy el marcador de provider propio de pibox,pibox-provider.json, para que los tokens OAuth y el historial de sesiones sobrevivan a undocker starten vez de evaporarse.
Hay también un hack metido dentro de build_argv del que no estoy orgulloso pero que defenderé sin reservas: si ANTHROPIC_BASE_URL está puesta y quien llama no ha elegido ya un proveedor en extra_args, el adaptador inyecta a la fuerza --provider anthropic. ¿Por qué? Porque el proveedor integrado zai de pi se apropia automáticamente de cualquier nombre de modelo glm-* y se salta tu sobreescritura de base URL por completo. Sin el flag forzado, apuntar pibox a un proxy compatible con Z.AI y pedir un modelo glm-4.6 ignora tu proxy en silencio y habla directamente con lo que el proveedor zai de pi crea que debe. Ese es el tipo de bug que le cuesta a alguien una tarde y un ticket de soporte antes de que nadie se dé cuenta de que el tráfico nunca tocó el proxy.
Modo Esquema: pi No Tiene Validación Nativa, Así Que Es Un Añadido al Prompt de Sistema
pi no valida esquemas JSON. No tiene ningún flag para eso. Así que cuando una petición lleva jsonSchema, la única jugada del adaptador es añadir una directiva al prompt de sistema diciéndole al modelo, en inglés llano, «responde con un único documento JSON conforme a este esquema, sin prosa, sin vallas de código», y luego pasarle el esquema en JSON crudo con ello. Toda la validación de verdad, parsear el resultado, comprobarlo contra el esquema, reintentar hasta tres veces con un prompt correctivo cuando falla, ocurre en la capa base de aicodebox, no en pibox. El único trabajo del adaptador es empujar al modelo hacia el cumplimiento; no tiene voz ni voto en si el modelo cumple de verdad.
Esa separación importa por lo que llegó después. El ayudante de reintento de esquema de la base se fue volviendo más listo progresivamente sin que cambiara ni una línea del código de adaptador de pibox, y los comentarios del propio changelog del Dockerfile se leen como un diario de comportamiento de la base evolucionando bajo un contrato de adaptador completamente estable: reintentos que reformulan la tarea original en vez de solo el error (para que un esquema que pide elegir de un enum grande no reintente a ciegas), workspaces efímeros por petición para que una petición de 100k tokens que necesita tres reintentos pague unos 1,5k tokens de sobrecoste correctivo en vez de reproducir los 100k enteros tres veces, y, sacado del propio changelog v0.14.0 de aicodebox y no del Dockerfile, stream:true combinado con llamada a herramientas o modo esquema ya no devuelve un 400 seco. Ahora calcula la respuesta completa sin streaming y la reproduce como un único flujo SSE con buffer: un chunk de rol, un delta de content o de tool_calls, un chunk de cierre, [DONE]. El chat normal sigue haciendo streaming token a token. Nada de eso tocó pibox/pibox/adapter.py. Ese es todo el sentido del contrato, la imagen hija no tiene derecho a saber ni a que le importe que la base se volviera más lista por debajo.
La Extensión mcp-bridge: Darle a pi el Formato de Config de Otro
pi no lee de forma nativa un .mcp.json del workspace como hace Claude Code. pibox trae una extensión de TypeScript, pibox/extensions/mcp-bridge/index.ts, que lo hace por pi: al arrancar la sesión lee el .mcp.json del workspace usando el esquema de claude-code (mcpServers.<name>.{command,args,env}), levanta cada servidor por stdio, llama a listTools(), y registra cada herramienta en pi bajo un nombre saneado mcp__<server>__<tool> vía pi.registerTool(). Las llamadas a herramientas se reenvían al servidor MCP y el resultado vuelve por el mismo canal que usan las herramientas integradas de pi, el modelo no nota la diferencia.
El npm install de esa extensión ocurre en tiempo de build, RUN cd /opt/pibox/extensions/mcp-bridge && npm install --omit=dev --ignore-scripts --no-audit --no-fund, no en el primer arranque del container. Es una elección deliberada: nadie quiere que su primera ejecución de agente se atasque en un resolve de npm. Un script init.d, pibox/init.d/10-pi-extensions.sh, engancha la ruta de la extensión preinstalada en el array extensions de ~/.pi/agent/settings.json una vez por container (desde aicodebox v0.17.0 eso significa cada container nuevo, ya no una vez por directorio de estado montado), de forma idempotente, mediante un merge con jq que deduplica con unique para que volver a ejecutarlo no acumule entradas duplicadas.
También hay ahí dentro una carrera en el apagado que vale la pena señalar porque es el tipo de bug que solo aparece en producción: pi -p se queda colgado después de imprimir su respuesta final si los subprocesos de servidor MCP levantados siguen manteniendo el bucle de eventos abierto. La extensión escucha session_shutdown, hace competir el cierre de cada cliente MCP contra un timeout de 2 segundos, y luego, cinturón y tirantes, llama a la fuerza a process.exit(0) medio segundo después porque algunas versiones de Node mantienen el bucle vivo incluso después de que close() resuelva. Sin eso, una llamada de API de un solo tiro se quedaría ahí sentada hasta que algo externo la matara.
pibox-entrypoint: 19 Alias y una Regeneración de Config al Arrancar
pibox-entrypoint.sh existe puramente para darle a pibox su propia superficie de variables de entorno con marca sin duplicar nada de la lógica de la base. Define una lista de 19 sufijos, API_MODE, API_MODE_PORT, API_MODE_TOKEN, TELEGRAM_MODE, TELEGRAM_MODE_TOKEN, TELEGRAM_MODE_CONFIG, TELEGRAM_MODE_OVERRIDES, CRON_MODE, CRON_MODE_FILE, CRON_MODE_HISTORY_DIR, MCP_MODE, MCP_MODE_PORT, MCP_MODE_TOKEN, MCP_MODE_ALLOWED_HOSTS, MCP_MODE_ALLOWED_ORIGINS, WORKSPACE, AVAILABLE_MODELS, AVAILABLE_EFFORTS, CONTAINER_NAME, y para cada uno, si PIBOX_<suffix> está puesta y AICODEBOX_<suffix> no, copia el valor. AICODEBOX_* gana si están las dos, así que los usuarios avanzados no quedan excluidos de los nombres subyacentes. Las dos variables de selección de adaptador, ADAPTER y AGENT_BINARY, están excluidas deliberadamente de esa lista; esas las fija el Dockerfile y no son algo que un usuario deba poder sobreescribir en tiempo de ejecución.
El entrypoint también ejecuta setup-provider-env.sh en cada arranque, no solo en el primero, y ese «cada arranque» es el arreglo de un bug real, no una elección de estilo. El script regenera la entrada de proveedor anthropic de pi en ~/.pi/agent/models.json a partir de ANTHROPIC_BASE_URL / ANTHROPIC_MODEL cada vez que arranca el container. Antes corría vía init.d, que por aquel entonces solo se disparaba una vez por directorio de estado montado, porque el marcador vivía en ~/.aicodebox, bien para un container desechable, roto en el momento en que alguien bind-montea ~/.aicodebox o ~/.pi como volumen persistente, porque entonces el marcador de init sobrevive a la reconstrucción y una ANTHROPIC_BASE_URL cambiada ya no vuelve a llegar nunca a models.json, en silencio. Moverlo al entrypoint significa que la config de base URL se refresca en cada arranque, con volumen persistente o sin él. Desde entonces aicodebox v0.17.0 movió ese marcador al filesystem del container, así que init.d vuelve a correr en cada container nuevo de todas formas. Da igual, al entrypoint le importa una mierda cómo decide la base cuándo corre el init.
v0.16.0: la ruta de Anthropic es ahora solo una de cuatro
Todo lo de arriba está escrito alrededor de una sola forma, doblar el proveedor anthropic de pi hacia el endpoint al que apuntes ANTHROPIC_BASE_URL. Eso sigue funcionando, y sigue siendo lo que corre si es todo lo que pones. Pero ahora es el camino heredado.
La v0.16.0 hizo genérico el proveedor. Cinco variables describen cualquier endpoint HTTP: PIBOX_PROVIDER_BASE_URL, PIBOX_PROVIDER_API, PIBOX_PROVIDER_API_KEY, PIBOX_PROVIDER_MODEL, PIBOX_PROVIDER_NAME. La de API elige la forma del cable, y hay cuatro: openai-completions, openai-responses, anthropic-messages, google-generative-ai. Lo que cubre LiteLLM y Z.AI sin fingir que ninguno de los dos es Anthropic.
El hack de inyección forzada en build_argv creció en consecuencia. Ahora es un selector de dos ramas: usa el proveedor configurado si lo hay, e inyecta su modelo como --model salvo que quien llama haya elegido otro, y si no, cae de vuelta a anthropic cuando ANTHROPIC_BASE_URL está puesto.
Pon PIBOX_PROVIDER_* y ANTHROPIC_BASE_URL al mismo tiempo y pibox sale en vez de elegir uno. Bien. Dos esquemas de enrutado a medio configurar que se contradicen es exactamente el fallo que quieres ruidoso y al arrancar, no callado y tres llamadas a herramientas más adentro.
Un detalle que vale la pena robar: la clave de aguas arriba solo vive en el entorno del proceso de Pi. Lo que aterriza en models.json es una referencia del tipo $OPENAI_API_KEY, no el secreto en sí, así que el fichero de config en disco sigue siendo aburrido si alguien lo lee.
v0.18.0: por fin tiene un wrapper
Todo lo que va debajo era antes la única forma de ejecutarlo: líneas de docker run escritas a mano con los montajes y el entorno deletreados cada vez. claudebox y codexbox tenían los dos un wrapper de host y un instalador. pibox no, porque era el hijo sin brillo y nadie se puso a ello.
Ahora tiene uno. install.sh te pone un comando pibox en el PATH y el wrapper se ocupa de la fontanería de containers, así que ejecutarlo en un directorio es solo pibox. Diez variables propias dirigen el wrapper: PIBOX_DATA_DIR, PIBOX_STATE_DIR y PIBOX_SSH_DIR para dónde vive el estado, PIBOX_IMAGE y PIBOX_FULL para qué imagen, PIBOX_DETACH para ejecuciones en segundo plano, PIBOX_CONTAINER_NAME para el nombre del container (si no, lo saca de la ruta del workspace), PIBOX_MAX_MEM para el tope de memoria (10g si no dices otra cosa), y PIBOX_ENV_* y PIBOX_MOUNT_* para pasar entorno y montajes. El instalador toma PIBOX_INSTALL_DIR con PIBOX_BIN_NAME para decidir dónde aterriza el comando y cómo se llama. Desde la v0.19.0, el directorio de estado (~/.aicodebox, salvo que PIBOX_STATE_DIR diga otra cosa) guarda también tus propios hooks: los ejecutables de su bin/ van primero en el PATH, para pi en cualquier modo, para los scripts de init y para las shells de docker exec, y init.d/*.sh corre una vez por cada container nuevo, después de los scripts de init de la imagen, como aicode con sudo sin contraseña. Un script que peta queda en el log y el resto corre igual, así que escríbelos para que aguanten ejecutarse otra vez, porque cada container nuevo los vuelve a ejecutar.
La v0.17.0 le dio también una variante psyb0t/pibox:latest-full construida sobre la imagen full de aicodebox (aicodebox:v0.15.0-full entonces, v0.17.0-full desde la v0.19.0), que es a donde se mudó el toolchain compartido de Go, Node, Python, clientes de bases de datos y ops. pibox dejó de construir nada de eso él mismo.
Y las cajas pueden llamarse unas a otras
Instala pibox, claudebox y codexbox en el mismo directorio y cada wrapper monta las tres, la suya incluida, en solo lectura en /usr/local/bin/<name>, así que pi puede pasarle una tarea a otro agente, o a un pi recién arrancado, sin salir de su sesión. Las ejecuciones anidadas llevan un contexto de host versionado, AICODEBOX_LAUNCH_CONTEXT_VERSION=1 más AICODEBOX_HOST_HOME, AICODEBOX_HOST_WORKSPACE, AICODEBOX_HOST_WRAPPER_DIR y un home y una ruta de wrapper por agente, porque una ruta dentro de un container no dice nada del host. pibox no escribe ningún fichero de auth, así que lo único que se salta una ejecución anidada es el mkdir del home de pi y de los directorios de estado y de SSH que hace una de primer nivel, porque son rutas del host y crearlas dentro del container que la llama solo lo llenaría de directorios vacíos. AICODEBOX_ENV_* y AICODEBOX_MOUNT_* reenvían a todas las cajas de golpe, y con AICODEBOX_MANAGED_INSTALL=1 los instaladores de claudebox y codexbox se saltan la pregunta de si reemplazar la clave SSH y se quedan con la que hay, mientras que el de pibox ni pregunta ni la pisa nunca.
El bug que convertía la lista de modelos en una mentira
Merece mención porque la sección de arriba promete algo que la v0.16.0 no llegó a entregar. Podías listar varios modelos en PIBOX_AVAILABLE_MODELS, pero solo el nombrado en PIBOX_PROVIDER_MODEL quedaba registrado con el proveedor. Pide cualquiera de los otros y la ejecución moría con Stream ended without finish_reason, que no te dice nada de la causa real. La v0.16.2 registra todos los modelos anunciados, así que la lista significa lo que dice. El arranque ahora también avisa cuando PIBOX_PROVIDER_BASE_URL y PIBOX_PROVIDER_API no coinciden sobre qué protocolo estás hablando, que es la otra manera de conseguir un fallo confuso tres llamadas más adentro.
Uno más heredado de la base: eventMode en una ejecución controla la retención de eventos con independencia de si pediste un esquema. full te devuelve los registros nativos del proveedor intactos en lugar de la versión resumida.
Uso
De un solo tiro, sin servidor:
docker run --rm \
-e ANTHROPIC_AUTH_TOKEN=your-token \
-e ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic \
-e ANTHROPIC_MODEL=glm-4.6 \
psyb0t/pibox:latest \
-p "list the files in /workspace"Servidor de API, el mismo aliasado de variables de entorno que aparece en cada imagen de psyb0t:
docker run -d --network host \
-e PIBOX_API_MODE=1 \
-e PIBOX_API_MODE_TOKEN=your-secret \
-e PIBOX_AVAILABLE_MODELS=glm-4.6,glm-4.5-air \
-e ANTHROPIC_AUTH_TOKEN=your-token \
-e ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic \
-e ANTHROPIC_MODEL=glm-4.6 \
-v "$PWD/workspace:/workspace" \
psyb0t/pibox:latestO constrúyela tú mismo sobre la base fijada:
# derives VERSION from pibox/pyproject.toml, pulls
# psyb0t/aicodebox:v0.17.0, tags :v<VERSION> and :latest
make buildPIBOX_API_MODE=1 levanta FastAPI en :8080 y se niega a arrancar sin PIBOX_AVAILABLE_MODELS puesta, porque no hay un valor por defecto sensato, ya que pi puede conducir la lista de modelos de cualquier proveedor, y una lista de modelos vacía en silencio es peor que un fallo de arranque. Expone /run, /openai/v1/chat/completions, /files/*, y, cuando PIBOX_MCP_MODE=1, /mcp/ montado en el mismo puerto, con el /mcp sin barra cayendo en el mismo handler, sin redirección. MCP mantiene activa la protección contra DNS rebinding, así que detrás de un reverse proxy o de un nombre de host público metes el host y el origen exactos en PIBOX_MCP_MODE_ALLOWED_HOSTS y PIBOX_MCP_MODE_ALLOWED_ORIGINS, o cada petición se va a tomar por culo. PIBOX_CRON_MODE_HISTORY_DIR es toda la raíz de estado del cron (~/.aicodebox/cron por defecto), así que el historial de ejecuciones bajo history/, los resúmenes por job y los metadatos de respuesta de Telegram se mueven todos con ella. Los modos de Telegram y cron reciben el mismo tratamiento cubierto en el post de aicodebox; vete a leer ese si quieres la matriz de modos en vez de la fontanería específica de pi.
El Hijo Sin Brillo
pibox no es la imagen hija llamativa. Es la que demuestra que el contrato de la base sobrevive al contacto con un binario de agente que dio por hecho que un humano, y no una API, iba a leer su salida. Dos variables de entorno anclan el adaptador, una tercera solo etiqueta la imagen, una clase de Python traduce el CLI con forma de terminal de pi a algo que la base sabe conducir, y el trabajo propio de funciones de la base, streaming, reintentos de esquema, llamada a herramientas, aterrizó por debajo sin que se moviera ni una línea de código de adaptador. Eso es lo que se supone que significa «implementación de referencia»: no la más grande, la que demuestra que la superficie más pequeña sigue funcionando.
Si quieres el agente más completo, la misma base, Claude Code en lugar de pi, ese es el post de claudebox. Si quieres ver al hermano de pibox con sabor a Z.AI corriendo como proveedor de verdad dentro de un stack más grande en vez de en solitario, eso está en el post de aigate, siendo pibox-zai uno de los proveedores a los que enruta aigate, y el pibox a secas también corre ahí, enchufado a los modelos que sirve el propio aigate, así que cuesta lo que cuesten esos modelos. El código está en GitHub. Es la imagen menos interesante de la familia y la primera que señalaría si estás escribiendo la tuya.
Cómo Instalarla En Tu Agente
La imagen menos interesante de la familia trae igualmente la misma ruta de instalación que el resto. Todo lo que hay bajo .agents/ está catalogado en un solo marketplace, así que son dos comandos:
claude plugin marketplace add psyb0t/agents
claude plugin install pibox@psyb0tCodex usa el mismo marketplace con un verbo distinto, codex plugin add pibox@psyb0t, porque no existe codex plugin install. También encuentra el skill por su cuenta en un checkout del repo, ya que escanea .agents/skills/ de forma nativa sin nada instalado en absoluto. Además ahora está listado en el MCP Registry oficial, así que un cliente que resuelva servidores desde ahí puede encontrarlo sin que le den una URL.