{"slug": "show-hn-generate-images-from-the-cli-with-a-chatgpt-subscription-no-api-key", "title": "Show HN: Generate images from the CLI with a ChatGPT subscription (no API key)", "summary": "A new open-source Python CLI tool called chatgpt-imagegen allows users to generate images from the command line using a ChatGPT subscription without needing an API key. The tool supports two backends—one driving a logged-in Chrome browser and another using the Codex CLI—and works with free-tier accounts. It provides a zero-dependency, single-file solution for developers and AI agents.", "body_md": "**English** | [中文](/leeguooooo/chatgpt-imagegen/blob/main/README.zh-CN.md)\n\n**Generate images using your ChatGPT subscription — no OPENAI_API_KEY needed.**\n\nA tiny, zero-dependency Python CLI (and AI-agent skill) — one file, stdlib only — that generates images with your ChatGPT account, on the command line and for any AI agent.\n\n✨ Works on a free ChatGPT account too.The default`web`\n\nbackend just drives the normal ChatGPT web chat, whereeven free-tier users get image generation— so no paid plan, no API key, and no Codex required (subject to the free tier's daily image limit). Paid plans simply get higher limits.\n\n``` php\nchatgpt-imagegen \"a watercolor cat sitting on a windowsill\" -o cat.png\n# -> saved: cat.png  (812,344 bytes)  size=1024x1024  quality=medium\n```\n\nOpenAI offers image generation in two completely separate ways:\n\n| Path | What you pay | How |\n|---|---|---|\nDirect API (`/v1/images/generations` ) |\nper-image, on top of an `OPENAI_API_KEY` |\ncurl / OpenAI SDK / etc. |\nChatGPT subscription (Plus / Pro / Team) |\nflat monthly fee | ChatGPT web/desktop app, or the Codex CLI's built-in `image_gen` |\n\nThe **subscription path is invisible** to people who don't use the Codex CLI. It runs on ChatGPT's internal `backend-api/codex/responses`\n\nendpoint as a Responses-API tool, authenticated by the OAuth token written into `~/.codex/auth.json`\n\nwhen you run `codex login`\n\n.\n\n`chatgpt-imagegen`\n\nexposes that capability on the command line and to any AI agent — with **two backends** that hit different parts of your subscription.\n\nThe same subscription meters two separate buckets, and which one you spend depends on *where* the image is generated:\n\n| Backend | How it generates | Bucket spent | Needs |\n|---|---|---|---|\n`web` |\nDrives your already-logged-in ChatGPT browser (via\n`chrome-use` |\nChatGPT conversation — does not touch your metered Codex-usage limit. |\nAny logged-in chatgpt.com browser (free tier works) + `chrome-use` . |\n`codex` |\nHeadless POST to `backend-api/codex/responses` , reusing `~/.codex/auth.json` . |\nCodex-usage (the metered bucket). |\n`codex login` . |\n\n**Default auto** tries\n\n`web`\n\nfirst (to spare Codex-usage) and falls back to `codex`\n\nwhen no logged-in browser is reachable. Force one with `--backend web`\n\n/ `--backend codex`\n\n(or `CHATGPT_IMAGEGEN_BACKEND`\n\n).**Laptop / desktop**(Chrome open + signed in) →`web`\n\n— no Codex-usage spent.**Server / headless agent box**→`codex`\n\n— no browser there, so`auto`\n\nfalls back on its own.\n\n`web`\n\ngenerates under **whatever account that browser is logged into**, which may differ from `~/.codex/auth.json`\n\n— sign the browser into the account whose bucket you want.\n\nYou need Python 3.10+, a ChatGPT subscription, and **at least one backend** (`auto`\n\nuses whichever is set up, preferring `web`\n\n):\n\n** codex backend** —\n\n`npm i -g @openai/codex`\n\nthen `codex login`\n\n(writes `~/.codex/auth.json`\n\n).** web backend** —\n\n[(formerly](https://github.com/leeguooooo/chrome-use)\n\n`chrome-use`\n\n`agent-browser-stealth`\n\n; it drives your real logged-in Chrome via an extension, which is what passes Cloudflare + ChatGPT's anti-bot check) connected to a Chrome signed in to chatgpt.com:\n\n```\ncurl -fsSL https://raw.githubusercontent.com/leeguooooo/chrome-use/main/install.sh | sh\nchrome-use extension install\n# then: add the Chrome extension → restart Chrome → sign in to chatgpt.com\n```\n\nExtension: [Chrome Web Store](https://chromewebstore.google.com/detail/agent-browser-stealth/knfcmbamhjmaonkfnjhldjedeobeafmk). Older installs exposing the binary as `agent-browser`\n\n/ `abs`\n\nkeep working — the CLI accepts both names.\n\nNo\n\n`chrome-use`\n\n? Nothing breaks and nothing gets installed behind your back:`auto`\n\nmode falls back to`codex`\n\nand prints a one-line tip that installing`chrome-use`\n\nmakes generation cost no Codex-usage.\n\nInstall via [skills.sh](https://www.skills.sh) — works with Claude Code, Codex Agent, Cursor, OpenClaw, etc.:\n\n```\nnpx skills add leeguooooo/chatgpt-imagegen -g\n```\n\nThis drops both the agent instructions (`SKILL.md`\n\n) and the CLI itself into your agent's skill directory. Just ask any compatible agent: *\"画一张 xxx\"* / *\"generate a hero banner for the README\"*.\n\n```\ngit clone https://github.com/leeguooooo/chatgpt-imagegen\ncd chatgpt-imagegen\nchmod +x chatgpt-imagegen\n./chatgpt-imagegen \"a tiny pixel-art mushroom\"\n```\n\nOr put it on your `$PATH`\n\n:\n\n```\nsudo install chatgpt-imagegen /usr/local/bin/chatgpt-imagegen\n```\n\nThat's the entire setup. No `pip install`\n\n, no virtualenv, no daemon.\n\n```\nchatgpt-imagegen \"<prompt>\" [options]\n```\n\n| Flag | Default | Notes |\n|---|---|---|\n`--backend` |\n`auto` |\n`auto` | `web` | `codex` . `auto` prefers web (spares Codex-usage), falls back to codex if no logged-in browser. See\n`CHATGPT_IMAGEGEN_BACKEND` . |\n`--profile` |\n`auto` |\n(web) Which Chrome profile to drive. `auto` : use your open Chrome if it's logged in, else auto-switch to a profile that is (detected offline). `relay` : only your open Chrome. Or a name like `\"Profile 3\"` . |\n`--session` |\n`imagegen` |\n(web) Named `chrome-use` Chrome tab group reused across runs (one stable daemon instead of one per run). Falls back to `imagegen-<pid>` when web concurrency is raised above 1, to isolate parallel runs. |\n`--project` |\n`imagegen` |\n(web) ChatGPT Project to file the conversation under — matched by exact name, created on first use, reused afterwards. Pass `--project \"\"` for a plain top-level chat. Also `CHATGPT_IMAGEGEN_PROJECT` . Failures degrade to a plain chat with a warning, never block the run. |\n`--keep-tab` |\noff | (web) Leave the ChatGPT tab open after generating (default closes it). Implies `--keep-conversation` . |\n`--keep-conversation` |\noff | (web) Keep the ChatGPT conversation after generating. Default deletes it so the run leaves no history (filed under the project only transiently). Also `CHATGPT_IMAGEGEN_KEEP_CONVERSATION=1` . |\n`--web-model` |\n`Instant,Auto` |\n(web) Comma-separated model/effort candidates; the first one present in the picker is selected before generating. The tier has no native image generator (it answers image requests by writing Python), so the web backend switches off it automatically. Pass `Pro` `\"\"` to keep whatever is selected. Also `CHATGPT_IMAGEGEN_WEB_MODEL` . |\n`-i` , `--ref PATH_OR_URL` |\n— | Image-to-image. Reference image to edit (repeatable for multiple references). A local path or `http(s)` URL. When given, the model edits the reference(s) instead of rendering from text. Works on both backends. See\n|\n`--style NAME` |\n— | Apply a saved\n|\n\n`--no-style`\n\n`-o`\n\n, `--out PATH`\n\n`assets/generated/<slug>.<ext>`\n\n`--format`\n\ndisagree (e.g. `-o foo.jpg --format png`\n\n).`--size`\n\n`auto`\n\n`auto`\n\nor any `WIDTHxHEIGHT`\n\n. Verified working: `1024x1024`\n\n, `1024x1536`\n\n, `1536x1024`\n\n. Larger sizes are forwarded as-is.`--format`\n\n`png`\n\n`png`\n\n| `jpeg`\n\n| `webp`\n\n`--model`\n\n`gpt-5.5`\n\n`image_generation`\n\ntool`--timeout`\n\n`300`\n\n**Total wall-clock budget**(seconds) for the whole request. Large/detailed images can take 2–3 min.`--stall-timeout`\n\n`120`\n\n**stall**— caught well before the total budget. Clamped to`--timeout`\n\n.`--quiet`\n\n**only** the saved path on stdout (perfect for agent pipelines). Progress still streams to stderr — use`--no-progress`\n\nto silence it.`--no-progress`\n\n`-V`\n\n, `--version`\n\n`chatgpt-imagegen 0.11.0`\n\n) and exit.Examples:\n\n```\n# Default → assets/generated/<slugified-prompt>.png\nchatgpt-imagegen \"watercolor cat\"\n\n# Pick the path\nchatgpt-imagegen \"logo for a coffee shop, vector style\" -o brand/logo.png --size 1024x1024\n\n# Landscape hero banner\nchatgpt-imagegen \"moody mountain sunset\" -o web/hero.png --size 1536x1024\n\n# Use in a shell pipeline\nOUT=$(chatgpt-imagegen \"icon\" --quiet)\necho \"saved to $OUT\"\n```\n\nA **style** is a named, reusable snippet of prompt text. Apply one with `--style NAME`\n\nand it's appended to your prompt as a suffix — so `--style doodle`\n\nturns `a cat`\n\ninto `a cat, drawn as a deliberately crude doodle …`\n\n. Styles live in a small JSON file (`~/.config/chatgpt-imagegen/styles.json`\n\n, honouring `$XDG_CONFIG_HOME`\n\n), seeded on first use with one built-in style, `doodle`\n\n— the deliberately-awful MS-Paint look the [example below](#image-to-image) is drawn in.\n\nThere is **no default style out of the box** — nothing changes unless you opt in with `--style`\n\nor set an active default. Manage them with the `style`\n\nsubcommand:\n\n| Command | What it does |\n|---|---|\n`chatgpt-imagegen style list` |\nList all styles; the active default (if any) is marked `*` |\n`chatgpt-imagegen style show NAME` |\nPrint one style's full snippet |\n`chatgpt-imagegen style add NAME \"snippet\"` |\nCreate or overwrite a style |\n`chatgpt-imagegen style rm NAME` |\nDelete a style |\n`chatgpt-imagegen style use NAME` |\nSet NAME as the active default — auto-applied to every run |\n`chatgpt-imagegen style clear` |\nUnset the active default |\n`chatgpt-imagegen style reset` |\nRe-seed the built-in styles (discards your edits; `-y` skips the prompt) |\n\nAt generation time:\n\n```\n# Apply a style for one run\nchatgpt-imagegen \"a robot mascot\" --style doodle\n\n# Save your own house style once, then make it the default\nchatgpt-imagegen style add brand \"flat vector, bold shapes, teal #00b3a4 accent, white background\"\nchatgpt-imagegen style use brand\nchatgpt-imagegen \"a settings icon\"            # → uses the brand style automatically\n\n# Skip the active default for one run\nchatgpt-imagegen \"a photorealistic forest\" --no-style\n```\n\nResolution order per run: `--no-style`\n\nwins, else `--style NAME`\n\n, else the active default, else no style. The snippet only affects the text sent to the model — your output filename and the prompt shown in the progress log stay your raw prompt.\n\nPass a reference image with `-i`\n\n/`--ref`\n\nto **edit it** instead of generating from\ntext — the same mechanism as dragging an image into the ChatGPT composer and asking\nit to restyle the scene. Still your ChatGPT subscription, still no API key.\n\nWorks on **both backends**, and `auto`\n\npicks the right one for you:\n\n**web**(default, no Codex-usage): the reference is uploaded into the ChatGPT composer (via`chrome-use`\n\n), then the edit prompt is sent on the conversation surface — exactly like doing it by hand.**codex**(`--backend codex`\n\n): the reference is sent as an`input_image`\n\npart and the image tool is forced; this bills the metered Codex-usage bucket.\n\n```\n# Restyle / edit a local image\nchatgpt-imagegen \"make it a warm golden-hour photo, cinematic 35mm\" -i photo.jpg -o out.png\n\n# Reference from a URL\nchatgpt-imagegen \"place this product in a minimalist studio scene\" -i https://example.com/item.png -o scene.png\n\n# Multiple references (repeat -i) — e.g. several angles of the same subject\nchatgpt-imagegen \"put this rug in a cozy living room\" -i front.jpg -i detail.jpg -o room.png --size 1024x1536\n```\n\nNotes:\n\n- Supported reference types: PNG, JPEG, WEBP.\n**web:** references are uploaded as files; the browser handles sizing. The result is read from the conversation, never confused with the uploaded reference.**codex:** references are sent as base64; oversized images are auto-downsized to a JPEG under a ~5 MB budget via macOS`sips`\n\nwhen available (no extra dependencies).\n\nReal output of the exact example commands above — every image in this README is made by this tool:\n\n`watercolor cat sitting on a windowsill` |\n`logo for a coffee shop, vector style` |\n`moody mountain sunset` (1536×1024) |\n|---|---|---|\n\nIt can also do this — asked to draw its own two-backend architecture as a deliberately awful, mouse-drawn MS-Paint doodle.\n\n| Parameter | Subscription path | Notes |\n|---|---|---|\n`--size` |\n✅ honoured | `auto` or any `WIDTHxHEIGHT` ; backend rejects sizes it doesn't support. Verified working: `auto` , `1024x1024` , `1024x1536` , `1536x1024` . Larger sizes (`2048x*` , `3840x*` ) are forwarded as-is — the backend may accept or reject depending on subscription tier. |\n`--format` |\n✅ honoured | `png` / `jpeg` / `webp` |\n| Quality | The script does not expose a `--quality` flag because the subscription path does not expose reliable quality control — the backend has been observed picking `low` or `medium` on its own and ignoring or downgrading any request for `high` . Use the official `/v1/images/generations` API with `OPENAI_API_KEY` if you need explicit quality control. |\n|\n`background: transparent` |\n❌ not supported on subscription | needs API-key path with `gpt-image-1.5` |\nImage edits (`/v1/images/edits` ) |\n❌ not exposed yet | open an issue if you need this |\n| Speed | typically 15–60 s, occasionally 2–3 min for large/detailed images | streamed end-to-end; a per-phase timeline prints to stderr so you can see it working |\n\nEach backend has its own cross-process concurrency cap, because they hit different limits:\n\n| Backend | Default cap | Why | Override |\n|---|---|---|---|\n`web` |\n1 (serialized) |\nDrives the one shared logged-in Chrome, and the chatgpt.com page surface rate-limits aggressively (\"Too many requests… temporarily limited access to your conversations\"). |\n`CHATGPT_IMAGEGEN_WEB_CONCURRENCY` |\n`codex` |\n4 |\nIndependent HTTP POSTs; measured fine at 4 concurrent on a Plus account (no 429s, wall time ≈ slowest single). Capped so a big agent fan-out can't trip the per-account limiter. | `CHATGPT_IMAGEGEN_CODEX_CONCURRENCY` (`0` = unlimited) |\n\nFiring more processes than the cap is **safe** — excess runs queue on a flock slot pool (waiters print a \"waiting…\" line, and the `--timeout`\n\nbudget only starts once a slot is acquired, so queue time is free).\n\n```\n# Fire 4 in parallel from a shell (note: --backend codex):\nfor p in apple sky tree sun; do\n  chatgpt-imagegen \"a tiny $p icon, flat vector, white background\" \\\n    -o \"icons/$p.png\" --backend codex --quiet &\ndone\nwait\n```\n\nWhy web stays at 1: concurrent runs on the shared Chrome used to cross-contaminate each other's images ([#7](https://github.com/leeguooooo/chatgpt-imagegen/issues/7), fixed in v0.6.0), and the page surface throttles fast bursts regardless. If chatgpt.com does rate-limit the account, the web backend detects the \"Too many requests\" dialog and **fails fast with a clear message** — before the prompt is submitted `auto`\n\nmode falls back to codex; after submission it stops cleanly instead of double-spending.\n\nCaveat: subscription quota is shared with the ChatGPT web app and Codex CLI. Don't run sustained batches (>10 images/min) — you'll eventually hit per-day rate limits. For bulk batches, use the official `/v1/images/generations`\n\nAPI with an `OPENAI_API_KEY`\n\n.\n\nIf any of these apply, this tool is the wrong fit:\n\n- You want\n**true** or`quality=high`\n\n**native transparent backgrounds**— both require the official`/v1/images/generations`\n\nAPI with an`OPENAI_API_KEY`\n\n. - You're building a\n**production service** that serves images to end users — using your personal ChatGPT subscription for that violates OpenAI's ToS and burns the quota you use for actual ChatGPT. - You need\n**deterministic per-call billing** that you can pass through to customers — the API has that, the subscription doesn't. - You want\n**>10 images per minute** sustained — subscription rate limits are tighter than the API.\n\nFor those cases, just call OpenAI's official endpoint:\n\n```\ncurl https://api.openai.com/v1/images/generations \\\n  -H \"Authorization: Bearer $OPENAI_API_KEY\" \\\n  -d '{\"model\":\"gpt-image-2\",\"prompt\":\"...\",\"size\":\"1024x1024\"}'\n```\n\n**Need an HTTP API?** exposes the same tool as an OpenAI-compatible**agent-cli-to-api**`/v1/chat/completions`\n\nserver — pick it for network-callable, multi-client, or team-shared use. This repo is for local / agent-driven use.**Deep dives (blog):**[why this exists + OAuth/SSE walkthrough](https://blog.misonote.com/zh/posts/chatgpt-subscription-image-api/)·[visual guide](https://blog.misonote.com/zh/posts/chatgpt-imagegen-visual-guide/)(zh; EN/JA under`/en/`\n\nand`/ja/`\n\n).\n\nDrives your logged-in browser via `chrome-use`\n\nso generation runs on the consumer ChatGPT surface — which a headless client can't reach. The gate has three layers: Cloudflare's edge check and a sentinel proof-of-work (`backend-api/sentinel/chat-requirements`\n\n+ an in-page `sentinel/sdk.js`\n\n) are both passable by a bare client, but a **Cloudflare Turnstile** token isn't — that interactive token can only come from a real browser, and it's single-use, so there's no \"grab the token then go headless\" shortcut. The flow:\n\n```\nchatgpt-imagegen --backend web\n   │\n   ├── chrome-use open https://chatgpt.com/      (a *regular* chat — Temporary Chat disables the image tool)\n   ├── resolve the ChatGPT Project (--project)    (in-page fetch: list via gizmos/snorlax/sidebar,\n   │   and open chatgpt.com/g/<g-p-id>/project     create via POST /backend-api/projects if absent)\n   ├── type the prompt with real keystrokes        (ProseMirror/React composer ignores DOM-only `fill`)\n   ├── poll the page: wait until streaming stops AND a new <img> asset is stable\n   └── fetch the asset bytes in-page (credentials:'include') → base64 → save\n       (the signed estuary/content URL is authorized by the browser's own cookies)\n```\n\nNo tokens leave the browser. Each run's chat lands inside the `imagegen`\n\nProject (auto-created) instead of the top-level history; pass `--project \"\"`\n\nto opt out.\n\nThe Codex CLI's built-in `image_gen`\n\nskill is implemented as a native Responses-API tool:\n\n```\n// Codex CLI's request to chatgpt.com/backend-api/codex/responses:\n{\n  \"model\": \"gpt-5.5\",\n  \"tools\": [{\"type\": \"image_generation\"}],\n  \"input\": [{\"role\": \"user\", \"content\": [{\"type\":\"input_text\",\"text\":\"draw a cat\"}]}],\n  // ...\n}\n```\n\nThe server replies with an SSE stream whose `response.output_item.done`\n\nevents carry an `item.type === \"image_generation_call\"`\n\npayload, where `item.result`\n\nis base64 PNG. `chatgpt-imagegen`\n\ndoes exactly that:\n\n```\nchatgpt-imagegen\n   │\n   ├── reads ~/.codex/auth.json     (OAuth access_token, account_id, refresh_token)\n   ├── reads ~/.codex/version.json  (codex CLI version → matches server expectations)\n   │\n   └── POST https://chatgpt.com/backend-api/codex/responses\n       headers: Authorization, version, originator, session_id, …\n       body:    tools: [image_generation]\n       │\n       └── SSE stream\n           ├── response.image_generation_call.in_progress    → \"queued\"\n           ├── response.image_generation_call.generating      → \"generating\"\n           ├── response.image_generation_call.partial_image   → \"receiving image (partial N)\"\n           ├── response.output_item.done  ← item.result = base64 PNG\n           └── response.completed\n```\n\nIf the OAuth token has expired the script auto-refreshes via `https://auth.openai.com/oauth/token`\n\n(using the refresh_token already stored by `codex login`\n\n) and persists the new token back to `~/.codex/auth.json`\n\n.\n\nMIT — see [LICENSE](/leeguooooo/chatgpt-imagegen/blob/main/LICENSE).\n\nThis tool calls ChatGPT's internal `backend-api/codex`\n\nendpoint, which is the same endpoint the official Codex CLI uses. It is not a documented public API. OpenAI could change or restrict it at any time. Use is at your own risk and within the [OpenAI Terms of Use](https://openai.com/policies/row-terms-of-use/) — in particular, **do not use your ChatGPT subscription to power a public-facing image generation service**.\n\n`ChatGPT subscription image generation`\n\n, `free ChatGPT account image generation`\n\n, `use ChatGPT Plus for image API`\n\n, `gpt-image-2 without OPENAI_API_KEY`\n\n, `gpt-image-2 ChatGPT subscription`\n\n, `image_generation tool Responses API`\n\n, `ChatGPT image CLI`\n\n, `Codex CLI image_gen as standalone tool`\n\n, `DALL-E via ChatGPT Plus`\n\n, `OAuth-backed OpenAI image generation`\n\n, `no-API-key image generation`\n\n, `AI agent image generation skill`\n\n, `Claude Code image skill`\n\n, `OpenAI image generation without billing`\n\n.\n\n**中文：** 用 ChatGPT 订阅生成图片、免费 ChatGPT 账号生图、ChatGPT Plus 生图工具、不用 API key 生图、gpt-image-2 用订阅、ChatGPT 订阅生图 CLI、Codex CLI 生图能力独立工具、给 AI agent 用的生图 skill、本地生图脚本、零依赖 Python 生图工具。", "url": "https://wpnews.pro/news/show-hn-generate-images-from-the-cli-with-a-chatgpt-subscription-no-api-key", "canonical_source": "https://github.com/leeguooooo/chatgpt-imagegen", "published_at": "2026-06-22 06:23:59+00:00", "updated_at": "2026-06-22 06:40:37.570550+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "generative-ai"], "entities": ["OpenAI", "ChatGPT", "Codex CLI", "chrome-use", "agent-browser-stealth", "skills.sh"], "alternates": {"html": "https://wpnews.pro/news/show-hn-generate-images-from-the-cli-with-a-chatgpt-subscription-no-api-key", "markdown": "https://wpnews.pro/news/show-hn-generate-images-from-the-cli-with-a-chatgpt-subscription-no-api-key.md", "text": "https://wpnews.pro/news/show-hn-generate-images-from-the-cli-with-a-chatgpt-subscription-no-api-key.txt", "jsonld": "https://wpnews.pro/news/show-hn-generate-images-from-the-cli-with-a-chatgpt-subscription-no-api-key.jsonld"}}