# pinfra CLI — guía para agentes de IA

Este documento es para un agente de IA (Claude Code, Cursor, u otro) que
va a instalar el CLI de pinfra y publicar una aplicación en nombre de un
humano. Es la versión condensada de https://platform.pinfra.app/cli — sin
marketing, sin capturas de pantalla en palabras.

## 1. Instalación no interactiva

Detectá el sistema operativo y la arquitectura, bajá el binario correcto
y verificá su checksum antes de ejecutarlo. Los binarios están en
https://platform.pinfra.app/cli/download/pinfra-<os>-<arch>, con
<os> en {darwin, linux, windows} y <arch> en {amd64, arm64}
(pinfra-windows-amd64.exe para Windows).

```bash
os=darwin        # o linux / windows
arch=arm64        # o amd64
asset="pinfra-${os}-${arch}"

curl -fsSLO "https://platform.pinfra.app/cli/download/${asset}"
curl -fsSLO "https://platform.pinfra.app/cli/download/checksums.txt"

want="$(grep " ${asset}\$" checksums.txt | awk '{print $1}')"
got="$(shasum -a 256 "${asset}" | awk '{print $1}')"
[ "$want" = "$got" ] || { echo "checksum mismatch, aborting"; exit 1; }

chmod +x "${asset}"
mv "${asset}" ~/.local/bin/pinfra
```

Si preferís el instalador interactivo (misma verificación de checksum,
menos pasos manuales): `curl -fsSL https://platform.pinfra.app/install | sh`
en Mac/Linux, `irm https://platform.pinfra.app/install.ps1 | iex` en
Windows PowerShell.

Verificación: `pinfra --version`.

## 2. Login — requiere confirmación humana

`pinfra login` abre el navegador del HUMANO para que confirme la
conexión con un click. **No lo corras y sigas de largo**: pausá, avisale
al humano que tiene que confirmar en el navegador (te va a mostrar un
código corto — el mismo que ve la terminal), y esperá su confirmación
antes de continuar. El pedido vence a los 10 minutos.

Si estás en un entorno headless/CI sin humano para hacer click, usá una
clave personal en cambio:

```bash
pinfra login --key=pinfra_pat_xxxxx   # sin prompt
pinfra login --key                    # la pide por teclado
```

La clave se crea desde Configuración → "Claves para conectar
herramientas" en la plataforma — mostrada una sola vez, guardala vos.

## 3. Publicar

Parado en la carpeta del proyecto:

```bash
pinfra publish --yes                      # sin preguntar nada
pinfra publish --name mi-aplicacion       # crea con ese nombre
pinfra publish --app <id-o-slug>          # publica sobre una app existente
```

Sin `--yes`, `--name` o `--app`, y sin
terminal interactiva (un pipe, un hook), el comando no adivina: falla con
un mensaje de qué le falta. `--json` da salida parseable en
cualquier comando.

`pinfra deploy` es el mismo comando, otro nombre.

Estado de una publicación:

```bash
pinfra status --app <id-o-slug>
pinfra status --app <id-o-slug> --json
```

`estado` en la salida JSON es uno de `en_curso`,
`publicado`, `fallo` — no lo hardcodees, usá el campo
`terminal` (bool) para saber si terminó. Código de salida de
`pinfra publish`: `0` si la app quedó online,
`1` ante cualquier problema (clave inválida, build fallido,
límite diario).

Para ver qué aplicaciones existen y su dirección:

```bash
pinfra apps
```

## 4. La API abierta (`pinfra api`)

Todo lo que se ve en el panel se puede leer por API, con la misma clave.
`pinfra api` (estilo `gh api`) le habla directo a
cualquier `/api/v1/*` — útil cuando ninguno de los comandos de
arriba (o de las herramientas MCP de la sección 5) cubre lo que
necesitás, en vez de esperar a que agreguemos un comando nuevo.

Listar aplicaciones y ver el detalle de una:

```bash
pinfra api /api/v1/cli/apps
pinfra api /api/v1/cli/apps/<id-o-slug>
```

Ver el estado de la cuenta (uso del mes, plan vigente) sin pasar por la
web:

```bash
pinfra api /api/v1/cli/usage
pinfra api /api/v1/cli/plan
```

Crear una aplicación vacía por API (el mismo POST que hace
`pinfra publish --name` la primera vez):

```bash
pinfra api POST /api/v1/cli/systems -f nombre=mi-aplicacion
```

Publicar en sí — subir la carpeta del proyecto — sigue siendo
`pinfra publish`, no `pinfra api`: ese paso empaqueta
y sube un `.tar.gz` por multipart, algo que
`--field` (que arma JSON plano) no cubre a propósito.
`pinfra api` es para LEER y para las escrituras simples que
son un JSON chico — no reemplaza el empaquetado.

La salida es JSON crudo a stdout; no hay `--jq` acá, se
espera que lo canalices al `jq` del sistema. El método es
opcional (`GET` por defecto, `POST` si mandás body); la
ruta siempre tiene que empezar con `/api/v1/`. Un pedido que no
vuelve `2xx` corta con código de salida `1` y el error
del servidor va a stderr — buena señal para que un agente decida el
siguiente paso sin tener que parsear prosa. **No hay borrado**: eso queda
siempre del lado de la web, con sesión humana.

