Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,44 @@ needs [libwebp](https://developers.google.com/speed/webp/download) (`img2webp`).

Full options: `image-use --help`. → **[Generate images](https://drawstyle.leeguoo.com/en/docs/generate)** · **[Styles](https://drawstyle.leeguoo.com/en/docs/styles)**

To organize web runs, `--project` accepts an exact **name** (found or created;
default `imagegen`) or an existing **Project landing URL** such as
`https://chatgpt.com/g/g-p-<32-hex-id>/project`. URL targets open directly by ID,
without listing or creating projects; localized display slugs, query strings,
and fragments are accepted and discarded when opening the canonical URL.
Malformed URL targets are rejected. `--project ""` uses a plain chat.

Project routing is best-effort by default: an unavailable project warns and
continues in a plain chat. Add `--require-project` (or `IMAGE_USE_REQUIRE_PROJECT=1`)
when that fallback would be unwanted. It requires a non-empty target and
`--backend web` or `auto`, verifies Project identity after opening and again in
a capture guard on the native Send click, and disables Codex fallback even when
the browser is unavailable. `IMAGE_USE_PROJECT` can supply either a name or URL.
Use `--no-require-project` to override the environment default for one run,
restoring best-effort routing and ordinary backend/fallback rules without
changing the exported variable.

Required-mode failures before Send retain the current draft for inspection.
Inspect it in ChatGPT and manually clear the composer before the next run;
ChatGPT can restore unsent drafts in new chats.

```bash
image-use "a watercolor cat" --project "Art" --require-project --keep-conversation
# To target an existing project exactly, set PROJECT_URL to its browser URL:
image-use "a watercolor cat" --project "$PROJECT_URL" --require-project --keep-conversation
```

Routing and retention are independent: **the conversation is deleted by default**,
including with `--require-project`. Use `--keep-conversation` (or
`IMAGE_USE_KEEP_CONVERSATION=1`) to retain it; `--keep-tab` also retains it.

The ChatGPT browser backend pastes multiline prompts, checks the exact editor
text, and waits for every reference upload to finish before clicking Send once.
Incomplete uploads or altered text stop the run. If a send cannot be confirmed,
it reports the uncertainty without sending again. Start with an empty composer;
an existing draft is preserved. A run that stops before sending clears the text
it pasted; if a run still reports a non-empty composer, clear the box at
chatgpt.com — ChatGPT restores unsent drafts in new chats.
it pasted, except in required Project mode: it preserves the current draft for
inspection because the route or draft ownership may have changed.

After sending, the web backend waits up to `--timeout` for a fresh image confirmed
in two consecutive page reads. Assistant text or a missing Stop control does not
Expand Down
31 changes: 29 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,38 @@ image-use "产品主图" --backend codex --image-model gpt-image-2.5-sunburst --

完整参数:`image-use --help`。→ **[生成图片](https://drawstyle.leeguoo.com/zh/docs/generate)** · **[风格系统](https://drawstyle.leeguoo.com/zh/docs/styles)**

web 后端的 `--project` 可以传精确**名称**(找到就复用,没有就创建;默认 `imagegen`),
也可以传已有 **Project 首页链接**,形如
`https://chatgpt.com/g/g-p-<32-hex-id>/project`。链接按完整 ID 直接打开,不依赖侧栏列表,
也不创建 Project;支持中文等显示名 slug、query 和 fragment,打开时去掉这些装饰。
格式错误的链接会直接报错。`--project ""` 则使用普通 chat。

默认仍是 best-effort:Project 不可用时会警告并继续使用普通 chat。如果必须在目标
Project 中提交,加 `--require-project`(或 `IMAGE_USE_REQUIRE_PROJECT=1`)。
它要求目标非空,backend 为 `web` 或 `auto`,进入后核对 Project 身份,并在原生
发送点击事件的捕获阶段再次校验身份和输入状态;浏览器不可用时也禁止回退 Codex。
`IMAGE_USE_PROJECT` 同样可以设置名称或链接。
用 `--no-require-project` 可以只在本次运行覆盖环境变量的默认值,恢复 best-effort
路由和普通 backend/fallback 规则,不需要修改已 export 的变量。

required 模式在发送前失败时会保留当前草稿供检查。请先在 ChatGPT 中检查草稿,
再手动清空输入框后运行下一次;ChatGPT 可能会在新对话里恢复未发送的草稿。

```bash
image-use "一只水彩猫" --project "Art" --require-project --keep-conversation
# 精确指定已有 Project 时,先把浏览器里的首页链接赋给 PROJECT_URL:
image-use "一只水彩猫" --project "$PROJECT_URL" --require-project --keep-conversation
```

路由与保留对话是独立选项:**默认仍会删除对话**,即使加了 `--require-project`。
要让对话留在 Project 中,加 `--keep-conversation`(或 `IMAGE_USE_KEEP_CONVERSATION=1`);
`--keep-tab` 也会保留对话。

ChatGPT 浏览器后端会粘贴多行提示词,核对编辑器中的完整文本,等待所有参考图上传
完成后只点击一次发送。上传不完整或文本发生变化时会停止;无法确认发送结果时会
报告问题,不会重复发送。运行前请确保输入框为空,已有草稿会被保留。未发送就中止的运行
会清掉自己粘贴的文本;如果仍提示输入框非空,请到 chatgpt.com 手动清空——ChatGPT 会在
新对话里恢复未发送的草稿。
会清掉自己粘贴的文本;required Project 模式会保留当前草稿供检查,避免在路由或草稿
归属变化后误清其他内容。

发送后,web 后端会在 `--timeout` 内等待新图片,要求相邻两次页面读取确认到同一张图。
assistant 正文出现或 Stop 控件缺失,都不能证明图片任务已经结束。已检测到的限流
Expand Down
19 changes: 11 additions & 8 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ A standalone Python CLI that produces images via the user's existing subscriptio

**Default is `auto`** (`--backend auto`, or `IMAGE_USE_BACKEND`): it tries **web first** because that spares the Codex-usage limit, and falls back to **codex only when web is unavailable** — i.e. `chrome-use` isn't installed, the browser isn't reachable, or chatgpt.com isn't logged in. The two not-set-up cases are handled explicitly:

- **Browser not logged in / chrome-use missing** → auto silently falls back to codex (a one-line notice prints to stderr). If codex is *also* not set up, it exits naming both fixes.
- **Browser not logged in / chrome-use missing** → auto falls back to codex (a one-line notice prints to stderr), unless `--require-project` is set. Required mode stops without reading Codex credentials. If codex is *also* not set up in ordinary auto mode, it exits naming both fixes.
- **codex not logged in** (`~/.codex/auth.json` absent) → auto still uses web; codex is only the fallback.

Auto does **not** fall back to codex if web was reachable but the generation itself failed after submitting — that would spend the very bucket auto-mode protects. In that case it errors and tells you to rerun with `--backend codex` if you want the Codex-usage path. Force a single backend with `--backend web` or `--backend codex`.
Auto does **not** fall back to codex if web was reachable but the generation itself failed after submitting — that would spend the very bucket auto-mode protects. In ordinary auto mode it errors and tells you to rerun with `--backend codex` if you want the Codex-usage path; required Project mode stops without that suggestion. Force a single backend with `--backend web` or `--backend codex`.

### Gemini backends (opt-in — `auto` never picks them)

Expand Down Expand Up @@ -141,13 +141,15 @@ Useful flags:

| Flag | When to use |
| --- | --- |
| `--backend auto` \| `web` \| `codex` \| `gemini` \| `agy` | `auto` (default) prefers web and falls back to codex only when the browser is unavailable/not-logged-in; `web` forces the logged-in-browser path (spares Codex-usage); `codex` forces the headless path (bills Codex-usage); `gemini` and `agy` use a Google account instead and are never picked by `auto` (see [Gemini backends](#gemini-backends-opt-in--auto-never-picks-them)). Also settable via `IMAGE_USE_BACKEND`. |
| `--backend auto` \| `web` \| `codex` \| `gemini` \| `agy` | `auto` (default) prefers web and falls back to codex only when the browser is unavailable/not-logged-in, unless `--require-project` disables that fallback; `web` forces the logged-in-browser path (spares Codex-usage); `codex` forces the headless path (bills Codex-usage); `gemini` and `agy` use a Google account instead and are never picked by `auto` (see [Gemini backends](#gemini-backends-opt-in--auto-never-picks-them)). Also settable via `IMAGE_USE_BACKEND`. |
| `--gemini-profile NAME` | (`gemini` backend) Chrome profile to drive, overriding `--profile`. Worth setting — auto-detection cannot tell which Google account holds the subscription. Also `IMAGE_USE_GEMINI_PROFILE`. |
| `--no-gemini-image-tool` | (`gemini` backend) skip switching the composer to the dedicated image model (Nano Banana 2). Rarely wanted — the switch is already best-effort. |
| `--no-agy-yolo` | (`agy` backend) don't pass `--dangerously-skip-permissions`. Only use it if the user has their own `permissions.allow` rules — otherwise every headless run fails. |
| `--profile auto` \| `relay` \| `NAME` | (web) Which Chrome profile to drive. `auto` (default): use the open Chrome if it's logged in, else auto-switch to a profile that is (detected offline from the cookie DB, read-only). `relay`: only the open Chrome. `"Profile 3"`: that profile. Note: *logged in* ≠ *able to generate* — a free-tier account can still hit its daily image cap. |
| `--session NAME` | (web) Drive a named Chrome tab group instead of the shared `chatgpt-web` session. Rarely wanted: the default is shared ON PURPOSE so the whole machine keeps ONE chatgpt.com tab. |
| `--project NAME` | (web) ChatGPT Project to file the run's conversation under — matched by exact name, **created automatically if absent**, reused if present. Default `imagegen` (or `IMAGE_USE_PROJECT`). Pass `--project ""` for a plain top-level chat. If the project step fails, the run warns and continues in a plain chat — it never blocks generation. |
| `--project NAME_OR_URL` | (web) An exact name (**created if absent**, reused if present) or an existing ChatGPT Project landing URL (`https://chatgpt.com/g/g-p-<32-hex-id>/project`, optional localized slug/query/fragment). URLs open directly by ID without listing/creating; malformed URLs fail. Default `imagegen` (or `IMAGE_USE_PROJECT`, also accepts URLs). `--project ""` uses a plain chat. Project failures warn and continue in a plain chat unless required. |
| `--require-project` | Require a non-empty Project target and verify its identity after opening and in a capture guard on the native Send click. Only `web`/`auto`; **disables Codex fallback**, including browser-unavailable failures. Also `IMAGE_USE_REQUIRE_PROJECT=1`. Routing does **not** imply retention: add `--keep-conversation` to retain history. Pre-send failures retain the current draft; inspect it and manually clear the composer before the next run. |
| `--no-require-project` | Override the required-mode environment default for this run without changing the exported variable. Restores best-effort Project routing and ordinary backend/fallback rules; allows `--project ""` or an explicit non-web backend. Retention remains independent. |
| `--keep-tab` | (web) Leave the ChatGPT tab open after generating (default closes it). Useful for debugging. Implies `--keep-conversation`. |
| `--keep-conversation` | (web) Keep the ChatGPT conversation after generating. **Default deletes it** (`PATCH is_visible:false`) so the run leaves no history — it's filed under the project only transiently. Also `IMAGE_USE_KEEP_CONVERSATION=1`. |
| `-o PATH` | Always use when you know where the file should go in the repo. |
Expand Down Expand Up @@ -309,8 +311,9 @@ Update notices are covered under [Upgrade](#upgrade).
| `no image returned. events seen: ...` | Model decided not to call the tool | Rephrase prompt to explicitly say "Use the image_generation tool to render…" |
| `HTTP 429` | Subscription rate-limited | Wait a few minutes; do not retry in a loop |
| `warning: --format=X but FILE.Y has .Y extension` | `-o` extension disagrees with `--format` | Fix the path or the format flag; the file IS written with the format you specified |
| `warning: project 'X' unavailable (…); using a plain chat` | (web) Project list/create API hiccup, or the project page's composer didn't render | Nothing — the image still generated, just in a top-level chat. If it recurs, check the name or pass `--project ""` |
| `chatgpt.com rate-limited this account ('Too many requests') …` | (web) The page surface temporarily blocked the account for making requests too quickly | Wait a few minutes. If it fired *before* submit, `auto` mode already fell back to codex; if *after* submit, check the conversation later — the image may still appear there. Don't retry in a loop |
| `warning: project 'X' unavailable (…); using a plain chat` | (web, without `--require-project`) Project list/create API hiccup, or the project page's composer didn't render | Nothing — the image still generated, just in a top-level chat. If it recurs, check the target; use `--require-project` to stop instead of degrading |
| `required project unavailable` / `required Project Send stopped` | Project access, route, or composer state could not be verified | Stop; inspect the Project link, permissions, and retained draft. Manually clear any retained composer text before the next run; ChatGPT can restore drafts in new chats. No plain-chat or Codex fallback. An uncertain Send is never repeated; inspect the conversation before retrying. |
| `chatgpt.com rate-limited this account ('Too many requests') …` | (web) The page surface temporarily blocked the account for making requests too quickly | Wait a few minutes. A detected rate limit stops the run without automatic Codex fallback; if the prompt was already submitted, check the conversation later — the image may still appear there. Don't retry in a loop |
| `waiting for a free web/codex slot (max N concurrent …)` | More parallel runs than the backend's concurrency cap | Nothing — the run starts when a slot frees up; queue time doesn't eat `--timeout` |

## Upgrade
Expand All @@ -331,8 +334,8 @@ The notice is one stderr line, checked at most once a day; `IMAGE_USE_NO_UPDATE_
**web backend (`run_web`)**
- Shells out to `chrome-use` against a session-named Chrome tab group.
- Opens a *regular* `https://chatgpt.com/` chat (Temporary Chat disables the image tool).
- Resolves the target ChatGPT Project from inside the authenticated page (undocumented endpoints, probed live): `GET /backend-api/gizmos/snorlax/sidebar` lists projects (a project is a gizmo with id `g-p-…`); `POST /backend-api/projects {name, instructions}` creates one. It then navigates to `https://chatgpt.com/g/<g-p-id>/project` and submits from that composer, which files the conversation inside the project. Any failure degrades to a plain chat with a stderr warning.
- Pastes with `fill --stdin`, verifies the editor's exact logical text and every completed reference upload, then clicks Send once. Multiline `keyboard type` can turn newlines into partial submissions. Unconfirmed uploads or changed text stop the run; an uncertain send is observed without replaying it. The composer must be empty before a run so an existing draft is preserved; a run that stops before sending clears its own pasted text, since ChatGPT restores unsent drafts in new chats and one would block every later run.
- Resolves a named ChatGPT Project from inside the authenticated page (undocumented endpoints, probed live): `GET /backend-api/gizmos/snorlax/sidebar` lists projects (a project is a gizmo with id `g-p-…`); `POST /backend-api/projects {name, instructions}` creates one. An existing Project URL bypasses both endpoints and opens `https://chatgpt.com/g/<g-p-id>/project` directly. Failures warn and degrade to a plain chat by default; `--require-project` makes them fatal and prevents auto-mode Codex fallback.
- Pastes with `fill --stdin`, verifies the editor's exact logical text and every completed reference upload, then clicks Send once. Required mode also checks the Project before reference upload/paste. It marks only the current document's Send button; a capture listener rechecks route, text, uploads, turns, and readiness during its native click. The marker/listener are removed after the attempt, or expire after 30 seconds if the CLI disappears. Multiline `keyboard type` can turn newlines into partial submissions. Unconfirmed uploads or changed text stop the run; an uncertain send is observed without replaying it. Existing drafts are preserved. Pre-send failures clear the pasted text in legacy mode; required mode retains the current draft because route or ownership may have changed. Inspect it and manually clear the composer before retrying.
- Polls page state via `eval`: waits until the streaming/stop control is gone AND a brand-new `<img>` (src matching `estuary/content|files/download|oaiusercontent`) is present and stable across two reads. The img scan is scoped to `main img` (the tab's own conversation thread) — ChatGPT pushes an "Image created" toast with a matching thumbnail into any open tab when *another* conversation finishes an image, and a document-wide scan grabs that sibling's image (issue #7). The generated img is NOT inside `[data-message-author-role="assistant"]`, so `<main>` is the right scope.
- Downloads the bytes with an in-page `fetch(src, {credentials:'include'})` → base64, so the browser's own session cookies authorize the signed asset URL. No tokens leave the browser.

Expand Down
Loading
Loading