Metadata-Version: 2.4
Name: tena
Version: 0.1.1
Summary: My Text-to-Image kits
Requires-Python: >=3.10
Requires-Dist: google-genai>=2.10.0
Requires-Dist: httpx>=0.28.1
Requires-Dist: openai>=2.44.0
Requires-Dist: pytest>=9.0.2
Requires-Dist: python-dotenv>=1.2.1
Description-Content-Type: text/markdown

# tena

`tena` provides a small async wrapper for text-to-image model calls.

## Install

Prerequisites:

- Python 3.10+
- uv

Dependencies are declared in `pyproject.toml`:

- `openai`
- `google-genai`
- `httpx`
- `pytest`
- `python-dotenv`

## Image generation

### Python API

Use `draw` with a registered `model_path`. The function returns a list of
`GeneratedImage` objects. Each object contains image bytes and a MIME type.

```python
import asyncio
from pathlib import Path

from tena import draw, suffix_for_mime_type


async def main() -> None:
    images = await draw(
        model_path="openrouter/gpt-image-2",
        prompt="A clean product photo of a ceramic cup",
        size="1024x1024",
        number=1,
        input_images=[
            "./reference-1.png",
            "https://example.com/reference-2.png",
        ],
    )

    image = images[0]
    suffix = suffix_for_mime_type(image.mime_type)
    Path(f"result{suffix}").write_bytes(image.data)


asyncio.run(main())
```

The package exports the reusable API from `tena/__init__.py`. `tena/main.py` is
only the CLI entrypoint; core image generation logic lives in `tena/image.py`.

### CLI

The `tena` command is a thin wrapper around the Python API.

Generate one image and write it to a required output path:

```bash
uv run tena --model-path openrouter/gpt-image-2 --prompt "A clean product photo of a ceramic cup" --output ./result.png --size 1024x1024
```

The output path supports `~`, including `--output ~/Downloads/result.png` and
`--output=~/Downloads/result.png`.

Use `-` as the prompt to read from stdin:

```bash
echo "A simple red cube on a clean white background" | uv run tena --model-path openrouter/gpt-image-2 --prompt - --output ./result.png --size 1024x1024
```

Generate multiple images with `--number`. The first image uses the exact output
path, and later images are numbered:

```bash
uv run tena --model-path zenmux/gpt-image-2 --prompt "A blue sphere" --output ./result.png --number 2 --size 1024x1024
```

This writes `result.png` and `result-2.png`.

Provide reference images with repeated `--input-image` arguments. Each value can
be a local file path or an `http`/`https` URL:

```bash
uv run tena \
  --model-path openrouter/gpt-image-2 \
  --prompt "Create a studio product photo using the reference objects" \
  --input-image ./reference-1.png \
  --input-image https://example.com/reference-2.png \
  --output ./result.png \
  --size 1024x1024
```

URL images are downloaded to `/tmp/tena/input_images` and cached by URL, so the
same URL is reused on later runs instead of downloaded again.

CLI logs are written to stderr and default to `INFO`. The generated output paths
are still written to stdout. Use `--log-level WARNING` to hide normal progress
logs, or `--log-level DEBUG` for diagnostic details:

```bash
uv run tena --model-path openrouter/gpt-image-2 --prompt "A blue sphere" --output ./result.png --log-level WARNING
```

INFO logs include concise request metadata, URL cache/download status, byte
counts, and elapsed times. DEBUG logs include more detailed SDK and input image
diagnostics. Logs do not include API keys, full prompts, full input URLs, or
image data.

### Return object

```python
@dataclass(frozen=True)
class GeneratedImage:
    data: bytes
    mime_type: str
```

### Registered models

Models are currently registered in code. `model_path` is the lookup key, and
`model_realname` is the model id sent to the upstream API.

```python
@dataclass(frozen=True)
class ImageModel:
    model_displayname: str
    model_realname: str
    client: Literal[
        "openai",
        "openrouter",
        "gemini-interactions",
        "gemini-generate-content",
    ]
    api_key_env: str
    base_url: str | None = None
    default_mime_type: str = "image/png"
```

Current model paths:

- `302ai/gpt-image-2`
- `302ai/gemini-3.1-flash-image-preview`
- `google/gemini-3.1-flash-image`
- `openrouter/gpt-image-2`
- `zenmux/gpt-image-2`
- `zenmux/gemini-3.1-flash-image`

`client` means which upstream API contract is used. OpenAI-compatible gateways
can use `client="openai"` with a custom `base_url`. OpenRouter uses
`client="openrouter"` and calls its `/images` unified image generation endpoint
directly.

Gemini image models use two different API contracts:

- `gemini-interactions` uses Google's Interactions API and is used for direct
  Google Gemini access.
- `gemini-generate-content` uses the Generate Content API and is used for
  Gemini-compatible gateways such as Zenmux and 302AI.

### Environment variables

Set the API key required by the selected model entry. Values can be provided by
the process environment, a project-root `.env` file, or
`~/.tena/config/.env`:

- `OPENROUTER_API_KEY`
- `ZENMUX_API_KEY`
- `AI302_API_KEY`
- `GEMINI_API_KEY`

Dotenv files are loaded in this order: project-root `.env`, then
`~/.tena/config/.env`. Later values override earlier values.

### Gemini size format

Gemini image models use a single `size` string that is parsed into
the Gemini image response configuration. Interactions requests use
`response_format`; Generate Content requests use `image_config`.

- `16:9` -> `aspect_ratio`
- `2K` or `4K` -> `image_size`
- `16:9@2K` -> both `aspect_ratio` and `image_size`
- `auto` -> no explicit Gemini `response_format`

OpenAI-compatible and OpenRouter clients pass `size` through as the API `size`
parameter.

## Not implemented yet

The following parameters are part of the public function signature but are not
implemented yet:

- `web_search`

Passing `web_search` raises `NotImplementedError`.

## Integration tests

Live image generation tests are grouped under `tests/integration`. Set the API
key for the model you want to test in the environment or in the project `.env`
file:

- `OPENROUTER_API_KEY` for `openrouter/gpt-image-2`
- `ZENMUX_API_KEY` for `zenmux/gpt-image-2`

Then run:

```bash
pytest tests/integration
```

Tests with missing API keys are skipped. When a test succeeds, it writes the
generated image to `~/Downloads` with a `tena-<provider>-gpt-image-2` filename
prefix.
