# CLI y dispositivos

> Autenticar desde una terminal, con receptor loopback o con el flujo de dispositivo.


```bash
npm install @andco/sdk
```

Una herramienta de terminal tiene dos caminos, y la diferencia es si la máquina que ejecuta el comando puede abrir un navegador.

## Con navegador: receptor loopback

```ts title="src/auth.ts"
import { AndcoCliPresenter, createAndcoInstanceForCLI } from "@andco/sdk/cli";

const andco = createAndcoInstanceForCLI({
  clientId: "tu-client-id",
  // El puerto 0 pide uno libre; el SDK pone en la autorización el que realmente obtuvo.
  redirectTo: "http://127.0.0.1:0/callback",
  initialScopes: ["openid", "email", "profile"],
  presenter: new AndcoCliPresenter({
    open: (url) => open(url.href),
  }),
});

const { data: session, error } = await andco.auth.signIn();
```

El presentador levanta un servidor loopback de un solo uso: acepta exactamente la ruta registrada, comprueba el `state`, responde una vez y cierra el puerto. Escribir eso a mano son unas noventa líneas y cada atajo —aceptar cualquier ruta, saltarse el `state`, dejar el puerto abierto— es un agujero de seguridad en la herramienta de alguien.

Si no le pasas `open`, imprime la URL para que la persona la pegue.

```ts
new AndcoCliPresenter({
  print: (message) => console.log(message),
  timeoutMs: 5 * 60 * 1_000,
});
```

## Guardar la sesión entre ejecuciones

El almacenamiento por omisión vive en memoria y desaparece al terminar el proceso. Entrega el tuyo para que la persona no vuelva a autenticarse en cada comando.

```ts
import type { AndcoStorage } from "@andco/sdk";

const keychain: AndcoStorage = {
  async getItem(key) {
    return readFromKeychain(key);
  },
  async setItem(key, value) {
    await writeToKeychain(key, value);
  },
  async removeItem(key) {
    await deleteFromKeychain(key);
  },
};

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

El almacenamiento puede ser asíncrono, así que un llavero del sistema operativo o un archivo cifrado sirven igual.

Si varios comandos pueden correr a la vez, entrega también un `lock` para que solo uno renueve el token: los refresh tokens rotan, y dos renovaciones simultáneas invalidan una a la otra.

## Sin navegador: flujo de dispositivo

En un contenedor, por SSH o en un servidor de compilación no hay navegador que abrir. El flujo de dispositivo muestra un código que la persona escribe en otro equipo.

```ts
const { data: device, error } = await andco.oauth.createDeviceAuthorizationRequest({
  scopes: ["openid", "email", "profile"],
});
if (error) throw error;

console.log(`Abre ${device.verificationUriComplete} y escribe el código ${device.userCode}`);

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

`awaitDeviceAuthorization` hace el sondeo completo: respeta el intervalo que pide el servidor, obedece los avisos de ir más lento y termina cuando la autorización se aprueba, se rechaza o vence. No escribas tu propio bucle.

Se puede cancelar:

```ts
const controller = new AbortController();
const { data: session } = await andco.oauth.awaitDeviceAuthorization(device, { signal: controller.signal });
```

## Llamar a la API

```ts
const { data, error } = await andco.rest.http.GET("/accounts");
if (error) {
  console.error(error.code, error.message);
  process.exitCode = 1;
}
```

Si el error trae el permiso que faltaba, dilo: quien usa una terminal puede actuar sobre esa información.

```ts
const missing = error?.details?.["required_scope"];
if (missing) console.error(`Falta el permiso ${String(missing)}. Vuelve a autenticarte pidiéndolo.`);
```
