Metadata-Version: 2.5
Name: dungle-scrubs-image-generation
Version: 0.1.0
Summary: Standalone multi-provider image generation CLI and library
Requires-Python: >=3.13
Requires-Dist: fal-client>=0.5.0
Requires-Dist: google-genai>=1.0.0
Requires-Dist: openai>=1.0.0
Requires-Dist: pillow>=10.0.0
Description-Content-Type: text/markdown

# Image Generation MCP

Generate and edit images using Gemini "nano banana" (gemini-3-pro-image-preview),
OpenAI GPT Image, and fal.ai Flux (text-to-image and image-to-image).

**Model aliases:** "nano banana" / "nano-banana" → gemini-3-pro-image-preview

## When to Use

- Creating AI-generated images from text prompts
- Editing existing images with AI instructions
- Generating UI mockups, illustrations, or concept art
- Creating marketing images or visual assets
- Image-to-image transformations
- Batch generating multiple images cost-efficiently

## When NOT to Use

- Taking screenshots of websites (use Playwright)
- Exporting designs from Figma (use Figma MCP)
- Visual regression testing (use image-diff)
- Photo editing that requires precise control (use dedicated tools)

## Architecture

```
+------------------+     stdin/stdout      +------------------+
|   Claude Code    | <---- JSON-RPC -----> |  Image Gen MCP   |
|   (tool-proxy)   |                       |  (subprocess)    |
+------------------+                       +------------------+
                                                   |
                                     +-------------+-------------+
                                     v HTTPS        v HTTPS       v HTTPS
                              +------------+  +------------+  +-----------+
                              | Gemini API |  | OpenAI API |  | fal.ai    |
                              +------------+  +------------+  +-----------+
```

## Authentication

Set at least one provider API key:

```bash
# Google Gemini (default — best general quality)
GEMINI_API_KEY=your_api_key_here

# OpenAI GPT Image (auto-selected for text-heavy images)
OPENAI_API_KEY=sk-...

# fal.ai Flux (auto-selected for batch generation, cheapest)
FAL_KEY=your_fal_key_here
```

Gemini key: https://aistudio.google.com/app/apikey
OpenAI key: https://platform.openai.com/api-keys
fal.ai key: https://fal.ai/dashboard/keys

## Available Tools (17 tools)

### Generation
- **generate_image** — Create image from text prompt with aspect ratio control
- **batch_generate_images** — Generate multiple images from prompts or templates
- **edit_image** — Modify existing image using AI instructions
- **generate_with_references** — Generate with style, character, and object references

### Element and Background Editing
- **remove_element** / **add_element** / **replace_element** — Targeted image edits
- **remove_background** / **replace_background** — Subject isolation and background swaps

### Specs and Sessions
- **generate_from_spec** — Generate from a JSON image spec
- **save_spec** / **load_spec** / **list_specs** — Persist reusable specs
- **get_generation_job** — Inspect persisted job status, events, and result
- **start_session** / **refine_image** / **end_session** — Multi-turn refinement

## Jobs and Streaming Progress

Provider-backed tools such as `generate_image`, `edit_image`, and
`batch_generate_images` create a persisted job record and return `job_id` in the
final result. Job records are stored under `IMAGE_GENERATION_STATE_DIR` when set,
otherwise under the platform state directory (`$XDG_STATE_HOME/image-generation`
or `~/.local/state/image-generation`).

The Python API accepts an optional event sink:

```python
from image_generation.image_generation_session import ImageGenerationSession

def handle_event(event):
    print(event.to_dict())

session = ImageGenerationSession(interactive=False, event_sink=handle_event)
session.call_tool("generate_image", {"prompt": "A clean product photo"})
```

The tool-proxy adapter maps these events to structured stderr stream chunks, so
compatible clients can show progress while the blocking tool call is running.
Use `get_generation_job` with the returned `job_id` to inspect the durable event
history after completion.

## Model Options and Costs

| Model | Alias | Provider | Cost/Image | Best For |
|-------|-------|----------|------------|----------|
| `gemini-3-pro-image-preview` | **nano-banana** | Gemini | ~$0.04 | **Default.** Multi-reference, iterative editing, general quality |
| `flux-schnell` | | fal.ai | ~$0.003 | Batch, product images, cheapest |
| `flux-pro` | | fal.ai | ~$0.05 | Higher quality fal.ai generation |
| `chatgpt-image-latest` | | OpenAI | ~$0.08 | Highest quality, best text rendering |
| `gpt-image-1` | | OpenAI | ~$0.04 | Good quality |
| `gpt-image-1-mini` | | OpenAI | ~$0.02 | Fastest OpenAI option |

