Hay una pregunta que hace un año no nos hacíamos y que ahora aparece en cada herramienta que tocamos: ¿esto lo puede usar un agente? No un desarrollador con la documentación abierta, sino un modelo que recibe una tarea, decide qué comando ejecutar, lee lo que sale y sigue. En Transparent Edge tenemos una herramienta de línea de comandos, te-api, que envuelve toda nuestra API. Funcionaba bien para personas. Para un agente, era un campo de minas. Hoy contamos qué hemos cambiado, en qué nos hemos fijado y qué puedes hacer tú con ello.
Lo que nos enseñó Cloudflare
Hace unos días Cloudflare presentó cf, su nueva CLI, y lo hizo con una frase que nos quedó grabada: la han construido “desde cero pensando en agentes”. No es una herramienta para personas a la que luego se le añade un –json, es al revés.
Del post nos interesan menos las partes de Workers y de configuración en TypeScript, que no aplican a nuestro caso, y mucho más tres ideas sobre cómo debe comportarse una CLI cuando quien la invoca es un modelo:
- JSON por defecto. En sus palabras, “pretty printed para humanos y condensado para agentes, para ahorrar contexto al máximo”. El formato lo decide quién está al otro lado, en lugar de una opción que nunca te acuerdas de poner.
- Descubrimiento integrado. Con más de 3.000 operaciones, un agente no puede leer la ayuda de todas. Así que
cf cli searchrecibe una pregunta en lenguaje natural y devuelve los comandos candidatos a partir de la descripción de la API y de sus parámetros. Y la propia herramienta se lo cuenta al agente la primera vez que pide--help. - Una guía para el agente, no para el desarrollador. Un fichero AGENTS.md (gracias Claude por ceñirte por fin al estándar) que explica cómo usar la herramienta, no cómo contribuir a ella.
Lo que más nos llamó la atención es que todo esto se genera a partir del esquema OpenAPI, el mismo que alimenta su documentación y sus SDK. Y ahí sonreímos un poco, porque te-api lleva tiempo ya haciendo exactamente eso. Gracias Peter Steinberger por marcarnos el camino 🙂
De dónde partíamos
te-api es una CLI desarrollada en Python que se genera «sola» a partir del esquema OpenAPI que sirve nuestra API. La primera vez que la ejecutas, descarga el esquema con tus credenciales (en tu .env) y construye un comando por cada operación a la que tienes acceso: más de 350 en la versión completa. Cuando la API cambia, la herramienta lo detecta y se regenera. Nadie debería mantener a mano una lista de comandos, así que hemos intentado que sea lo más fácil y mantenible posible.
La estructura ya era la que un agente agradece: te-api <módulo> <verbo> <recurso>, con los verbos normalizados a get, create, update y delete, y una variante te-api-ro que solo expone lecturas, para cuando prefieres que un agente no pueda romper nada.
El problema estaba en lo que salía por pantalla. La herramienta hablaba para una persona sentada delante de un terminal:
- Si una llamada fallaba, imprimía Error: … por stdout y terminaba con código 0. Para un script, eso es un éxito. Para un agente, es un texto que tiene que interpretar y en el que es fácil equivocarse.
- Todo el JSON salía indentado, siempre. Cómodo de leer, caro en tokens cuando la respuesta son miles de líneas.
- Los comandos que envían datos tenían una opción –json-body cuya ayuda decía, literalmente, “JSON string for request body”. El esquema de ese cuerpo estaba en el OpenAPI, sin usar. El agente sabía que había que mandar algo, pero no qué. Cabe destacar que esto tampoco era de lo más amable para las personas, así que había que arreglarlo.
- Para encontrar un comando había que navegar –help nivel a nivel. Con 350 comandos, eso son muchas llamadas y mucho contexto gastado antes de hacer nada útil.
Nada de esto era un error de diseño cuando se escribió. Simplemente, el usuario que teníamos en la cabeza era otro.
Un contrato de salida que una máquina pueda leer
Lo primero que hicimos fue lo menos vistoso y lo más importante: definir, en un solo sitio, qué sale por dónde y qué significa cada código de salida. Antes, cada uno de los 350 comandos generados llevaba su propia copia del bloque que hacía la petición y pintaba el resultado. Ahora todos delegan en un módulo común, y el contrato es este:
stdoutlleva la respuesta de la API y nada más. Sistdoutes un terminal, el JSON sale indentado. Si es una tubería, sale compacto. Es la misma idea que cuenta Cloudflare: el formato lo decide quién escucha.--output pretty|compactfuerza uno u otro cuando hace falta.- Los errores son un objeto JSON en
stderr, con el tipo de fallo, el estado HTTP, la URL y el cuerpo que devolvió la API. - El código de salida distingue el tipo de fallo: 0 todo bien, 1 la API respondió con error, 2 la línea de comandos estaba mal, 3 no se pudo llegar a la API o no se pudo obtener el token.
En la práctica, una llamada que falla se ve así:
$ te-api inventory get node 999999999
{"error":{"type":"api","message":"HTTP 404 Not Found","status":404,"url":"https://api.transparentcdn.com/v1/inventory/node/999999999/","body":{"code":"not_found","message":"Not found."}}}
$ echo $?
1
Y una que funciona, encadenada con jq, se queda en lo que importa:
$ te-api companies get current-user | jq .id
10
Parece poca cosa. Pero un agente que puede fiarse del código de salida y parsear stderr no necesita adivinar nada, y uno que recibe JSON compacto gasta la mitad de contexto en cada respuesta. Todo lo demás se apoya en esto.
Que el agente sepa qué enviar
El segundo cambio ataca el “JSON string for request body”. El esquema de cada cuerpo de petición estaba en el OpenAPI, casi siempre detrás de una referencia a un componente compartido. El generador ahora resuelve esas referencias, pliega las composiciones (allOf, oneOf) y construye la ayuda a partir del esquema real: cada propiedad con su tipo, si es obligatoria y su descripción. Las propiedades que la API marca como de solo lectura se omiten, porque se reciben, nunca se envían.
$ te-api companies create alerts --help
--json-body TEXT JSON request body. JSON object with keys:
threshold(integer [required]) company(integer [required])
service(integer) active(boolean)
reactions(array of object [required])
Además de --json-body, todos esos comandos aceptan ahora --body-file, con – para leer de la entrada estándar. Escribir JSON en una línea de comandos es una fuente inagotable de errores de quoting, y un agente que construye el comando programáticamente los comete con la misma facilidad -o más- que una persona. Con un fichero, no hay nada que escapar. Y si el JSON llega roto, por el camino que sea, la herramienta lo dice como un error de uso, con código 2, en lugar de soltar una traza de Python.
Encontrar el comando sin leer 350 ayudas
Aquí copiamos la idea de cf cli search sin complejos, para qué engañarnos . Cloudflare tiene más de 3.000 operaciones y necesita un índice de búsqueda. Nosotros tenemos una décima parte, y con eso basta algo mucho más simple.
El generador escribe ahora, junto a los comandos, un catalog.json con una ficha por comando: nombre, resumen, método HTTP, ruta, cada parámetro con su tipo, si es obligatorio y sus valores posibles, y el esquema del cuerpo si lo tiene. Sobre ese catálogo hay dos comandos nuevos:
$ te-api search waf events --limit 2
[{"command":"statistics get waf","summary":"WAF statistics","method":"get","path":"/v2/statistics/{company_id}/waf/{temporality}/{request_type}/","score":3.0},
{"command":"statistics get waf-header-info","summary":"WAF header Details","method":"get","path":"/v1/statistics/{company_id}/waf/header_info/{waf_header_id}/","score":3.0}]
$ te-api describe statistics get waf
{"command":"statistics get waf","method":"get","path":"/v2/statistics/{company_id}/waf/{temporality}/{request_type}/","parameters":[{"name":"temporality","in":"path","argument":true,"required":true,"choices":["historic","analytic"]}, ...
➜ search puntúa por solapamiento de palabras entre lo que preguntas y el nombre del comando, su resumen, su descripción y los nombres de sus parámetros. El nombre pesa más que el resumen, y el resumen más que el resto. No hay embeddings, no hay dependencias, y para este tamaño funciona.
➜ describe es, en realidad, la pieza que más nos gusta. Devuelve la definición completa de un comando como JSON, que es exactamente lo que un agente necesita para construir una llamada correcta a la primera. Es el equivalente a la definición de una herramienta en un servidor MCP, pero para el cien por cien de la API y sin desplegar nada. Y si le das solo un prefijo, te-api describe billing get, te lista lo que hay debajo.
La guía vive dentro del binario
Cloudflare habla de un AGENTS.md que acompaña a la herramienta. Nosotros ya teníamos uno, pero era para quien desarrolla te-api, no para quien la usa. Así que añadimos un comando, te-api agents, que imprime una guía corta pensada para quien va a conducir la herramienta sin supervisión: la forma de los comandos, cómo encontrarlos, el contrato de salida y los códigos de error, cómo fijar la empresa sobre la que se trabaja con una variable de entorno en lugar de tocar estado compartido, y cómo funciona la autenticación.
La guía está dentro del binario por una razón práctica: así siempre describe la versión que tienes instalada, no la que había cuando alguien la documentó. Y con --skill se envuelve con la cabecera que espera Claude Code, de modo que instalarla como skill es una línea:
mkdir -p ~/.claude/skills/te-api
te-api agents --skill > ~/.claude/skills/te-api/SKILL.md
También copiamos el detalle de que la herramienta se presente sola. El --help raíz dice ahora, en tres líneas, que existen search, describe y agents, y que las respuestas son JSON en stdout y los errores JSON en stderr. Es la única ayuda que un agente lee seguro, así que es donde tiene que estar.
Lo que no hemos hecho, a propósito
Sería fácil leer el post de Cloudflare y querer replicarlo entero, pero optamos por darle nuestro toque práctico y adoptar aquello que es más útil.
➜ No hay búsqueda semántica. Con 350 comandos, la búsqueda por palabras resuelve la mayoría de los casos, y cuando falla, el listado completo de comandos cabe en el contexto de cualquier modelo actual. Meter embeddings para esto sería añadir una dependencia y una fuente de errores para ganar poco. Si algún día la API triplica su tamaño, lo revisaremos. Problema para el equipo del mañana.
➜ No hay configuración tipada. La parte del post dedicada a cloudflare.config.ts resuelve un problema de despliegue de Workers que nosotros no tenemos. Lo único que te-api necesita saber es sobre qué empresa trabajas, y eso se resuelve con una variable de entorno.
➜ No hemos tocado los nombres de los comandos. Vienen del esquema OpenAPI y los agentes los manejan sin problema. Cambiarlos ahora rompería los scripts que ya los usan, para una ganancia estética para gente con TOC.
La regla que seguimos fue sencilla: cada cambio tenía que eliminar una forma concreta en la que un agente se podía equivocar al usar la herramienta. Si no la había, no entraba.
Pruébalo
te-api es código abierto, con licencia GPL-3.0, y está en GitHub. Se instala con uv y necesita tus credenciales OAuth2 de la API de Transparent Edge:
uv tool install git+https://github.com/TransparentEdge/te-api.git
export TRANSPARENT_CLIENT_ID="..."
export TRANSPARENT_CLIENT_SECRET="..."
te-api agents
La primera ejecución descarga tu esquema y genera los comandos a los que tienes acceso. A partir de ahí, te-api search y te-api describe son el punto de entrada, tanto si eres tú quien teclea como si es un agente.
Si lo que quieres es dar acceso a un agente sin riesgo de que modifique nada, instala lo mismo y usa te-api-ro: la misma herramienta, el mismo contrato de salida, solo lecturas.
Y si lo pruebas y encuentras una forma en la que tu agente se sigue tropezando, abre un ticket. Es exactamente el tipo de problema que queremos que nos cuenten.
Autor: Diego Suárez, Director de Tecnología de Transparent Edge
Preguntas frecuentes
No, lo complementa. Un servidor MCP ofrece un conjunto curado de herramientas con descripciones pensadas para el modelo, y es la mejor opción para los casos de uso más frecuentes. La CLI cubre el cien por cien de la API, incluidas las operaciones que nadie pensó en exponer, y no requiere desplegar ni mantener nada: el esquema OpenAPI es la única fuente. En la práctica, un agente con acceso a un terminal y a te-api describe puede hacer cualquier cosa que permita la API.
Con cualquiera que pueda ejecutar comandos de terminal. El contrato de salida, search, describe y la guía de agentes son independientes del modelo. La opción --skill añade la cabecera que espera Claude Code porque es lo que usamos nosotros, pero el texto de la guía sirve igual como instrucciones de sistema para cualquier otro.
Nada que tengas que hacer tú. Los comandos y el catálogo se generan a partir del esquema que sirve la propia API, y la herramienta detecta cuando lo que tiene generado no coincide con la versión instalada. Si prefieres forzarlo, te-api build regenera todo en unos segundos.