Metadata-Version: 2.4
Name: open-image
Version: 0.6.0
Summary: Tiny CLI for OpenAI image generation. Prompt in, PNG out. Model-agnostic.
Author-email: tvtdev94 <tvtdev94@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/tvtdev94/open-image
Project-URL: Repository, https://github.com/tvtdev94/open-image
Project-URL: Issues, https://github.com/tvtdev94/open-image/issues
Keywords: openai,image,cli,dall-e,gpt-image,generative-ai
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai>=1.0.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

<p align="center">
  <img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/logo-mark.png" alt="open-image" width="140" />
</p>

<h1 align="center">open-image</h1>

<p align="center">
  <b>Tiny CLI for OpenAI image generation. Prompt in, PNG out. Model-agnostic.</b>
</p>

<p align="center">
  <a href="https://pypi.org/project/open-image/"><img src="https://img.shields.io/pypi/v/open-image.svg?color=22d3ee&label=pypi" alt="PyPI version" /></a>
  <a href="https://pypi.org/project/open-image/"><img src="https://img.shields.io/pypi/pyversions/open-image.svg?color=8b5cf6" alt="Python versions" /></a>
  <a href="https://github.com/tvtdev94/open-image/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-22c55e.svg" alt="MIT license" /></a>
  <a href="https://github.com/tvtdev94/open-image/stargazers"><img src="https://img.shields.io/github/stars/tvtdev94/open-image?style=flat&color=d97706" alt="GitHub stars" /></a>
</p>

<p align="center">
  <img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/hero.png" alt="open-image hero" width="100%" />
</p>

---

## Why another CLI?

Every serious image-gen workflow needs a **stable, forgettable command** — one you can pipe into, script around, and re-run six months later without rewriting. The official SDKs are fine for apps; they're heavy for "just give me a PNG."

`open-image` is **~340 lines of Python, pure stdlib + `openai`** — one file for the CLI (`gen.py`), two tiny stdlib-only helpers for the Claude Code skill. No framework, no config, no lock-in to a specific model.

```bash
pip install open-image
export OPENAI_API_KEY=sk-...
open-image --prompt "a red fox in a snowy forest, cinematic"
# → /abs/path/output/20260423-223012-a1b2c3d4.png
```

That's it.

---

## Features

### Four ways to feed a prompt

<p align="center">
  <img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/feature-four-inputs.png" alt="four input methods" width="85%" />
</p>

| Method | Example |
|---|---|
| **Inline** | `open-image --prompt "a red fox in snow"` |
| **File**   | `open-image --prompt-file prompts/scene.txt` |
| **Stdin**  | `echo "a blue cat" \| open-image` |
| **Editor** | `open-image` (no args in a TTY → opens `$EDITOR`, or `notepad` on Windows, `vi` otherwise) |

The resolver picks them in that order. Lines starting with `#` in the editor buffer are stripped — write notes to yourself without polluting the prompt.

---

### Model-agnostic by design

<p align="center">
  <img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/feature-model-agnostic.png" alt="model-agnostic design" width="85%" />
</p>

`--model` is a **flag, not a constant**. The day a new image model ships, swap the string — no code change, no version bump, no fork:

```bash
open-image --model gpt-image-2   --prompt "..."   # default; requires org verification
open-image --model gpt-image-1   --prompt "..."   # transparency, output_format support
open-image --model future-model  --prompt "..."   # whenever it arrives
```

Default is `gpt-image-2`. Change per call, or `alias open-image='open-image --model gpt-image-1'` in your shell if you prefer a different default.

---

### Curated styles + aspect shortcuts

Quick prompt modifiers — no need to memorize style fragments or `--extra` JSON for common sizes:

```bash
# Style preset: appends "3D render, octane render, hyperrealistic detail, 8k" to the prompt
open-image --style 3d-render --prompt "a cat astronaut"

# Aspect shortcut: equivalent to --extra '{"size":"1024x1792"}'
open-image --portrait --prompt "a tall waterfall"

# Combine freely — portrait 3D render
open-image --style 3d-render --portrait --prompt "a cat astronaut"

# Discover what's available
open-image --list-styles
```

**8 styles:** `3d-render`, `anime`, `watercolor`, `cyberpunk`, `photoreal`, `sketch`, `oil-painting`, `minimalist`. Hard-capped at 10 forever — for more variety, use `--extra` or write style words directly in your prompt. This isn't a style library.

**3 aspect flags:** `--portrait` (1024×1792), `--landscape` (1792×1024), `--square` (1024×1024). Mutually exclusive. If you also pass `--extra '{"size":...}'`, your `--extra` value wins (explicit beats implicit). Filename slug always derives from the **original** prompt, never the style-augmented one — so filenames stay short and readable.