## Smart Routing

When no explicit `model` is provided, the router picks the best provider
automatically using this priority:

1. **Explicit model** — always wins
2. **Explicit use_case** — routes to a known-good provider
3. **Prompt analysis** — detects text-heavy prompts and routes to OpenAI
4. **Batch context** — batch_generate_images defaults to flux-schnell
5. **Default** — single generations default to **nano-banana** (Gemini)

### use_case Routing

| use_case | Routed To | Rationale |
|----------|-----------|-----------|
| `batch` | `flux-schnell` | Cheapest per-image cost |
| `product` | `flux-schnell` | Fast, cost-efficient |
| `quality` | `gemini-3-pro-image-preview` | Best multi-reference support |
| `text` | `chatgpt-image-latest` | Best text rendering |
| _(unspecified)_ | `gemini-3-pro-image-preview` | Best general quality |

### Automatic Text Detection

The router analyzes the prompt for signals that the image needs readable text
rendered in it (e.g. "with the text", "that says", "typography", "poster",
"business card"). When detected, it auto-routes to `chatgpt-image-latest` which has
the best text rendering of any model.

### Routing Metadata

Every response includes `routing_reason` explaining why a model was chosen:
- `"explicit"` — user specified the model
- `"use_case=quality"` — routed by use_case
- `"text_detected: 'with the text'"` — prompt text analysis
- `"batch_default"` — batch context
- `"default_quality"` — no signals, used nano-banana

### When to Ask the User

**IMPORTANT for calling agents:** If the user's request is ambiguous about
quality expectations, ask before generating. Specifically:

- User asks for a "poster" or "banner" without specifying text content →
  ask if it needs text rendered in it (OpenAI) or is purely visual (Gemini)
- User asks for "logo" → ask if text/wordmark is needed (OpenAI) or
  it's an icon/symbol only (Gemini)
- User says "quick image" or "just a rough idea" → suggest flux-schnell
  to save cost, but confirm
- User doesn't specify quality expectations for a non-trivial image →
  default to nano-banana but mention the choice

Do NOT ask when:
- The prompt clearly describes a visual scene with no text → use nano-banana
- The prompt explicitly mentions text to render → use chatgpt-image-latest
- The user specified a model or use_case → honor it
- It's a batch operation → use flux-schnell

An explicit `model` parameter always overrides automatic routing.

## Usage

### Session Script

```bash
image-generation
```

### Generate Image

```python
session.call_tool("generate_image", {
    "prompt": "A minimalist logo for a tech startup, blue and white colors",
    "aspect_ratio": "1:1",
    "output_dir": "./generated-images",
    "goal": "logo"  # Creates logo_01.jpg, logo_02.jpg, etc.
})
```

### Smart Routing Examples

```python
# Auto-routes to nano-banana (default quality)
session.call_tool("generate_image", {
    "prompt": "Detailed illustration of a fantasy castle",
})
# routing_reason: "default_quality"

# Auto-detects text rendering → routes to OpenAI
session.call_tool("generate_image", {
    "prompt": "A poster with the text 'HELLO WORLD' in bold",
})
# routing_reason: "text_detected: 'with the text'"

# Explicit use_case override
session.call_tool("generate_image", {
    "prompt": "Product photo of a coffee mug",
    "use_case": "product"
})
# routing_reason: "use_case=product"
```

### Batch Generate Images

```python
# From a list of prompts
session.call_tool("batch_generate_images", {
    "prompts": [
        "A red sports car",
        "A blue sedan",
        "A green SUV"
    ],
    "output_dir": "./car-images"
})

# From a template with variables
session.call_tool("batch_generate_images", {
    "prompt_template": "A photo of a {plant_name} in a {pot_style} pot",
    "variables": [
        {"plant_name": "cactus", "pot_style": "terracotta"},
        {"plant_name": "fern", "pot_style": "ceramic"},
        {"plant_name": "succulent", "pot_style": "modern"}
    ],
    "output_dir": "./plant-images"
})
# Returns manifest: [{"prompt": "...", "file": "/path/to/image.png", "model": "flux-schnell", "provider": "fal"}, ...]
```

