gemini-image-api
by CongrooDevs
README.md
# gemini-image-api
[](LICENSE)
[]()
Servidor **MCP (stdio)** y CLI **`gia`** para generar y editar imágenes con **tu propia sesión web de Gemini** (cookies del navegador), sin API key de pago. Incluye **Musa**, una app web local para organizar y generar: galería, estilos con imagen de referencia, colecciones y generación con cola.

**Características**
- 🤖 MCP con 14 tools (`generate_image`, `edit_image`, `list_generations`, colecciones, Gems de estilo…) y CLI `gia` equivalente.
- 🖼️ Referencias locales/URL/`gen:<id>` (hasta 10, 20 MB, validadas por magic bytes y anti-SSRF), estilos con imagen de referencia, plantillas con variables, 10 proporciones.
- 🗂️ **Musa** (`gia app`): app local en `127.0.0.1` con clave por arranque — galería, generación con cola, colecciones, estilos, Gems y pantalla de cuota real.
- 🧠 **Gems de estilo**: cada estilo puede usar un Gem de tu cuenta de Gemini para salidas más consistentes.
- 🔒 Chats temporales por defecto (no ensucian tu historial de Gemini), logs sin secretos, estado local con permisos restrictivos.
Arquitectura: `agente MCP / CLI gia → capa de servicio (refs, estilos, almacenamiento, estado) → client.py (único módulo que toca gemini-webapi) → gemini.google.com`.
## ⚠️ Advertencia: Términos de Servicio y estabilidad
- Automatizar `gemini.google.com` puede quedar **fuera de los Términos de Servicio de Google**. Las consecuencias (límites, bloqueos, suspensión) recaen sobre tu cuenta.
- **Usa una cuenta Google secundaria dedicada que tenga la suscripción**, no tu cuenta principal.
- Se usa una API interna **no oficial** (`gemini-webapi`) que cambia cada 1–2 meses y puede romperse sin aviso. Por eso la versión está fijada: `gemini-webapi==2.1.1`. Tras actualizarla, ejecuta el E2E manual de abajo.
- **No uses el contenido generado para entrenar modelos**: lo prohíbe el ToS.
## Requisitos e instalación
- Python >= 3.11 y [uv](https://docs.astral.sh/uv/).
```bash
uv sync --extra test
```
Se necesita **Firefox instalado** (en macOS, `/Applications/Firefox.app`; en otro sitio, `GIA_FIREFOX_PATH`). `gia auth` lo abre como un navegador normal con un perfil propio en `<state_dir>/firefox-profile`: Google rechaza el inicio de sesión en navegadores automatizados ("This browser or app may not be secure"), y Chromium no sirve por las cookies de sesión ligadas al dispositivo.
## Primer uso
```bash
uv run gia auth # abre Firefox: inicia sesión en gemini.google.com; se cierra solo al detectar la cookie (o a los 5 min)
uv run gia gen "un gato astronauta" # genera una imagen
uv run gia auth --status # comprueba la sesión
```
Las cookies quedan en `<state_dir>/cookies/` con permisos `0600` (`<state_dir>` es `~/.gemini-image-api` por defecto; configurable con `GIA_STATE_DIR`).
## Registrar el MCP en tu agente
El mismo bloque sirve para **omp**, **Claude Code**, **Claude Desktop** y **Cursor** (en su archivo de configuración MCP respectivo):
```json
{
"mcpServers": {
"gemini-image-api": {
"command": "/ruta/absoluta/a/uv",
"args": ["run", "--project", "<ruta-al-repo>", "gia-mcp"]
}
}
}
```
Se usa `--project` y no `--directory` para que el servidor arranque siempre sobre el repo, pero **sin cambiar el directorio de trabajo del agente**: ese cwd es el "directorio de trabajo" que usa la allowlist de refs y `copy_to`. El `command` lleva la ruta absoluta de `uv` porque los agentes MCP no siempre tienen el PATH del usuario. Si `gia-mcp` está en el PATH, basta `"command": "gia-mcp"` sin `args`. Para imprimir este JSON con las rutas reales:
```bash
uv run gia mcp-config
```
## Tools MCP
Hay 14 tools: `generate_image`, `edit_image`, `retry_download`, `delete_conversation`, `auth_status`, `auth_login`, `list_styles`, `list_style_gems`, `style_gem`, `list_templates`, `list_models`, `list_generations`, `list_collections` y `add_to_collection`.
Las tools de generación (`generate_image`, `edit_image`) devuelven el contrato `{status, code, conversation_id, images[], gemini_text, elapsed_s, quota}` como `structured_content` (y el mismo JSON como bloque de texto). `quota` es `{tier, window_5h_pct, weekly_pct, resets_at_5h, resets_at_weekly}` (`tier` puede ser `null` si Google no devuelve datos). `status` es `ok`, `error` o `generated_not_saved` (se generó pero falló el guardado; en ese caso `code` también es `generated_not_saved` y el payload añade `details.reason`). Cada elemento de `images[]` incluye `path`, `width`, `height`, `bytes`, `format`, `full_size` e `index`, y además `copied_to` (o `copy_error`) cuando se usó `copy_to`. Con `return_image=true` (por defecto) se añade además un **bloque de imagen MCP** por imagen con una miniatura JPEG de 512 px (unos 40-80 KB), que los agentes multimodales ven como imagen; nunca va como base64 dentro del JSON. En error: `status: "error"`, `code`, `message`, `details` y los campos nuevos `retryable` (bool) y `next_action` (qué hacer; ver tabla de errores).
| Tool | Parámetros clave | Devuelve |
|---|---|---|
| `generate_image` | `prompt`, `refs[]`, `style`, `style_text`, `template`, `template_vars`, `aspect_hint`, `model`, `project`, `subdir`, `copy_to`, `collection`, `return_image` | Contrato de generación (chat nuevo) |
| `edit_image` | `conversation_id`, `prompt`, `refs[]`, `style`, `style_text`, `template`, `template_vars`, `aspect_hint`, `model`, `project`, `subdir`, `copy_to`, `collection`, `return_image` | Contrato de generación (continúa la conversación) |
| `retry_download` | `conversation_id`, `image_index` | `{status, code, image{path,width,height,bytes,format,full_size}}`; reintenta solo la descarga, sin regenerar ni gastar cuota |
| `delete_conversation` | `conversation_id` | `{status, code, conversation_id}`; borra en Gemini y marca en el historial local |
| `auth_status` | — | Estado de la sesión: cookies, validez, modelos, imágenes hoy, estado de la cuenta, antigüedad de la cookie (`cookie_age_s`) y cuota Google (5h/semanal %) |
| `auth_login` | — | Login guiado: abre Firefox (perfil propio), inicia sesión en gemini.google.com; el navegador se cierra solo al detectar la cookie (o tras 5 min → `auth_expired` "login timeout"). El perfil persiste, así que los siguientes logins suelen ser instantáneos |
| `list_styles` | — | `{nombre: descripción}` |
| `list_style_gems` | — | `{status, gems: [{style, gem_id, name, custom}]}`; solo lectura |
| `style_gem` | `style`, `enabled`, `instructions?` | Activa/desactiva el Gem de un estilo (ver «Gem de estilo»): `enabled=true` lo crea, o actualiza `instructions` si las pasas; `enabled=false` lo borra. Devuelve `{style, gem: {gem_id, name, custom} \| None}` |
| `list_templates` | — | `{nombre: {description, vars}}` |
| `list_models` | — | Modelos de chat de tu cuenta (el modelo determina la variante de imagen) |
| `list_generations` | `query`, `project`, `since`, `status`, `collection`, `limit` (1-100), `offset` | Generaciones pasadas (más reciente primero); sus `id` sirven como refs `gen:<id>[#index]` y sus `conversation_id` para `edit_image`. `collection` filtra solo las de esa colección |
| `list_collections` | — | Colecciones (etiquetas): `[{id, name, description, count}]` |
| `add_to_collection` | `image` (`gen:<id>[#index]`), `collection` (nombre o id; se crea si no existe) | Añade una imagen existente a una colección (idempotente) |
`refs[]` admite rutas locales (dentro de `output_root` o del directorio de trabajo), URLs http/https públicas, o `gen:<id>[#index]` de `list_generations` (máx. 10). `copy_to` es una ruta opcional (directorio o archivo) dentro de `output_root` o del directorio de trabajo donde también se copia el resultado.
`aspect_hint` admite exactamente `1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `5:4`, `4:3`, `3:2`, `16:9` y `21:9` (se traduce a texto en el prompt; Gemini lo trata como **pista** y no lo garantiza). Los mismos 10 valores valen en `--aspect` de la CLI (`gia gen`/`gia edit`). Cualquier otro valor devuelve error `invalid_aspect` (con `details.valid`).
## Comandos CLI
| Comando | Descripción |
|---|---|
| `gia gen PROMPT` | Genera imagen (chat nuevo). Opciones: `-r/--ref` (repetible), `--style`, `--style-text`, `--template`, `-v/--var k=v` (repetible), `--aspect`, `--project`, `--subdir`, `--copy-to`, `--collection`, `--model` |
| `gia edit CONVERSATION_ID PROMPT` | Edita la imagen de una conversación existente (mismas opciones que `gen`, incluidos `--copy-to` y `--collection`) |
| `gia auth [--status]` | Login guiado (Firefox) o comprobación de sesión |
| `gia styles` | Lista estilos y plantillas |
| `gia styles gems` | Lista los Gems de estilo activos: `[{style, gem_id, name, custom}]` |
| `gia styles gem ESTILO on\|off [--instructions TEXTO]` | Activa (`on`) o desactiva (`off`) el Gem de un estilo (ver «Gem de estilo»); `--instructions` fija instrucciones personalizadas (cadena vacía: restaura la plantilla) |
| `gia history` | Generaciones pasadas (más reciente primero); sus id sirven como refs `gen:<id>[#index]`. Opciones: `--query`, `--project`, `--since`, `--status`, `--limit` (20), `--offset` |
| `gia models` | Modelos de chat disponibles en tu cuenta |
| `gia retry-download CONVERSATION_ID IMAGE_INDEX` | Reintenta solo la descarga |
| `gia delete CONVERSATION_ID` | Borra la conversación en Gemini y en el historial local |
| `gia mcp-config` | Imprime el JSON para registrar el MCP |
| `gia app` | Abre Musa, la galería local (ver "Musa: galería local"). Opciones: `--port`, `--no-open` |
| `gia collections [add IMAGE COLLECTION]` | Lista colecciones (etiquetas) con sus conteos, o añade una imagen (`gen:<id>[#index]`) a una colección (se crea si no existe) |
| `gia serve` | Ejecuta el servidor MCP por stdio |
## Musa: galería local
**Musa** es una pequeña app web local para ver, organizar y reutilizar tus imágenes generadas: galería en mosaico, estilos con imagen de referencia y colecciones (etiquetas).
```bash
uv run gia app # abre el navegador en la URL de Musa
uv run gia app --port 9000 --no-open # puerto propio, sin abrir el navegador
```
La app escucha **solo en `127.0.0.1`** (nunca se expone a la red) y la URL lleva una **clave secreta** (`http://127.0.0.1:<port>/?key=...`): al entrar con la clave en la URL se fija como cookie `gia_key` (httponly, samesite=strict) y toda petición sin ella devuelve 401; las peticiones que modifican datos exigen además la cabecera `Origin` correcta. La URL completa se imprime en stderr y queda registrada en `<state_dir>/app.json` (con `url`, `port`, `pid`); `auth_status` la devuelve como `app_url`.
Con `GIA_APP_WITH_MCP=true`, el servidor MCP (`gia-mcp`) arranca Musa en el mismo proceso (en `GIA_APP_PORT`, 8765 por defecto, sin abrir navegador; stdout se mantiene puro JSON-RPC). Si ya hay una instancia corriendo o el puerto está ocupado, el MCP sigue sin ella.
Pantallas:
- **Pantalla de carga**: se muestra siempre al abrir la app (láminas de la Encyclopédie y obras de dominio público); se puede volver a ver desde el logo y desde «Pantalla de carga» en la barra lateral.
- **Inicio**: banner con crédito, «Empieza con un estilo», «Recientes» (pendientes y últimas generaciones) y panel de pendientes.
- **Estilos**: tarjetas de los estilos del catálogo con su imagen de portada; al elegir uno queda seleccionado para el compositor.
- **Galería**: mosaico con las tarjetas de pendientes al principio, filtros (Todas · En colección · Sin colección · Favoritas), búsqueda por texto y filtro por estilo; al agotar la lista, carga más automáticamente.
- **Colecciones**: tarjetas con portadas (hasta 3) y detalle de colección (banner, pestañas Imágenes/Actividad y panel lateral); creación, renombrado y borrado (borrar la colección conserva las imágenes).
- **Límites de uso**: datos reales de tu cuenta de Gemini (ver más abajo).
En el **panel de detalle** de cada imagen: marcar/desmarcar favorita, copiar su referencia (`gen:<id>[#index]`), descargar, revelar en el Finder (solo macOS), crear un estilo nuevo a partir de ella, añadirla/quitarla de colecciones y **borrarla: el borrado elimina el archivo del disco** (y su miniatura) y la marca como borrada en la galería; no se puede deshacer.
**Generar desde la app** (compositor fijo en todas las pantallas: prompt, estilo, proporción, modelo, referencias, variaciones, «Guardar en» y bandeja de cola; `POST /api/jobs`):
- Las generaciones se procesan **una a una** contra Gemini; como máximo hay 20 trabajos pendientes (`MAX_PENDING` en `jobs.py`), contando los que están en cola y el que está en curso.
- **Variaciones (− N +, de 1 a 8)**: un envío con N crea N trabajos en cola y **cada uno gasta cuota** de Gemini.
- Solo se puede **cancelar lo que está en cola**; un trabajo en curso no se cancela (`409 job_running`) porque Gemini ya está gastando la cuota.
- Hasta **3 referencias** por generación (`MAX_REFS_PER_JOB` en `jobs.py`), de la galería (`gen:<id>#<i>`) o subidas (botón +, arrastrar o pegar; `POST /api/uploads`, máx. 20 MB). Las subidas se guardan con permisos `0600` y **se borran a las 24 h**.
- La cola vive solo en memoria: **no persiste al reiniciar la app** (lo que estaba en cola se pierde; lo que estaba en curso queda registrado por el backend).
- Modelos disponibles: Auto, Gemini Flash y Gemini Pro (`MODELS` en `jobs.py`).
**Límites de uso** muestra la cuota real de Gemini (`GET /api/quota`, `GET /api/usage`): % libre de la ventana de 5 h y su hora de renovación, **estimación de imágenes restantes** en la ventana (calculada del historial + % usado; campo `est_images_left_5h`), uso semanal con su renovación, estado del servicio, «1 a la vez · N en cola», tamaño de la galería frente al espacio libre del disco, imágenes por día (últimos 7 días) y tabla de actividad (últimas 20 generaciones con modelo, estilo, tiempo y estado).
**Estilos**: el catálogo no trae estilos integrados; cada uno crea los suyos en Musa («Mis estilos») de 3 formas: solo texto (se añade al prompt), desde una imagen de la galería o subiendo un archivo (png/jpeg/webp/gif/svg, máx. 20 MB; los SVG se convierten a PNG). Si un estilo tiene imagen, al generar con él esa imagen se envía a Gemini **como primera ref** y **cuenta para el límite de 10 refs** (`GIA_MAX_REFS`). Los estilos propios se borran con el botón ✕ de la tarjeta (siempre visible). Se guardan en `<state_dir>/styles/styles.yaml`.
**Plantillas** incluidas: `anuncio_producto`, `post_redes` (1:1), `banner_web` (16:9) y `ficha_producto`; salen en `list_templates` (MCP) y en la CLI.
**Colecciones (etiquetas)**: organizan las imágenes sin mover los archivos. Desde la app se crean, renombran, borran y se añaden imágenes; desde el agente: `collection=` en `generate_image`/`edit_image` (se crea si no existe), `collection` como filtro en `list_generations`, y las tools `list_collections` y `add_to_collection`. Por CLI: `--collection` en `gia gen`/`gia edit` y `gia collections` / `gia collections add <gen:<id>[#index]> <colección>`.
## Estilos y plantillas
El catálogo se carga del primer archivo que exista en este orden de búsqueda:
1. la ruta indicada en `GIA_STYLES_FILE`;
2. `~/.config/gemini-image-api/styles.yaml`;
3. el `styles.yaml` empaquetado dentro del paquete (`src/gemini_image_api/styles.yaml`).
Para personalizar estilos o plantillas, **copia el archivo empaquetado a `~/.config/gemini-image-api/styles.yaml`** (o apunta `GIA_STYLES_FILE` a otro archivo). Tiene dos secciones:
```yaml
styles: {} # sin estilos integrados; los tuyos se crean en Musa o aquí
templates:
post_redes:
description: "Post cuadrado 1:1 para redes sociales"
prompt: "Generate an image (do not search the web): square 1:1 social media post about {tema}, {estilo} style, clean composition, space for text overlay"
vars: [tema, estilo]
```
```bash
uv run gia styles
uv run gia gen "" --template post_redes -v tema="lanzamiento de producto" -v estilo="fotorealista"
```
## Gem de estilo
Cualquier estilo (del catálogo o tuyo) puede tener un **Gem** asociado en tu cuenta de Gemini: instrucciones de sistema persistentes que hacen las generaciones de ese estilo más consistentes. Se activa **estilo por estilo**, bajo demanda:
- Desde **Musa**: el interruptor «Usar Gem» de cada tarjeta de estilo; al activarlo se crea el Gem y al desactivarlo se borra. La insignia «Gem» y el enlace «Instrucciones» abren un diálogo para editarlas.
- Desde el **agente MCP**: `style_gem(style, enabled, instructions?)` para activar/desactivar/editar y `list_style_gems()` para listarlos.
- Por **CLI**: `gia styles gem <estilo> on|off [--instructions TEXTO]` y `gia styles gems`.
El Gem se crea con el nombre **«Musa · <nombre del estilo»** (60 caracteres como máximo) y con una plantilla de instrucciones fija (en inglés) rellenada con la descripción y las reglas visuales del estilo. Si editas las instrucciones, se guardan como personalizadas (`custom: true`) y dejan de regenerarse a partir del estilo; pasar una cadena vacía restaura la plantilla. Los Gems activos se guardan en `<state_dir>/gems.json`.
Al generar con un estilo que tiene Gem, su `gem_id` se envía a Gemini automáticamente (y queda registrado en el historial; `edit` lo reutiliza). Si el Gem **ya no existe en tu cuenta de Gemini**, la imagen se genera igualmente **sin él** y la respuesta incluye un aviso `warning` con `code: "gem_missing"` (en Musa se muestra el aviso «El Gem de <estilo> ya no existía; se generó sin él» y el interruptor queda apagado).
## Configuración
Archivo opcional `~/.config/gemini-image-api/config.yaml`; las variables de entorno `GIA_*` tienen prioridad sobre él.
| Variable | Por defecto | Descripción |
|---|---|---|
| `GIA_STATE_DIR` | `~/.gemini-image-api` | Cookies, historial, logs, locks |
| `GIA_OUTPUT_ROOT` | `~/Pictures/gemini-image-api` | Raíz de las imágenes generadas |
| `GIA_QUOTA_STOP_PCT` | `90` | Si la cuota 5h o semanal supera este %, la llamada se detiene con `quota_guard` antes de llegar a Gemini; `0` desactiva el guardián |
| `GIA_GEN_TIMEOUT_S` | `300` | Timeout de generación |
| `GIA_DOWNLOAD_TIMEOUT_S` | `60` | Timeout de descarga |
| `GIA_MAX_REFS` | `10` | Máximo de imágenes de referencia |
| `GIA_ALLOWED_REF_DIRS` | vacío (output_root + cwd si es utilizable) | Directorios permitidos para refs locales, separados por `:`. Vacío = `output_root` + el directorio de trabajo, solo si este no es `/` y es escribible |
| `GIA_WRITE_SIDECAR` | `false` | Escribe un `.json` junto a cada imagen |
| `GIA_DEFAULT_MODEL` | `gemini-flash` | Modelo de chat por defecto |
| `GIA_STYLES_FILE` | ninguno | Archivo de estilos alternativo; si no se define se sigue el orden de búsqueda de la sección "Estilos y plantillas" |
| `GIA_APP_PORT` | `8765` | Puerto de Musa, la app web local (`gia app`, `GIA_APP_WITH_MCP`) |
| `GIA_APP_WITH_MCP` | `false` | Si es `true`, `gia-mcp` arranca también Musa en el mismo proceso |
| `GIA_TEMP_CHATS` | `true` | Genera en chats temporales de Gemini: no quedan en el historial de chats de gemini.google.com (el `edit` sigue funcionando). `false` para dejarlos visibles |
Otras: `GIA_MAX_REF_BYTES`, `GIA_MIN_DELAY_S`, `GIA_THUMB_SIZE`, `GIA_FIREFOX_PATH` (binario de Firefox para `gia auth`).
## Salida y estado
- Imágenes en `<output_root>/<project>/<subdir>/` con nombre `YYYYMMDD-HHMMSS_slug_index.ext` (`<output_root>` es `~/Pictures/gemini-image-api` por defecto).
- Con `return_image` se crea una miniatura JPEG (`GIA_THUMB_SIZE`, 512 px por defecto) que viaja como bloque de imagen MCP; los PNG llevan metadatos `tEXt` (prompt, modelo, conversation_id, hashes de refs).
- Sidecar `.json` opcional (`GIA_WRITE_SIDECAR=true`).
- Historial en `<state_dir>/generations.jsonl` (por defecto `~/.gemini-image-api/generations.jsonl`): de ahí sale el `conversation_id` para `gia edit`, `retry-download` y `delete`, y los id para refs `gen:<id>` (`gia history` / `list_generations`).
- Logs en `<state_dir>/logs/gia.log` (rotativo) y en stderr, con los secretos de sesión (`__Secure-*`, `SAPISID*`, `Cookie:`, ruta de caché de cookies) redactados. La caché de cookies refrescadas de `gemini-webapi` se guarda en `<state_dir>/cookies/upstream_cache/` (no en `$TMPDIR`): se escribe al terminar cada comando CLI y cuando el cliente del servidor MCP se cierra solo tras 15 min de inactividad. Los logs de upstream van a `<state_dir>/logs/upstream.log` (nivel WARNING, rotación 1 MB, retención 3 archivos), también redactados.
## Seguridad
- **Allowlist de refs locales** (`GIA_ALLOWED_REF_DIRS`; por defecto `output_root` más el directorio de trabajo, solo si este no es `/` y es escribible).
- Validación por **magic bytes**, no por extensión.
- Límites: **20 MB** por ref y **10 refs** por petición.
- **Anti-SSRF** en refs por URL (solo http/https; bloquea IPs privadas/locales, validando también redirecciones).
- Cookies con permisos `0600`, directorios `0700`.
- Los logs no contienen secretos (cookies/tokens).
## Errores
Todas las respuestas de error incluyen `code`, `message`, `details`, `retryable` (si el error es transitorio y conviene reintentar) y `next_action` (qué hacer a continuación). Hay 22 códigos:
| `code` | `retryable` | Significado / qué hacer |
|---|---|---|
| `auth_expired` | no | Sesión caducada (o modo invitado detectado): ejecuta `gia auth` / `auth_login` y reintenta |
| `usage_limit` | no | Límite de uso de la cuenta alcanzado: para y espera a que se reinicie la ventana de cuota |
| `temporarily_blocked` | sí | Bloqueo temporal de Google: espera varios minutos; no reintentes en bucle |
| `no_image` | no | Gemini respondió sin imagen: lee `gemini_text` y reformula el prompt |
| `timeout` | sí | Se agotó el tiempo: reintenta una vez; si se repite, simplifica el prompt o reduce refs |
| `generated_not_saved` | sí | Se generó pero no se pudo guardar: usa `retry_download` (sin coste de cuota); ver `details.reason` |
| `content_blocked` | no | Contenido rechazado por las políticas de Gemini: cambia el prompt o las refs |
| `invalid_ref` | no | Ref inválida (formato, tamaño, magic bytes): ruta local, URL https o `gen:<id>[#index]` de `list_generations` |
| `ref_not_allowed` | no | Ref local fuera de la allowlist (usa `output_root` o el directorio de trabajo) |
| `ssrf_blocked` | no | URL de ref bloqueada por anti-SSRF (IP privada/local): usa una URL https pública |
| `unknown_style` | no | Estilo o plantilla inexistente: usa `list_styles` / `list_templates` (`details.valid`) |
| `template_missing_var` | no | Falta una variable de la plantilla: pásalas todas en `template_vars` (`details.required`) |
| `unknown_conversation` | no | `conversation_id` no está en el historial: búscalo con `list_generations` |
| `unknown_model` | no | Modelo no disponible en tu cuenta: elige uno de `list_models` |
| `auth_failed` | no | Falló el login guiado (p. ej. Firefox no encontrado: instálalo o usa `GIA_FIREFOX_PATH`) |
| `upstream_error` | sí | Error no clasificado de la API de Gemini: reintenta más tarde; si persiste, `auth_status` |
| `quota_guard` | sí | El guardián de cuota (`GIA_QUOTA_STOP_PCT`) detuvo la llamada antes del límite de Google: espera hasta `details.resets_at` |
| `path_not_allowed` | no | `copy_to` debe apuntar dentro de `output_root` o del directorio de trabajo |
| `invalid_aspect` | no | `aspect_hint` inválido: usa uno de `details.valid` (`1:1`, `4:5`, `3:4`, `2:3`, `9:16`, `5:4`, `4:3`, `3:2`, `16:9`, `21:9`) |
| `invalid_collection` | no | Nombre de colección vacío o ya existente: elige otro nombre o usa la existente |
| `unknown_collection` | no | Colección desconocida: usa `list_collections` / `gia collections` (`details.valid`) |
| `invalid_style` | no | Estilo inválido: falta el nombre o la imagen subida no es png/jpeg/webp/gif/svg de menos de 20 MB (el SVG se convierte a PNG) |
## E2E manual mínimo tras actualizar gemini-webapi
Nunca se ejecuta en los tests (no hay red). Tras cambiar la versión pinneada:
```bash
uv run gia auth # login y cookies
uv run gia gen "un gato astronauta" # anota el conversation_id
uv run gia edit <conversation_id> "ponle un casco rojo"
```
Verifica que ambas llamadas devuelven imágenes guardadas en el `output_root` (`~/Pictures/gemini-image-api` por defecto; la base probada es `2.1.1`).
## Tests
```bash
uv run pytest -q
```
Los tests nunca tocan la red; `gemini-webapi` está mockeada.
## Known deviations from v1 spec
- **`model_downgraded`**: no se detecta si Gemini cambia de modelo por su cuenta. El fallback a **modo invitado** (solo flash-lite disponible) sí se detecta al iniciar el cliente (`auth_expired` "session is in guest mode"), y ante un `AuthError` el cliente se reconstruye una vez desde las cookies y se reintenta la llamada.
- **Fases de progreso parciales**: se emiten `generating` y `downloading`; faltan `waiting-lock` y `upload-refs`.
- **Plazo total por llamada no estricto**: el rebuild de auth puede duplicar `gen_timeout_s`, y cada imagen puede gastar hasta 2×`download_timeout_s` (descarga completa + preview).
## Roadmap v2
- Recursos MCP `image://`.
- `serve --http`.
- Almacenamiento de cookies en keyring.
- Fallback a la API oficial de pago.
## Licencia
**AGPL-3.0** (obligada por la licencia de `gemini-webapi`).
## Créditos y licencias de recursos
- **Instrument Sans**: tipografía de la app web Musa, bajo **SIL Open Font License 1.1** (`src/gemini_image_api/web/assets/fonts/OFL.txt`).
- Arte de dominio público usado en la pantalla de carga y el banner de Musa (Wikimedia Commons).
- **Logo de Musa** (`web/assets/logo-128.jpg`, favicon `web/assets/favicon.png`): generado con IA por el usuario a partir de una ilustración de referencia.
- **Figuras de la Encyclopédie de Diderot y d'Alembert** (láminas «Dessein», 1762–1772, dominio público) en la pantalla de carga de Musa.