---

### Image edit + inpainting

Pass `--input-image` to route to OpenAI's `images.edit` endpoint instead of `images.generate`. All other flags (`--style`, aspect shortcuts, `--extra`, `--name`, `--keep`) work identically. Add `--mask` for inpainting — the mask's transparent pixels mark the regions to regenerate:

```bash
# Image-to-image (edit whole image, no mask)
open-image --input-image cat.png --prompt "give the cat a top hat"

# Inpainting (only transparent regions in mask are changed)
open-image --input-image room.png --mask window-hole.png --prompt "a sunny garden visible through the window"

# Combine with style + aspect — every existing flag still works
open-image --input-image street.png --style cyberpunk --portrait --prompt "neon-soaked rain"
```

`--mask` without `--input-image` is rejected (mask is meaningless on its own). Filename slug always derives from the prompt — never from the input image filename. Note: not every model supports edit yet — if you hit an API error, try `--model gpt-image-1`.

---

### `--extra` escape hatch

<p align="center">
  <img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/feature-extra-param.png" alt="extra param forwarding" width="85%" />
</p>

Any keyword the API accepts, `--extra` forwards verbatim to `openai.images.generate(**params)`. Zero client-side validation — the API is the source of truth:

```bash
open-image \
  --model gpt-image-2 \
  --extra '{"size":"1024x1024","quality":"high"}' \
  --prompt "a lone surfer at dawn, Hokusai woodblock style"

open-image \
  --model gpt-image-1 \
  --extra '{"size":"1024x1024","output_format":"png","transparency":true}' \
  --prompt "a minimalist cat icon on a transparent background"
```

If you pass a wrong key, the API error surfaces verbatim — exactly what you want for debugging. No wrapper in the way.

---

## Install

### From PyPI (recommended)

```bash
pip install open-image
```

### With pipx (isolated global command)

```bash
pipx install open-image
```

### From source

```bash
git clone https://github.com/tvtdev94/open-image
cd open-image
pip install -e .
```

### Upgrade later

```bash
open-image upgrade        # subcommand form
open-image --upgrade      # equivalent flag form
```

Auto-detects pipx vs pip from `sys.executable` and runs the right upgrade command. Exit code propagates from the underlying tool.

---

## Setup

Set your OpenAI API key (must have image-generation credit):

```bash
# Option A — environment variable (recommended)
export OPENAI_API_KEY=sk-...

# Option B — per-call flag
open-image --api-key sk-... --prompt "..."
```

---

## Flags

| Flag | Default | Purpose |
|---|---|---|
| `--prompt` | — | Inline prompt text |
| `--prompt-file` | — | Path to a file containing the prompt |
| `--model` | `gpt-image-2` | Any OpenAI image model (`gpt-image-2`, `gpt-image-1`, `dall-e-3`, `dall-e-2`, …) |
| `--extra` | `{}` | JSON object forwarded to `images.generate` / `images.edit` |
| `--out-dir` | `./output` | Where to save PNGs (auto-created) |
| `--api-key` | `$OPENAI_API_KEY` | Override via flag if not in env |
| `--keep` | `50` | Keep only N newest PNGs in `--out-dir` after save; `0` disables pruning |
| `--name` | auto-derived from prompt | Custom slug for output filename (kebab-case, ASCII) |
| `--style` | — | Append a curated prompt fragment (one of 8 — run `--list-styles`) |
| `--input-image` | — | Path to an existing PNG. Routes to `images.edit` for image-to-image / inpainting |
| `--mask` | — | Optional mask PNG for inpainting (requires `--input-image`) |
| `--list-styles` | — | List known styles with full fragments, then exit |
| `--portrait` | — | Shortcut for `--extra '{"size":"1024x1792"}'` (mutually exclusive with `--landscape`/`--square`) |
| `--landscape` | — | Shortcut for `--extra '{"size":"1792x1024"}'` |
| `--square` | — | Shortcut for `--extra '{"size":"1024x1024"}'` |
| `--list-models` | — | List known OpenAI image models with notes, then exit |
| `--install-skill` | — | Re-install Claude Code skill at `~/.claude/skills/open-image/` (overwrites) |
| `--upgrade` | — | Upgrade to latest PyPI release (auto-detects pipx vs pip). Equivalent: `open-image upgrade` subcommand |

---

## Output

```
./output/{YYYYMMDD-HHMMSS}-{slug}-{uuid8}.png
```