### Edit Image

```python
session.call_tool("edit_image", {
    "prompt": "Change the background to a sunset beach",
    "image_path": "./original.jpg",
    "output_dir": "./edited-images",
    "goal": "sunset-version"
})
```

## Parameters

### generate_image

| Parameter | Required | Description |
|-----------|----------|-------------|
| prompt | Yes | Text description of desired image |
| reference_image | No | Single reference image path for style guidance |
| thought_signatures | No | Path to .sig file or array of signatures for reproducible variations |
| aspect_ratio | No | "1:1", "16:9", "9:16", "4:3", "3:4", "2:3", "3:2", "21:9" |
| image_size | No | "1K", "2K", "4K" (Gemini); mapped to quality for OpenAI |
| output_dir | No | Directory for output files |
| goal | No | Name prefix for auto-incrementing filenames |
| model | No | See Model Options table above |
| use_case | No | Routing hint: batch, product, quality, text |

### batch_generate_images

| Parameter | Required | Description |
|-----------|----------|-------------|
| prompts | No* | List of prompt strings |
| prompt_template | No* | Template with {variable} placeholders |
| variables | No* | List of dicts for template substitution |
| model | No | Model name (defaults to flux-schnell) |
| output_dir | No | Directory for output files |
| use_case | No | Routing hint for auto model selection |

\* Either `prompts` or both `prompt_template` and `variables` must be provided.

### edit_image

| Parameter | Required | Description |
|-----------|----------|-------------|
| prompt | Yes | Instructions for how to edit the image |
| image_path | Yes | Path to input image |
| thought_signatures | No | Path to .sig file or array of signatures for reproducible variations |
| image_size | No | "1K", "2K", "4K" |
| output_dir | No | Directory for output files |
| goal | No | Name prefix for auto-incrementing filenames |
| model | No | gemini-3-pro-image-preview (default), chatgpt-image-latest, gpt-image-1, gpt-image-1-mini |

## Thought Signatures

Thought signatures enable reproducible variations of generated images. When
you generate an image, a `.sig` sidecar file is saved alongside the image
containing base64-encoded tokens that capture the model's internal state.

To create variations:

1. Generate an initial image — a `.sig` file is saved (e.g., `car-original_01.sig`)
2. Pass that file path to subsequent generations with a modified prompt
3. The model will maintain consistency while applying your changes

```python
# Initial generation - saves car-original_01.jpg and car-original_01.sig
result1 = session.call_tool("generate_image", {
    "prompt": "A red sports car in a city",
    "goal": "car-original"
})
sig_file = result1.get("thought_signatures_file")

# Variation with same "thought"
result2 = session.call_tool("generate_image", {
    "prompt": "A blue sports car in a city",
    "thought_signatures": sig_file,
    "goal": "car-blue"
})
```

## Output Files

Images are saved with auto-incrementing filenames:

- With goal: `{goal}_01.jpg`, `{goal}_02.jpg`, etc.
- Without goal: `image_01.jpg`, `image_02.jpg`, etc.

A `.sig` sidecar file is saved alongside each image containing thought
signatures for reproducible variations.

## Limitations

- Requires at least one valid API key (Gemini, OpenAI, or fal.ai)
- Subject to each provider's content policies
- Image quality depends on prompt specificity
- Edit quality depends on source image and instruction clarity
- fal.ai Flux models do not support image editing (generate only)
- Batch concurrency limited to 5 parallel requests

## Best Practices

1. **Be specific in prompts**: "A watercolor painting of a serene mountain
   lake at dawn, soft pastel colors" beats "a lake"
2. **Use goal names**: Helps organize multiple generations
3. **Iterate**: Generate multiple versions and refine prompts based on results
4. **For edits**: Reference specific elements you want changed
5. **For batch**: Batch defaults to `flux-schnell` (~$0.003/image) automatically
6. **Trust the router**: The router auto-detects text-heavy prompts (OpenAI)
   and defaults to nano-banana (Gemini) for everything else. Only set `model`
   or `use_case` when you need to override
7. **Ask when ambiguous**: If the user hasn't specified quality expectations
   and it's unclear whether text needs to be rendered, ask before generating