## 5. Conectar por MCP

Hay dos formas de exponer las herramientas MCP de pinfra
(`listar_aplicaciones`, `publicar_aplicacion`,
`estado_de_la_aplicacion`) a un agente — remota, sin instalar
nada, y local, con el binario en tu máquina.

### Opción A — remota, sin instalar nada (recomendada)

`https://platform.pinfra.app/mcp` es un servidor MCP que corre DENTRO de
la plataforma: no hay binario que instalar ni toolchain de Go. Necesitás
una clave personal (Configuración → "Claves para conectar
herramientas", o la que dejó
`pinfra login` en `~/.pinfra/config.json`) y un
cliente MCP que hable HTTP:

```bash
claude mcp add --transport http pinfra https://platform.pinfra.app/mcp \
  --header "Authorization: Bearer <tu-clave>"
```

(la sintaxis de `--header`/`-H` es la del propio
`claude mcp add` — para otro cliente, buscá cómo agrega un
header HTTP al conectar un server remoto).

Con eso tenés `listar_aplicaciones` y
`estado_de_la_aplicacion` funcionando de verdad — son lecturas.
`publicar_aplicacion` está en el listado (mismo nombre, misma
descripción) pero **rechaza publicar por esta vía**: no hay una carpeta
de tu proyecto a la que la plataforma pueda acceder. Si la llamás, la
respuesta te va a sugerir `pinfra publish` o la opción B de
abajo — publicar por MCP remoto queda pendiente para una iteración
futura.

### Opción B — local (stdio), con `pinfra-mcp`

Esta es la única forma de publicar por chat hoy: el servidor corre en TU
máquina, con acceso a tu carpeta.

```bash
go install github.com/pinfra-systems/infra-platform/cmd/pinfra-mcp@latest
```

Si ya corriste `pinfra login`, no hace falta nada más — el
servidor lee la clave de `~/.pinfra/config.json`:

```json
{
  "mcpServers": {
    "pinfra": {
      "command": "pinfra-mcp"
    }
  }
}
```

Con una clave explícita (headless, CI, o para apuntar a otra plataforma):

```json
{
  "mcpServers": {
    "pinfra": {
      "command": "pinfra-mcp",
      "env": {
        "PINFRA_MCP_TOKEN": "pinfra_pat_...",
        "PINFRA_MCP_BASE_URL": "https://platform.pinfra.app"
      }
    }
  }
}
```

**No commitees** un `.mcp.json` con `PINFRA_MCP_TOKEN`
en texto plano — agregalo a `.gitignore`, o preferí
`~/.claude.json`, que vive fuera del repo
(`pinfra publish` tampoco sube `.mcp.json`: está en
la lista de exclusiones de la sección 7).
`publicar_aplicacion` exige el identificador de la aplicación
sin default: la clave alcanza todas las aplicaciones de la cuenta, así
que si no sabés cuál es, llamá primero a
`listar_aplicaciones`.

## 6. Variables de entorno relevantes

| Variable | Para qué |
|---|---|
| `PINFRA_API_URL` | Apunta el CLI a otra instancia (por defecto la del login) |
| `PINFRA_MCP_TOKEN` | La clave para `pinfra-mcp` en modo local. Opcional: sin ella usa la del login |
| `PINFRA_MCP_BASE_URL` | La plataforma para `pinfra-mcp` (default: la del login, o producción) |
| `PINFRA_BIN_DIR` | Dónde instala el script el binario (default `~/.local/bin`) |
| `PINFRA_INSTALL_BASE` | Origen de descarga que usa el instalador (default la plataforma) |

## 7. Qué NO se sube al publicar

Carpetas: `node_modules`, `.git`, `.next`,
`dist`, `build`, `.turbo`,
`.cache`, `.vercel`, `.netlify`,
`coverage`, `.pnpm-store`, `__pycache__`,
`.venv`/`venv`, y las carpetas de credenciales
`.ssh`, `.aws`, `.gnupg`,
`.claude`.

Archivos: todas las variantes de `.env` (excepto
`.env.example`, `.env.sample`,
`.env.template`, `.env.dist`), archivos de
credenciales en la raíz (`.mcp.json`, `.npmrc`,
`.yarnrc.yml`, `.netrc`,
`.git-credentials`, `terraform.tfstate`,
variantes de `terraform.tfvars`, claves privadas
`id_rsa`/`id_ed25519`/`id_ecdsa`/
`id_dsa`, y archivos `.pem`/`.key`/
`.p12`/`.pfx`/`.keystore`),
`.infra.json` (vínculo local carpeta↔aplicación, no viaja),
`.DS_Store`, y symlinks (no se siguen). El límite es 32 MB
comprimidos por publicación.

## 8. Alcance honesto

El CLI acepta dos bases hoy: un `Dockerfile` en la raíz del
proyecto (cualquier lenguaje — se publica como "servicio", con su propio
puerto, sin vista previa ni el flujo de edición conversacional — se
publica y se actualiza desde tu máquina), o un proyecto Next.js
(`package.json` con la dependencia). Un `Dockerfile`
en la raíz le gana a Next.js si están los dos. Si el proyecto no tiene
ninguno de los dos, `pinfra publish` rechaza la publicación con
un mensaje explicando qué falta — no adivina.
