# @andco/sdk/cli

> El constructor de terminal y el presentador con receptor loopback.


```ts
import { AndcoCliPresenter, createAndcoInstanceForCLI } from "@andco/sdk/cli";
```

## createAndcoInstanceForCLI

```ts
createAndcoInstanceForCLI(options: AndcoCliOptions): AndcoSessionClient
```

Un cliente público con sesión, como el de navegador, pero con un presentador de terminal y sin suponer que existe `window`. `AndcoCliOptions` son las opciones del cliente con sesión, con `presenter` opcional.

```ts
const andco = createAndcoInstanceForCLI({
  clientId: "tu-client-id",
  redirectTo: "http://127.0.0.1:0/callback",
  storage: keychain,
  presenter: new AndcoCliPresenter({ open: (url) => openInBrowser(url.href) }),
});
```

## AndcoCliPresenter

```ts
new AndcoCliPresenter(options?: AndcoCliPresenterOptions)
```

| Opción | Por omisión | Qué hace |
| --- | --- | --- |
| `open` | imprimir | abre la URL de autorización |
| `print` | — | recibe el mensaje cuando no se puede abrir un navegador |
| `timeoutMs` | 10 minutos | cuánto se espera a que la persona termine |

Levanta un receptor loopback de un solo uso: se ata al puerto, rechaza cualquier ruta que no sea la registrada, comprueba el `state`, responde una vez y cierra.

Todo integrador de línea de comandos necesita exactamente esto, y hasta ahora cada uno lo escribía. La versión de la propia CLI de Andco tenía noventa líneas. Equivocarse en cualquier parte —aceptar el callback en otra ruta, saltarse la comprobación del `state`, dejar el puerto abierto— es un fallo de seguridad en la herramienta de alguien.

El callback tiene que ser `http` sobre un host de loopback; cualquier otra cosa se rechaza con `invalid_configuration`.

### Puerto libre

```ts
redirectTo: "http://127.0.0.1:0/callback";
```

El puerto `0` pide uno libre al sistema. El presentador reemplaza el `redirect_uri` de la autorización por el puerto que realmente obtuvo, porque el servidor de autorización tiene que redirigir a esa URI exacta.

## Flujo de dispositivo

Cuando no hay navegador que abrir:

```ts
const { data: device } = await andco.oauth.createDeviceAuthorizationRequest({ scopes: ["openid", "email"] });
console.log(`Abre ${device.verificationUriComplete} y escribe ${device.userCode}`);

const { data: session } = await andco.oauth.awaitDeviceAuthorization(device);
await andco.session.set(session);
```

`AndcoDeviceAuthorization` trae `deviceCode`, `userCode`, `verificationUri`, `verificationUriComplete`, `expiresIn`, `interval` y la respuesta cruda.

`awaitDeviceAuthorization` hace el sondeo completo: respeta el intervalo, obedece los avisos de ir más lento y termina cuando la autorización se aprueba, se rechaza o vence. Acepta `signal` para cancelar.

## Persistir la sesión

El almacenamiento por omisión vive en memoria y desaparece con el proceso. Para que la persona no se autentique en cada comando, entrega el tuyo:

```ts
const andco = createAndcoInstanceForCLI({ clientId, redirectTo, storage: keychain, lock: crossProcessLock });
```

`lock` importa cuando varios comandos pueden correr a la vez: los refresh tokens rotan, y dos renovaciones simultáneas invalidan una a la otra.