`{slug}` is auto-derived from the prompt (kebab-case, ASCII-folded, max 40 chars — handles diacritics, Vietnamese horn/stroke letters, emoji-stripped, falls back to `image`). Override with `--name "my-slug"`.

One PNG per `response.data` item (so `n=4` → four files). Absolute path(s) printed to stdout, one per line — friendly to `xargs`, `fzf`, `wl-copy`, whatever you pipe into.

```bash
open-image --prompt "a red fox in snowy forest"
# → ./output/20260426-183246-a-red-fox-in-snowy-forest-a1b2c3d4.png

open-image --prompt "anything" --name "hero-shot"
# → ./output/20260426-183246-hero-shot-a1b2c3d4.png

open-image --prompt "a corgi" | tee -a log.txt
open-image --prompt "a corgi" | head -n1 | xargs -I{} open {}    # macOS preview
```

---

## Gallery

All generated by `open-image` with `gpt-image-2`:

<p align="center">
  <img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/sample-triptych.png" alt="sample gallery" width="100%" />
</p>

<table>
  <tr>
    <td><img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/sample-bee-lotus.png" alt="bee on a lotus at sunrise" /></td>
    <td><img src="https://raw.githubusercontent.com/tvtdev94/open-image/main/assets/sample-cyberpunk-market.png" alt="cyberpunk Hanoi night market" /></td>
  </tr>
  <tr>
    <td align="center"><sub><i>A close-up cinematic macro of a bee hovering over a lotus at sunrise.</i></sub></td>
    <td align="center"><sub><i>A bustling night market in a cyberpunk Hanoi alleyway.</i></sub></td>
  </tr>
</table>

---

## Error handling

Every error path exits with a clear, actionable message:

- **No API key** → `ERROR: No API key. Set OPENAI_API_KEY env or pass --api-key.`
- **`--extra` not valid JSON** → parser error with column offset
- **Empty prompt** → `ERROR: Empty prompt.`
- **API failure** (auth, model access, invalid params) → API error string forwarded verbatim
- **Un-writable `--out-dir`** → `PermissionError` surfaced with the path

---

## Models supported

The CLI is model-agnostic — `--model` accepts any string. These are the models known at write time; pass any future model ID without a code change.

| Model | Notes |
|---|---|
| `gpt-image-2` | Default. Requires org verification on OpenAI dashboard. Returns `b64_json`. |
| `gpt-image-1` | Newer GPT image model. Supports `input_fidelity`, `transparency`, `output_format`. |
| `dall-e-3` | `n=1` only. Sizes: `1024x1024` / `1792x1024` / `1024x1792`. `quality`: `standard` / `hd`. `style`: `vivid` / `natural`. Pass `response_format=b64_json` via `--extra` for offline storage. |
| `dall-e-2` | `n>1` supported. Sizes: `256x256` / `512x512` / `1024x1024`. |

Run `open-image --list-models` to print this table at any time.

---

## Claude Code integration

If you use [Claude Code](https://claude.com/claude-code), `open-image` ships a Claude skill that teaches the agent how to use this CLI — no manual prompt setup.

- **Zero-step install:** `pip install open-image` is enough. On the next Python startup (any Python invocation on that machine — no CLI required), the skill is silently written to `~/.claude/skills/open-image/SKILL.md`. Skipped entirely if `~/.claude/` doesn't exist.
- **Auto-update on upgrade:** `pip install -U open-image` → next Python startup → skill content auto-syncs to the new version. No manual step.
- **Force re-install** (rarely needed, e.g. after editing the skill): `open-image --install-skill`.

Once installed, Claude Code knows when to call `open-image`, which models exist, how `--extra` works, and how to capture the stdout paths.

#### How it works (transparency)

`open-image` ships a tiny `.pth` file to your Python `site-packages/` so the skill is synced on every Python startup (idempotent — only writes when content changes). Cost: a couple of `stat()` calls per Python startup, sub-millisecond. Removing the package via `pip uninstall open-image` removes the `.pth` and stops the sync.

---

## Philosophy

Three principles:

- **YAGNI** — no MCP server, no HTTP wrapper, no runtime plugins. The optional Claude Code skill is just markdown — Claude reads it, no daemon, no IPC. If your agent has a shell, it can use this.
- **KISS** — argparse + stdlib + one SDK call. Zero abstractions between you and the API.
- **DRY** — `--extra` means the tool never needs a new flag per new API param.

The whole tool fits in your head. When a future model adds a parameter, you already know how to use it.

---

## License

MIT © 2026 [tvtdev94](https://github.com/tvtdev94)
