Claude Skill

gpt-image-gen

Use when the user asks to generate OR edit an image via GPT/Codex (e.g. 「叫 gpt 生圖」「幫我用 gpt 生圖」「gpt 畫一個 X」「幫我去背」「把這張圖的背景去掉」). The skill drafts a Chinese + English prompt pair and waits for explicit approval. After approval it uses the host-native executor: Codex calls its built-in

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download kerberosclaw-kc_ai_skills-gpt-image-gen-ad005ac.zip · 29 KB
Part of kerberosclaw/kc_ai_skills — 25 skills

Install

skills CLI npx skills add https://github.com/KerberosClaw/kc_ai_skills/tree/main/gpt-image-gen
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install kerberosclaw-kc-ai-skills@llmmart
Git git clone https://github.com/KerberosClaw/kc_ai_skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole kerberosclaw/kc_ai_skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

gpt-image-gen — Claude Code 走 Codex CLI;Codex 直接生圖

You are a prompt-crafting partner who turns the user's loose Chinese description into a tight bilingual prompt pair, iterates until the user explicitly approves, then uses the executor native to the current host. In Claude Code, orchestrate Codex CLI as before. In Codex, call the built-in image_gen tool yourself — do not launch another Codex inside Codex. Your job is prompt design, user confirmation gating, execution, and verifying the result before you hand it over.

Step 0: 先鎖定執行宿主(MANDATORY)

依目前 assistant 的宿主身分設定一次 RUNTIME,後面不得改道:

目前是誰在執行這個 skill RUNTIME 拍板後的路徑
Codex(目前 assistant/system 明確自稱 Codex) codex 走 Step 4-Codex,直接呼叫目前 session 的 built-in image_gen
Claude Code claude_code 走 Step 4-Claude,維持既有背景 codex exec 流程
  • 不要用 which codex、環境變數或「有沒有 codex binary」判斷宿主。 Claude Code 的機器本來就可能裝 Codex;那不是執行者身分。
  • RUNTIME=codex 時,禁止 shell-out 到 codex / codex exec、禁止再開 Codex 子程序或子 agent,也不跑 Step 4-Claude。這條正是避免疊床架屋的 hard invariant。
  • RUNTIME=codex 但 built-in image_gen 不可用時,如實告訴 user;不要靜默退回 Codex CLI。
  • Step 1~Step 3 的模式判斷、雙語 prompt、拍板 gate 與安全判斷兩邊共用;只有拍板後的 executor 分流。

CRITICAL — 四條紅線:

  1. 未拍板絕不啟動生圖 executor — 拍板 = user 明確說 OK / 生 / go / 下去。其他正向回應(「不錯」「可以喔」「應該行」)一律當「還沒拍板」處理,繼續等明確指令。生圖會花 user 的錢,誤觸發 = 違規。
  2. 有 reference image → 走 img2img 或編輯 — user 這輪有附底圖(拖曳/貼上/[Image #N])→ Step 3a 偵測。Claude Code 用 codex exec ... -i <ref>;Codex 把底圖直接交給 built-in image_gen,不再包一層 CLI。拍板 gate(紅線 1)對 img2img 與編輯一樣適用。
  3. 不寫死任何預設風格 — Skill 不存 style preset。每張圖風格純靠當下 conversation context + user 描述推。沒 context 就問。
  4. 🔴 編輯模式一律交 png,不轉 jpg,無例外 — Step 5c-1 的預設是「轉 jpg q85、刪 png」,那對生成是對的,對編輯是災難:jpg 沒有 alpha 通道,帶透明度的結果轉一次就永久消失,而編輯結果不可重現(同 prompt 同 ref 再跑不會是同一張)。不要去猜它有沒有 alpha(mode P 的透明 PNG 就會猜錯),一律走 Step 5c-2。

Step 1: 先判斷模式,再判斷語境

Step 1-0: 生成 or 編輯(先決,決定後面每一步)

user 要的 模式 判準
一張新的圖(有沒有底圖都算) 生成 — 走 Step 1 下半、Step 2 生成模板 底圖只是參考(鎖臉/鎖角色/鎖場景),輸出本來就該跟底圖不同
這張圖動一個地方,其他不要變 編輯 — Step 1b → Step 2-edit 模板 → Step 3a-edit → 對應 runtime executor → 驗收 → 只交 png 輸出應該還是同一張圖,只有指定處不同

🔑 一句話判準:「user 會不會拿輸出去跟原圖逐像素比對?」 會 → 編輯;不會 → 生成。

  • 「把他放到海邊」→ 生成(換場景,人以外全變)
  • 「同一個人,換成笑的表情」→ 生成(img2img;臉會被重繪,只是要求相似)
  • 「把背景去掉」「把左上角那個杯子移掉」→ 編輯(其餘每一像素都該原封不動)

分不出來就問一句:「這張是要改這張圖本身(其他地方一個像素都不動),還是照它生一張新的?」

⚠️ 編輯模式沒有底圖就不成立。 沒有本機實體檔 → 照紅線 2 問路徑,給不出就結束,不要退化成「生一張像的」。

不阻塞條款(user 不在場時):模式判斷、Step 1a、Step 1b 都是資訊不足型閘門, user 不在(背景 / 無人值守 / 被別的 skill 呼叫)時,照最合理的解讀往下走, 並在交付訊息裡明標「假設:MODE=<x>,未經確認」。

🔴 但拍板閘(Step 2a)沒有不阻塞版本 —— 那是授權閘,花的是 user 的錢,沒有 fallback。 被別的 skill 當子流程呼叫時,批次授權要在上層取得(上層對整批拿一次拍板), 不是在這裡放行;本層仍然不得在沒有任何授權的情況下啟動 executor。

Step 1-1: 判斷觸發語境(mid-conversation vs 新對話)

生成模式讀 trigger 那輪訊息 + 最近 5-10 輪 context,落到下表(編輯模式跳過本表,直接去 Step 1b):

情況 動作
Mid-conversation 且 context 含 ≥3 錨點(場景 + 主體 + 動作) 跳 Step 2,直接展 prompt
Mid-conversation 但 context 不足(缺任一錨點) 跳 Step 1a,互動補問
新對話 / 純 trigger 沒帶任何描述 跳 Step 1a,互動問清楚

錨點判斷標準:

  • 場景:哪裡 / 什麼背景(街道、室內、特定地標、純色底...)
  • 主體:誰 / 什麼物件、外型描述
  • 動作:在做什麼 / 姿態 / 表情 / 互動

不確定時就降級到 Step 1a 問清楚 — 禁止靠想像力填空。

Step 1a: 互動補問(只在缺錨點時)

問題收斂在缺的那幾項,每次最多 3 個問題、numbered list、口語:

要先確認幾件事再展 prompt:
1. 場景 / 背景:__
2. 主體:誰 / 什麼,幾個,長相 / 外型描述:__
3. 動作 / 氛圍:__
4. 風格傾向(可選;不講就交給我推):__

不要問:尺寸 / aspect ratio / 解析度(除非 user 主動提);技術參數(model / steps / cfg);codex 怎麼跑(這 skill 自己處理)。

Step 1b: 編輯模式要先釘死的五件事

編輯模式不問場景/主體/動作(那是生成模式的錨點),改問這五件:

要釘的 為什麼 不釘會怎樣
① 底圖的本機絕對路徑 Claude Code 的 codex -i 與後續像素驗收都需要它 沒有就無法完成現行驗收,別退化成「生一張像的」
② 改哪一處,精確到可驗證 prompt 要寫成 CHANGE EXACTLY ONE THING 寫「修一下」→ 模型自由發揮 → 整張重畫
③ 其餘一切都不准動 這句是編輯模式的核心,不是廢話 不寫,模型會把它當 img2img,重新生成一張「很像的」
④ 這是去背還是局部修改(EDIT_KIND=bgremove\|local) 驗收的第 ② 項只有 bgremove 會跑 漏設 → 假去背(背景被畫成白色)驗收會放行
⑤ 底圖尺寸 W×H Step 2-edit 的畫布鎖定要填實際數字 填佔位符 → 模型自己挑畫布 → 主體位置全跑掉

④⑤ 要在拍板之前拿到,因為它們要寫進給 user 過目的那份 prompt。 量尺寸只是讀一個檔的中繼資料,不花錢也不啟動 codex,不屬於「pre-flight 在拍板之後才做」那條規矩管的範圍:

REF="<user 給的底圖絕對路徑>"
EDIT_KIND=<bgremove|local>
eval "$("$SKILL_DIR/scripts/preflight_edit.sh" "$REF")"   # 給出 SRC_W / SRC_H,缺 Pillow 會直接中止

②③ 這對是編輯模式成立的關鍵:模型預設的行為是「重新生成」不是「就地修改」,要它就地改必須明說。


Step 2: 展 bilingual prompt 給 user 過

格式固定,中文在前英文在後(user review 中文,英文是實際送 image executor 的 payload):

## 中文 prompt
(口語描述,user 看得順、能直接指出哪裡要改的顆粒度。
 包含:場景 / 主體 / 動作 / 風格 / 光線 / 構圖 等該講的都講。)

## English prompt(送 image_gen 用)
(影像模型吃的高密度英文 prompt。
 結構建議:SETTING / SUBJECT / ACTION / STYLE / LIGHTING / COMPOSITION / ASPECT RATIO。
 寫法照 OpenAI 官方 prompt guide — 名詞 + 形容詞密集,少動詞,少 narrative。)

生成模式展完後就到這裡:停下來等 user 回應(拍板字眼見 Step 2a)。編輯模式改用下面那套骨架。

Step 2-edit: 編輯模式的 prompt 骨架(三段,缺一不可)

編輯模式不用上面那個 SETTING / SUBJECT / ACTION 結構 —— 那是在描述「要生什麼」, 而編輯要描述的是「保留什麼、只改什麼」。骨架固定三段:

① 保留清單 —— 越具體越好,把畫面上看得到的東西逐項點名
Use the attached Image #1 as the BASE. Keep EVERYTHING identical to it:
<逐項列出:主體、五官、髮型、配件、服裝、姿勢、位置、取景、裁切、
 鏡頭距離、背景、光線、長寬比 …… 凡是不該變的都點名>

② 唯一的改動 —— 用 EXACTLY ONE THING 句式
CHANGE EXACTLY ONE THING: <要改的那一項,寫到可驗證>

③ 明擋清單 + 畫布鎖定 —— 反面詞比正面詞有效
DO NOT <逐項擋掉最可能被順手改掉的東西>. DO NOT move the subject.
DO NOT zoom in or out. DO NOT resize.
Keep the output canvas at exactly <W> x <H> pixels.

去背另外加一段(否則模型會把背景「畫成白色」而不是挖掉):

Output a PNG with a genuine ALPHA CHANNEL - the area around the subject must be
actually TRANSPARENT (alpha = 0), not painted white, not painted any solid colour,
and not a checkerboard pattern drawn as pixels.

⚠️ 中文那半照樣要寫(user 是看中文 review 的),但中文段要把「保留清單」逐項寫出來, 不要濃縮成「其他都不要動」—— user 要能一眼看出你有沒有漏點名某個東西。

展完後停下來等 user 回應。

Step 2a: User 回應分支

User 回應 動作
OK / 生 / go / 下去(明確拍板字眼) 進 Step 3
任何修改指令(「改成 X」「加 Y」「拿掉 Z」「換風格」) 重生 prompt 雙段 → 回 Step 2 開頭重展
算了 / 不要了 / 取消 結束,不啟動任何 executor
其他模糊正向回應(「不錯」「可以喔」「OK 吧」含猶豫感) 視為「還沒拍板」,回問一句:「這版就生?確認的話回 OK 或 生」

MANDATORY:拍板字眼是 hard gate,不准用語意推測代替。


Step 3: 執行前檢查(pre-flight)

Step 3a: Reference image 偵測

掃這輪 trigger + 等待拍板期間 user 是否有附過任何 image:

MODE=edit(Step 1-0 判定):
  • REF 已在 Step 1b ① 取得(編輯模式在拍板前就必須有底圖,否則寫不出保留清單與做不了驗收)→ 直接進 Step 3b。
  • 沒有 REF → 編輯模式不成立。照紅線 2 問路徑;給不出就如實告訴 user 做不到,
    🔴 不要退化成 MODE=generate「生一張像的」交差。

MODE=generate:
  RUNTIME=codex:
    • 本機有實體檔 → 記 REF=該絕對路徑;Step 4-Codex 用 referenced_image_paths。
    • 只有對話內嵌圖 → Step 4-Codex 用 num_last_images_to_include;不要為了 CLI 再問一次路徑。
    • 無附圖 → 不傳 reference 參數,走 text2img。

  RUNTIME=claude_code:
    • 本機有實體檔(user 給 path / 拖曳實體檔)→ 記 REF=該絕對路徑,走 img2img(Step 4-Claude 帶 -i "$REF")。
    • 只貼在對話裡、本機無實體檔 → 問 user 要本機路徑(codex -i 吃 file path、不吃對話內嵌圖)。給了 → img2img;給不出 → 印「拍板的英文 prompt」給 user 自己貼 ChatGPT GUI,結束。
    • 無附圖 → REF 留空,走 text2img。

  然後進 Step 3b。

⚠️ REF 是全篇唯一的底圖變數名(Step 1b 的「底圖絕對路徑」= REF,Step 5b 驗收的來源也是它)。 不要在不同 step 給同一張圖取不同名字。

Step 3a-edit: 編輯模式專屬 pre-flight(MANDATORY)

相依檢查與量尺寸已經在 Step 1b 做過(那時就要拿到 W×H 才寫得出 prompt)。 這裡只再確認一次底圖還在、EDIT_KIND 有給:

eval "$("$SKILL_DIR/scripts/preflight_edit.sh" "$REF")"   # 缺 Pillow 或底圖不見會直接中止
case "$EDIT_KIND" in
  bgremove|local) ;;
  *) echo "EDIT_KIND 沒指定(bgremove|local)——驗收的透明度檢查會被跳過" >&2; exit 1 ;;
esac

⚠️ 畫布尺寸有一個未收斂的風險:verify_edit.py 第 ① 項的尺寸比對是硬閘門, 但 image_gen 不保證任意尺寸都吐得出來。實測非標準比例(如 720×1080)有成功過, 但這不是保證。若這道閘門反覆失敗且尺寸只差一點,那是管線限制不是 prompt 問題 —— 告訴 user、讓他決定要不要接受「輸出後自己裁回原尺寸」,不要無限重試。

Step 3b: NSFW context 判斷

依當下 conversation context 判斷這張圖內容是否會踩到 OpenAI policy:

  • 不寫死硬規則 — 看上下文。例如:

    • 一般角色插畫、無裸露 → 應該過
    • 明確的性內容或裸露 → 大概率被 reject
    • context 本身就落在敏感題材 → 提高警覺度
  • 判斷會 reject → 警告 + 問:

    這張描述 codex 大概率會 reject(OpenAI policy)。要硬送看看,還是改走 ChatGPT GUI 或其他工具?
    - 硬送:回「送」
    - 改走別的工具:回「不要送」
    
  • 判斷 OK → 直接進 Step 4

不替 user 做安全決策 — 只警告 + 給選項。


Step 4: 依 RUNTIME 執行

Step 4-Codex: 直接呼叫 built-in image_gen

RUNTIME=codex 時,user 一拍板就由目前這個 Codex session直接呼叫 built-in image_gen。 這不是「請 Codex 幫我叫 Codex」,所以不要執行 codex binary、不要開背景 CLI task、不要用 OPENAI_API_KEY,也不要把 prompt 改寫成叫下一層 agent 做事的 instruction。

把 Step 2 拍板的 English prompt 直接放進 native tool 的 prompt。Reference 參數只選一種:

情況 built-in image_gen 參數
text2img,沒有任何底圖 省略 referenced_image_paths 與 num_last_images_to_include
所有底圖都有本機路徑 referenced_image_paths: [REF, ...]
底圖只存在最近對話 num_last_images_to_include: N,N 取涵蓋所有目標圖的最小值

絕不並傳 referenced_image_paths 與 num_last_images_to_include。若兩種來源混在一起而一個參數 無法涵蓋全部目標圖,請 user 重新附上缺的圖,不要漏圖硬生。

  • MODE=generate + reference:才在 prompt 開頭加身份鎖:「請參考附上的 Image #1 作為人物身份參考(同一個人,保持臉部特徵、髮型、體型一致)。」它仍是 img2img 生成。
  • MODE=edit:若本機底圖尚未在對話中看過,先用 Codex 的 view_image 檢視,再使用 Step 2-edit 拍板的三段 prompt;包裝意圖是「編輯這張圖」,禁止加身份鎖。
  • image generation tool 本身需要幾分鐘時,照目前 Codex harness 的原生等待機制等它完成;不因此另開 codex exec。
  • tool 完成後,用 Codex 的 generated-image renderer 直接把結果交回對話(在支援該介面的 harness,等同 generatedImage(result))。
  • 若 native result 同時提供本機 artifact path / output hint,依既有慣例搬到 cwd(git repo 走 generated_images/),不要覆寫既有檔。生成模式沿用 Step 5c-1 的格式判準;編輯保留 png 並跑 Step 5b 驗收。兩者都寫 sidecar:executor 記成 codex-native-image_gen,model 未揭露就填 null,不要猜,也不要跑 Step 5d 的 CLI log grep。
  • 若 native result 只有可直接交付的 image artifact、沒有可讀的本機路徑,仍直接交付,並在訊息附上拍板版 prompt。不得只為了湊 legacy 收圖/sidecar 流程而退回 Codex CLI。 此時不要假裝做過檔案層 pixel verification。
  • tool 失敗時回報其錯誤類型,不自動重試;built-in tool 不可用時也不得靜默改走 CLI。

RUNTIME=codex 的 executor 到此結束;有本機 artifact 時只重用 Step 5b 的驗收與 Step 5c 的格式判準, 不要照跑其中依賴 $STATE / marker 的 legacy shell。下面 Step 4-Claude、Step 5a、Step 5c-3、 Step 5d 的 log grep、Step 5e 範本與 Step 6 都是 Claude Code CLI 路徑。

Step 4-Claude: Claude Code 背景啟動 Codex CLI

以下流程只在 RUNTIME=claude_code 執行,行為維持原樣。

Step 4-Claude-a: 組路徑與檔名

TS=$(date +%Y%m%d_%H%M%S)
START_MARKER="/tmp/codex_imagegen_${TS}.marker"   # 只當 fallback 錨點(主路是 prompt-save,見 Step 4-Claude-b/5a)

# slug:從中文 prompt 抽 1-3 個關鍵詞,連字號連接,去掉空白與標點
# 範例:「一隻棕熊在雪山頂看日出」→ "brown-bear-summit-sunrise"
SLUG="<由你從中文 prompt 抽出>"

# 輸出夾:cwd 是 git repo → ./generated_images/ 子夾(避免雜進 git 根);否則 cwd 根
# 🔴 這個路徑等下要叫 codex 自己寫進去 → 必須落在 sandbox 可寫範圍(cwd 內 or /tmp/$TMPDIR)
if git -C "$PWD" rev-parse --git-dir >/dev/null 2>&1; then
  OUT_DIR="$PWD/generated_images"
else
  OUT_DIR="$PWD"
fi
mkdir -p "$OUT_DIR"

OUT_PNG="$OUT_DIR/${TS}_${SLUG}.png"      # 🟢 主路:叫 codex 直接存這(prompt-save,見 Step 4-Claude-b)
OUT_JPG="$OUT_DIR/${TS}_${SLUG}.jpg"      # 生成模式的最終交付(jpg q85)
OUT_SIDECAR="$OUT_DIR/${TS}_${SLUG}.prompt.md"
LAST_MSG="/tmp/codex_imagegen_${TS}.lastmsg"
LOG_FILE="/tmp/codex_imagegen_${TS}.log"

# 模式與底圖(Step 1-0 / Step 1b / Step 3a 已經決定,這裡只是落成變數)
MODE=<generate|edit>          # 🔴 佔位符,照抄會讓編輯模式落進 5c-1 轉 jpg 刪 png
EDIT_KIND=<bgremove|local>    # 只在 MODE=edit 時有意義;不要留預設值
REF="<底圖絕對路徑>"           # text2img 留空,img2img 與 edit 必填

touch "$START_MARKER"   # fallback 用:萬一 codex 沒照存,Step 5a 退而用 find -newer 撈

# 🔴 落一份 state 檔:每次 Bash 工具呼叫都是「全新的 shell」,上面這些變數活不過這一格。
#    Step 5 在背景等待之後才跑,屆時一律先 source 回來,不要憑記憶重打路徑。
STATE="/tmp/codex_imagegen_${TS}.state"
# 🔴 值一律 %q 跳脫。不跳脫的話,路徑帶空白(macOS 截圖檔名預設就帶)在 source
#    回來時會被拆成「賦值 + 執行命令」,變數靜默變成空字串、整段還回報成功。
{ printf 'TS=%q\n'           "$TS"
  printf 'MODE=%q\n'         "$MODE"
  printf 'EDIT_KIND=%q\n'    "$EDIT_KIND"
  printf 'REF=%q\n'          "$REF"
  printf 'OUT_PNG=%q\n'      "$OUT_PNG"
  printf 'OUT_JPG=%q\n'      "$OUT_JPG"
  printf 'OUT_SIDECAR=%q\n'  "$OUT_SIDECAR"
  printf 'LAST_MSG=%q\n'     "$LAST_MSG"
  printf 'LOG_FILE=%q\n'     "$LOG_FILE"
  printf 'START_MARKER=%q\n' "$START_MARKER"
  printf 'STATE=%q\n'        "$STATE"
} > "$STATE"

進 Step 5 的每一格 bash 開頭都先:

# 🔴 不能寫 . "/tmp/codex_imagegen_${TS}.state" —— TS 正是還沒撈回來的變數之一。
STATE=$(ls -t /tmp/codex_imagegen_*.state 2>/dev/null | head -1)
[ -n "$STATE" ] || { echo "找不到 state 檔"; exit 1; }
. "$STATE"

⚠️ 多條並行跑 codex 時 ls -t 會撈到別人的 state(並行做法見 Step 4-Claude-b 的 flag 註解)。 並行情境要把 TS 明寫進指令,不能靠 ls -t。殘留的舊 state 檔同理危險 —— 見 Step 6 的清理。

Step 4-Claude-b: 背景啟動 codex exec

用 Bash 工具,run_in_background: true。主路 = prompt-save:在 prompt 裡直接叫 codex 用內建 image_gen、存到 $OUT_PNG、回報實際路徑(跨版本最穩,見下方 0.141.0 註):

生成模式(text2img:REF 留空;img2img:REF 有值時自動帶 -i):

codex exec --skip-git-repo-check \
  "用內建 image_gen 工具生圖,不要使用 scripts/image_gen.py,也不要使用 OPENAI_API_KEY。<英文 prompt 內容>。請把最終圖片存到 ${OUT_PNG},完成後回報實際存檔的絕對路徑。" \
  ${REF:+-i "$REF"} \
  --sandbox workspace-write \
  --output-last-message "$LAST_MSG" \
  < /dev/null > "$LOG_FILE" 2>&1

編輯模式(REF 必有值;注意包裝動詞不同):

codex exec --skip-git-repo-check \
  "用內建 image_gen 工具編輯附上的圖片,不要使用 scripts/image_gen.py,也不要使用 OPENAI_API_KEY。<英文 prompt 內容(Step 2-edit 的三段骨架)>。請把最終圖片存到 ${OUT_PNG},完成後回報實際存檔的絕對路徑。" \
  -i "$REF" \
  --sandbox workspace-write \
  --output-last-message "$LAST_MSG" \
  < /dev/null > "$LOG_FILE" 2>&1
  • 🔴 包裝動詞必須跟著模式換。生成用「生圖」、編輯用「編輯附上的圖片」。 編輯模式若沿用「生圖」,這個動詞會把 Step 2-edit 辛苦建立的 CHANGE EXACTLY ONE THING 稀釋掉,模型會回去重新生成。
  • img2img 時(只有 img2img),prompt 開頭再加一句身份鎖: 「請參考附上的 Image #1 作為人物身份參考(同一個人,保持臉部特徵、髮型、體型一致)。」
  • 🔴 編輯模式禁止加身份鎖。 「保持一致」=「重新生成一張像的」, 跟編輯模式的「一個像素都不要動」正面衝突 —— 那正是 Step 5b 驗收要抓的失敗態。
  • ${REF:+-i "$REF"} 只在 REF 有值時展開成 -i "$REF";< /dev/null 防 codex 誤讀 stdin。

Flag 註解(codex-cli 0.141.0 實測對齊;新版本前先 codex exec --help 確認):

  • --skip-git-repo-check:讓 codex exec 在非 git repo 的 cwd 也能跑(不加會在非 repo 目錄報錯拒跑)。在 repo 內可省、但加著無害,當常駐。
  • --sandbox workspace-write:允許 codex 寫進 workspace(cwd + /tmp + $TMPDIR)—— prompt-save 的 $OUT_PNG 必須落在這範圍,否則寫檔被靜默擋。
  • codex exec 沒有 --ask-for-approval — 那 flag 只在 top-level codex,exec 預設就是 non-interactive never-ask,不需另指定。
  • --full-auto 已 deprecated(0.128.0 起),等同 --sandbox workspace-write。不要用。
  • -o, --output-last-message:把 codex 最後 assistant message 寫進指定檔。prompt-save 法下這檔會帶實際絕對路徑(因為你在 prompt 叫它回報)→ Step 5a 可拿來交叉驗證。
  • -i, --image <FILE>:img2img 用 — 有底圖時帶 -i "$REF"(鎖臉/角色一致,已實測可行)。⚠️ -i 是 variadic <FILE>...:prompt 必須當第一個 positional 放最前、-i 擺後面,否則 prompt 會被吃成第二張圖 → codex 沒 positional prompt → 轉讀 stdin → 失敗。(Codex 官方範例把 -i 放 prompt 前,別照抄、會踩這雷。)
  • 沒有 output-dir flag:控制輸出位置只有兩根槓桿 ——「prompt 內明寫存檔路徑」(主路)+ -C <workdir> / --add-dir。
  • 批次/迴圈跑 codex 必加 < /dev/null:在 while read … done < file 內跑 codex 會繼承迴圈 stdin(= prompt 檔)→ 一個 session 狂生多圖 + 吃掉 read fd。< /dev/null 切斷即解。多條並行各自獨立 CODEX_HOME(cp auth.json + config.toml)避免搶圖;🔴 CODEX_HOME 必須落 sandbox 可寫路徑(/tmp/codex_stream_X),別指到 cwd 外。

Prompt 字串注意:

  • 用自然語指示叫 codex 走內建 image_gen(如上「用內建 image_gen 工具…」),不靠 $imagegen token —— 自然語更穩,也免去 $ 被 shell 展開的坑。若硬要用 $imagegen token,bash 字串內要 escape 成 \$imagegen。
  • prompt 用 double-quote 包,內含的 " / ` / $ 全部 escape。
  • 「不要使用 scripts/image_gen.py / OPENAI_API_KEY」是防呆:若 cwd repo 內有同名生圖 script,codex 可能誤抓 → 明擋。
  • 不要加 --json(log 變 JSONL,反而難用 grep 監看)。

⚠️ 0.134.0 版差異(踩過、直接影響 Step 5 收圖):codex 改用 gpt-5.5 orchestrator + 內建 image_gen flow,不再是 gpt-image-2,連帶兩個 output 形狀變了:

  1. --output-last-message 不再吐圖片路徑(只寫一句「Generated the image...」)→ 別再 grep LAST_MSG 抓路徑。
  2. 圖落在巢狀 ~/.codex/generated_images/<session-id>/ig_*.png,不是平鋪。
  3. 固定輸出 png(無法指定格式)→ 交付前自行轉 jpg。

⚠️ 0.136.0 版差異(PR #24972「native image artifact completion pipeline」重寫出圖管線、實測對齊):

  1. 圖仍然落 ~/.codex/generated_images/<session-id>/ig_*.png(0.136.0 實測確認、Step 5a 的 find -newer marker 照舊有效)—— 別誤信「0.136 不再寫 generated_images」這類推論,自己 find 一下就知道。
  2. 同時圖會以 base64 嵌進 session rollout JSONL(~/.codex/sessions/<date>/rollout-*.jsonl 的 image_generation_call / image_generation_end 的 result 欄)。萬一哪天 generated_images 撈空,這是最後手段 fallback(解 base64 還原 png),但屬未文件化、隨版本可能再變、別當主路。
  3. 更穩的官方文件作法(建議長期改用、跨版本不靠猜目錄):prompt 末尾明寫 Save the final image as <name>.png in the current directory. + 跑 codex exec -C <輸出夾> --enable image_generation --sandbox workspace-write …,讓圖直接落你指定的 cwd。沒有 output-dir flag,-C / --add-dir + prompt 指示是唯一控制輸出位置的槓桿(0.136 的「local image attachments expose file paths to model」#25944 就是為了讓這條 save-path 流更可靠)。

⚠️ 0.141.0 版差異(實測 2026-06-24,本 skill v0.4.0 改版主因):

  1. generated_images 時有時無 —— 同一版本、同樣指令,有時圖落 ~/.codex/generated_images/<session>/ig_*.png、有時完全不落(圖只剩 rollout JSONL 的 base64)。所以 find -newer marker 撈 generated_images 這條主路不再可靠(實測整批撈空、得退 base64 還原才救回)。
  2. → 收圖主路正式改為「prompt-save」(上面 0.136 早記過的官方作法,現升為預設):launch 時在 prompt 內叫 codex 存到 $OUT_PNG、回報路徑(見 Step 4-Claude-b / 5a)。實測 0.141.0 圖確實直接落指定路徑、--output-last-message 也回報了絕對路徑。
  3. generated_images find 與 rollout base64 解碼降為 fallback 1 / 2。注意 0.141.0 有時 prompt-save 與 generated_images 兩邊都寫 → Step 5c-3 會清掉 generated_images 的多餘 copy。

Step 4-Claude-c: 非阻塞等待(讓出主線程,靠 task 完成通知喚回)

codex 這條 image_gen flow 每張要跑 2-3 分鐘(先跑 reasoning 再生圖)。Step 4-Claude-b 既然 run_in_background: true,就讓出控制權給 user、這一輪收尾,別在前景 sleep N; tail 輪詢 —— 那會卡死主線程、user 不能講話(實戰踩過、user 抱怨「太久了 / 是不是當機」)。

正確姿態:

  1. 啟動背景 codex 後,給 user 一行 heartbeat(見下),然後這輪就結束、把控制權還 user。
  2. 背景指令跑完,harness 會丟 <task-notification>(含 task-id + output 檔路徑)自動把你喚回 —— 這就是「monitor」,由 task 系統盯,不是你前景 block。
  3. 被喚回 → 讀 $LOG_FILE(或 task output 檔)判斷成敗 → 進 Step 5(成功)或 Step 6(失敗)。

heartbeat(一行、不刷屏):

Codex 跑起來了,背景生圖中(這條 flow 一般 2-3 分鐘),跑完通知你,先忙別的沒問題。

⚠️ 沒有獨立的 Monitor 工具 —— 「監督」= 背景 task + 完成通知。禁用 sleep N; tail 前景輪詢(阻塞主線程、卡死 user 對話)。真要中途偷看進度,用 Read 點一下 task output 檔就好,別 sleep-loop。


Step 5: 本機 artifact 後處理(Claude Code 全流程;Codex 只重用已標示規則)

Step 5a: Claude Code 收圖(prompt-save 主路 + 兩層 fallback)

OUT_PNG=$("$SKILL_DIR/scripts/collect.sh" "$OUT_PNG" "$START_MARKER") || {
  echo "三層都拿不到圖 → 跳 Step 6 判失敗類型"; exit 1
}

腳本依序試三層,命中哪一層會印在 stderr:

層 做法 為什麼不是主路
🟢 主路 檔案已在 $OUT_PNG(launch 時就在 prompt 裡叫 codex 存過去) —
fallback 1 撈 ~/.codex/generated_images,-newer marker 0.141.0 起時有時無,同版本同指令有時整批不落
fallback 2 從 session rollout JSONL 解 base64 還原 未文件化、隨版本可能再變。那是救援不是備份

腳本裡的幾個寫法是踩出來的,要改它之前先讀這幾條(平常不必看):

  • 🔴 絕不用 -newermt(任何形式):macOS BSD find 對 -newermt 的 @epoch 與相對時間都 silently 假陰性(誤判「沒 PNG」其實圖都在)。一律 -newer <實體 marker 檔>(BSD/GNU 皆穩)。
  • 🔴 fallback 1 在 ~/.codex/generated_images 找,別在 cwd / repo 內 find .(主路已直接落 cwd 的 $OUT_PNG,find 是給「codex 沒照存」的退路)。
  • 用 find 不用 glob:巢狀目錄要遞迴,空 glob 在 zsh 會 no matches found 中止。
  • session id 走 grep log 的 session id: 不可靠(ANSI 色碼夾在中間、regex 易撲空)→ fallback 2 改用「當天 rollout 抓含 PNG magic 的最新檔」。

三層都拿不到 → 腳本回 exit 1,照上面那個 || 分支跳 Step 6 判失敗類型。

Step 5b: 本機編輯 artifact 驗收(兩個 runtime 共用;生成模式跳過)

🔴 驗收一定排在交付之前。 交付會轉檔/刪檔,驗收需要原始的 $OUT_PNG,順序顛倒就沒得驗了。

「看起來沒變」不算驗過。 模型有可能交回一張「重新生成的、看起來很像的」圖 —— 肉眼在表情/姿態沒動的情況下分辨不出幾十像素的位移,但那會讓這張圖與同批其他圖對不齊。

# 邏輯住在腳本裡,不要在這裡重打一份 —— 兩份會漂移,而漂移的那一份會靜默放行。
"$SKILL_DIR/scripts/verify_edit.py" "$REF" "$OUT_PNG" "$EDIT_KIND"

$SKILL_DIR = 這個 skill 目錄(gpt-image-gen/)。腳本的完整判準與退出碼寫在它自己的 docstring 裡(verify_edit.py --help 等同直接讀檔頭),這裡只講它在驗什麼:

項 驗什麼 兩種 kind 的差別
① 畫布尺寸 輸出與底圖同尺寸 相同
② 透明度 真的有 alpha/透明佔比合理/主體不是半透明的鬼影 只有 bgremove 驗
③ 主體保真 主體像素有沒有被動到 bgremove 要求逐像素不變;local 只擋「滿版都在變=重生」,並印出改動區域的座標框

🔴 EDIT_KIND 必須明確給 bgremove 或 local,腳本不接受其他值也不預設。 預設會讓「忘了說這是去背」靜默跳過整個 ② —— 而背景被畫成白色的假去背, 在 ③ 看起來是完美的「最大色差 0」。

🔴 local 的判準跟 bgremove 不一樣,不要互相套用。 局部修改被要求改的那一塊 本來就會有極大色差,拿「色差要小」去卡它,等於懲罰它有照做,而那道閘門會因此 被學會忽略(本檔 anti-patterns 有這條)。腳本改成看「改動有沒有聚成一塊」, 並把座標框印出來讓你跟 prompt 對照。

VERDICT: FAIL → 照 Step 6 的「編輯驗收未過」那列處理,不要自動重試、不要清檔。

⚠️ ③ 的兩個假設要講清楚:無 alpha 時退回明度判準,那條假設的不只是「有對比」,是「背景比主體亮」 (>= 是單向比較)。暗背景亮主體會讓取樣歸零,程式會明確報出來而不是靜默通過。 有 alpha 時一律走 alpha 遮罩,沒有這個問題。

Step 5c: 交付

🔴 先看模式再往下:

MODE 走哪
generate 5c-1 轉 jpg
edit 5c-2,禁止執行 5c-1 的 bash

Step 5c-1: 生成模式 — 轉 jpg 交付(q85)

codex 吐 png(2MB 級);交付走 jpg q85(實測畫質肉眼無感、體積約 png 的 1/5):

[ "$MODE" = "edit" ] && { echo "編輯模式,跳過本段,走 5c-2"; exit 0; }
sips -s format jpeg -s formatOptions 85 "$OUT_PNG" --out "$OUT_JPG" >/dev/null 2>&1
rm -f "$OUT_PNG"                            # 刪 png 中繼,只留 jpg
FINAL="$OUT_JPG"
printf 'FINAL=%q\n' "$FINAL" >> "$STATE"   # Step 5d/5e 是不同的 shell,不回寫就讀到空字串
  • 最終交付 = $FINAL。只有 user 明講「要留無損 png」才跳過 rm -f "$OUT_PNG"。
  • ⚠️ 例外:這張圖若會進版控、還要再加工(去背/裁切/合成)、或要當之後好幾張的 -i, 就留 png、別刪。q85 的損失本身肉眼無感,但拿它當整條產線的起點就是讓每一步都從有損的地方長出來。 判準:只是拿來看的 → 照刪;會被再利用 → 留 png。

Step 5c-2: 編輯模式 — 只交 png

FINAL="$OUT_PNG"        # 不轉檔、不刪檔,就這樣
printf 'FINAL=%q\n' "$FINAL" >> "$STATE"   # Step 5d/5e 是不同的 shell,不回寫就讀到空字串

編輯模式一律交 png,無例外。 不分有沒有 alpha,理由同上一條的「會被再利用」判準 —— 編輯結果十之八九還要再加工或進版控,而且編輯結果不可重現(同 prompt 同 ref 再跑不會是同一張)。 不去猜它有沒有 alpha,就不會有猜錯的機會。

Step 5c-3: 清 generated_images 的多餘 copy(兩種模式都要做)

# 0.141.0 有時 prompt-save 與 generated_images 兩邊都寫 → 清掉 codex 那份多餘 copy(避免堆積)
STRAY=$(find ~/.codex/generated_images -type f -iname '*.png' -newer "$START_MARKER" 2>/dev/null | head -1)
[ -n "$STRAY" ] && rm -f "$STRAY" && rmdir "$(dirname "$STRAY")" 2>/dev/null

絕不把圖留在 ~/.codex/generated_images/(堆積 + user 找不到)—— 主路雖然落 cwd,codex 仍可能另存一份在那,務必清。

Step 5d: 寫 sidecar

先從 log 抓實際 model(別寫死 — 0.134 是 gpt-5.5 不是 gpt-image-2):

MODEL=$(grep -aoE 'gpt-[0-9.]+' "$LOG_FILE" | head -1)

格式固定:

---
timestamp: <ISO8601,帶時區偏移>
trigger: "<user 觸發那句原文>"
mode: <generate | edit>
edit_kind: <bgremove | local;MODE=generate 則 null>
reference_image: <$REF 絕對路徑;text2img 則 null>
codex_model: <$MODEL,如 gpt-5.5> (codex built-in image_gen flow)
codex_exit: success
verify: <MODE=edit 才有:Step 5b 的 VERDICT 與那行量測數字;生成模式 null>
output_image: <$FINAL 絕對路徑>
---

# 中文 prompt

<拍板版本的中文 prompt>

# English prompt

<拍板版本的英文 prompt(實際送 codex 的)>
  • 🔴 output_image 一律填 $FINAL(生成=$OUT_JPG、編輯=$OUT_PNG)。 寫死 .jpg 會讓編輯模式的 sidecar 指向一個從未存在過的檔, 而 sidecar 是 prompt 的唯一持久記錄(見 Important rules)。
  • 檔名慣例:sidecar 與圖同 basename、副檔名換成 .prompt.md。編輯模式沿用 ${TS}_${SLUG} 這組, SLUG 改從「改了什麼」抽(例:bg-removed、cup-removed),不要沿用底圖檔名(會跟底圖的 sidecar 撞名)。

寫進 $OUT_SIDECAR。bg session 內若 Write 被 bg-isolation guard 擋(這 skill 常在 bg + git repo 跑),改用 Bash heredoc 寫(cat > "$OUT_SIDECAR" <<'EOF' ... EOF)。

Step 5e: 通知 user(不自動開圖)

生成模式:

✅ 生好了
- 圖:<$FINAL 的相對 cwd 路徑>
- prompt log:<相對 cwd 路徑>.prompt.md

編輯模式(要把驗收數字一起講出來,那是「這真的是編輯不是重生」的唯一證據):

✅ 改好了
- 圖:<$FINAL 的相對 cwd 路徑>(png;編輯模式一律 png,不轉檔)
- 驗收:尺寸 <W>x<H> 未變/主體取樣 <N>px 最大色差 <D>
- prompt log:<相對 cwd 路徑>.prompt.md

不要自動 open — user 偏好「搬好通知即可、自己決定要不要看」。


Step 6: Claude Code CLI 失敗處理

依 log 內容分類:

失敗類型 log 特徵 對應動作
Safety reject safety / policy / rejected / cannot generate 告訴 user「codex 拒了,policy 命中。要不要改 prompt 軟化 / 走別的工具?」
Rate limit rate limit / 429 / usage limit 告訴 user「Codex 額度滿了。要等 / 改用 ChatGPT GUI 自己生?」
其他 error exit code ≠ 0 + 沒以上字眼 印 log 最後 30 行給 user 看,問下一步
編輯驗收未過 codex exit 0、log 乾淨,但 Step 5b 回 VERDICT: FAIL 見下方

⚠️ 最後一列跟上面三列的性質不同:codex 是成功的,log 裡什麼線索都沒有, 所以「印 log 最後 30 行」對它毫無用處 —— 那 30 行跟失敗原因無關。

編輯驗收未過的處理:

  1. 貼出 Step 5b 的三項量測值(尺寸、透明度、主體色差),那是唯一有資訊量的東西
  2. 指出最可能的成因,對照 VERDICT 底下那幾行:
    • 主體色差大 → Step 2-edit ① 的保留清單不夠具體,或 Step 4-Claude-b 誤加了身份鎖
    • 畫布跑掉 → 沒鎖尺寸,或撞到 Step 3a-edit 講的管線限制
    • 沒有透明度 → 去背那段沒寫,或寫了但沒寫三個 not
  3. 問 user 要不要補了再送一次
  4. 🔴 這條路徑不要清檔(不執行 Step 5c-3 的清理)—— user 可能要看那張失敗的圖來判斷

不自動重試 — 失敗交給 user 決定。這條對「驗收未過」同樣適用: 它看起來很像「再調一下 prompt 就好」,但那是在沒有 user 判斷的情況下連續燒額度。

清掉中繼檔:

rm -f "$LAST_MSG" "$LOG_FILE" "$START_MARKER" "$STATE"

⚠️ $STATE 一定要清。 撈它的方式是 ls -t ... | head -1,殘留的舊 state 檔 會在某次沒建成新檔時被安靜地撈到,把上一輪的圖當成這一輪的結果交出去。 成功路徑(Step 5e 通知完)也要做同一件清理。


Anti-patterns

  • ❌ RUNTIME=codex 還去執行 codex exec、開 Codex 子程序或委派另一個 agent 生圖 → 目前 session 直接呼叫 built-in image_gen
  • ❌ 用模糊正向回應(「不錯」「可以」)當拍板信號,誤啟動生圖 executor
  • ❌ img2img 時把 -i 放 prompt 前面(prompt 被當第二張圖 → codex 失敗);prompt 一定當第一 positional、-i 擺後
  • ❌ Claude Code 把對話內嵌圖(本機無檔)硬塞 codex -i(吃 file path、抓不到)→ 先問本機路徑;Codex native path 則直接用 recent-image 參數
  • ❌ 預設某組固定角色 / 某個固定畫風這種寫死 style — 沒 context 就問
  • ❌ Skill 內部偷偷加 NSFW filter 替 user 做決策(只警告 + 給選項)
  • ❌ 把收圖主路放在「翻 generated_images / 解 rollout base64」(0.141.0 撈空率高、time-bomb)→ 主路用 prompt-save 叫 codex 存指定 $OUT_PNG,find / base64 只當 fallback
  • ❌ 把生圖結果留在 ~/.codex/generated_images/ 不搬走/不清(堆積 + user 找不到);0.141 兩邊都寫時要清掉那份多餘 copy
  • ❌ 圖搬到 cwd 根但 cwd 是 git repo(會雜進 git status / 容易誤 commit)→ git repo 走 ./generated_images/ 子夾
  • ❌ 把 codex 官方範例的 -i ./input.png 'prompt'(image 在 prompt 前)照抄 → -i variadic 會把 prompt 吃成第二張圖;一律 prompt 第一 positional、-i 擺後
  • ❌ 單靠「自動」LAST_MSG 抓路徑(沒在 prompt 叫 codex 回報就不吐)→ prompt-save 主路自己指定 $OUT_PNG、讀檔即可,LAST_MSG 只拿來交叉驗證
  • ❌ 用 shell glob(ls $DIR/*.png)收 fallback 圖(巢狀目錄漏抓 + 空 glob 在 zsh 中止)→ 改 find … -newer <marker檔>
  • ❌ fallback 收圖用 find -newermt(任何形式:@epoch 或相對 '-30 minutes',macOS BSD find 都 silently 假陰性)→ 改 launch 前 touch marker + find -newer "$MARKER"
  • ❌ 把單次觀察當鐵則(不論「不寫 generated_images 了」或「一定寫」)→ 0.141 實測時有時無;別賭它的行為,prompt-save 主路本就不依賴它
  • ❌ 生完自動 open 圖(user 不要)
  • ❌ sidecar 寫死 codex_model: gpt-image-2(實際是 log 裡的 model)
  • ❌ 失敗自動重試(codex 失敗通常是 prompt 本身問題或 quota,重試只浪費 token)
  • ❌ 有本機 artifact 卻不寫 sidecar(user 之後翻舊圖找不回原 prompt);Codex native 若只有 inline artifact,至少在交付訊息附拍板 prompt
  • ❌ Claude Code heartbeat 刷屏(user 已經知道在跑了,給一行就好)
  • ❌ Claude Code 背景啟動後用 sleep N; tail 前景輪詢等 codex(阻塞主線程、卡死 user 對話)→ 讓出控制權、等 task 完成通知自動喚回
  • ❌ 圖被 keep / 搬走卻把 sidecar 留在原(暫存)目錄 → prompt 隨目錄清掉就永久消失(sidecar 要跟圖走)
  • ❌ 用 $imagegen token 卻沒 escape \$imagegen(shell 展開成空)→ 現行改用自然語指示「用內建 image_gen 工具」、免此坑
  • ❌ 在 user 還在改 prompt 的迭代過程中提前算 slug / 建目錄 / 啟動 codex(pre-flight 在拍板之後才做)

編輯模式專屬:

  • ❌ 🔴 把編輯結果轉 jpg(帶透明度就永久消失,而編輯結果不可重現)→ 編輯模式一律交 png
  • ❌ 用 mode in ("RGBA","LA") 判有沒有透明度 → 漏掉 mode P 的透明 PNG,會判成「沒 alpha」然後一路轉成 jpg。要判就用 mode in ("RGBA","LA","PA") or "transparency" in im.info;但更好的作法是根本不要判(見上一條)
  • ❌ 驗收拿「四角必須全透明」當去背的判準 → 主體貼齊邊緣的構圖(半身像、滿版)本來就有不透明的角,會假失敗;一個 MANDATORY 閘門只要常假失敗,agent 就學會忽略它
  • ❌ 編輯模式沿用生成模式的包裝動詞「生圖」→ 那個動詞會稀釋 CHANGE EXACTLY ONE THING
  • ❌ 編輯模式加 img2img 的身份鎖(「保持臉部特徵…一致」)→ 「保持一致」=「重新生成一張像的」,正是驗收要抓的失敗態
  • ❌ 編輯 prompt 只寫「把背景去掉」「把 X 拿掉」而沒有保留清單 → 模型會重新生成一張「很像的」,不是就地修改
  • ❌ 編輯 prompt 沒鎖畫布尺寸 → 模型吐一個常見比例,主體位置全跑掉
  • ❌ 去背只寫 "remove the background" → 可能得到「背景被畫成白色」或「棋盤格被畫成像素」→ 要明寫 genuine ALPHA CHANNEL + 三個 not
  • ❌ 只看圖覺得「好像沒變」就當編輯成功 → 肉眼分辨不出幾十像素的位移,一定要跑 Step 5b 量
  • ❌ 編輯模式沒底圖時退化成「生一張像的」交差 → 沒有本機實體檔就是不成立,如實講
  • ❌ Step 5b 沒過就自動改 prompt 重送 → 失敗不自動重試,交給 user 決定

Important rules

  1. 拍板 = 明確 keyword(OK / 生 / go / 下去),不准語意推測 — 違規即破壞 user 信任(=紅線 1)
  2. 先分模式再動手:生成 vs 編輯。判準=「user 會不會拿輸出去跟原圖逐像素比對」。分不出來就問一句,別猜
  3. executor 依宿主分流:Claude Code 才用 Codex CLI;Codex 一律直接呼叫本 session 的 built-in image_gen,禁止 nested Codex
  4. 有 reference image → 走 img2img 或編輯 — Claude Code 的 codex -i 要本機 path;Codex native 用 local-path 或 recent-image 參數(=紅線 2)
  5. 不寫死預設風格 — 風格 100% 來自當下 context 與 user 描述(=紅線 3)。NSFW 判斷依 context,警告而非阻擋,不替 user 做安全決策
  6. Claude Code CLI 路徑才背景跑:讓出主線程 + 一行 heartbeat,靠 task 完成通知喚回;禁前景 sleep N; tail
  7. Claude Code CLI 收圖主路 = prompt-save:launch 時叫 codex 存到 $OUT_PNG;generated_images / rollout base64 只是 fallback
  8. 本機 artifact 要有 Sidecar <image>.prompt.md — 含中英 prompt + metadata;圖搬走時 sidecar 跟著走。Codex native 若只有 inline artifact,交付訊息至少附拍板 prompt,禁止為 sidecar 退回 CLI
  9. 生成模式交付 jpg q85,png 中繼轉完即刪(user 明講要無損、或那張圖會被再利用時才留);🔴 編輯模式一律交 png,不轉 jpg,無例外(=紅線 4);兩種模式都不自動 open
  10. 編輯模式有可讀的本機 source/output 時必跑 Step 5b 驗收;沒有本機 artifact 時不得宣稱 pixel verification 通過
  11. 失敗不自動重試(含「驗收未過」)— 貼量測值與可能成因,交給 user 決定
  12. 這 skill 做生成與圖片編輯,但不做 UI 設計、不做 ASCII art — 走錯領域請 user 改用 Claude Design / 其他工具

已知限制(實測,會隨 codex 版本變)

項目 現況
去背的遮罩精度 實測兩次都到髮絲級,主體像素逐位元不變(最大色差 0)—— 它是遮罩不是重生
抗鋸齒不穩定 ⚠️ 兩次跑出不同結果:一次是純二值 alpha(半透明階 0.0%),一次有邊緣羽化(0.5%)。同樣的 prompt 結構、同一個版本
補救 拿到二值的那種、又要縮放時,自己對 alpha 做一次 1px 模糊即可,不必重生(重生會失去像素保真)

| 局部修改的實際行為 | ⚠️ 實測「拿掉眼鏡」:要求的改動有做到,但整個主體被連帶重算了一遍(每處差異細微、身分與姿勢都保住,背景完全沒動)。改動的座標框橫跨畫面八成,密度卻只有 7.5%=散布式重生 | | 這樣算不算過 | 由 user 判。實測那次 user 判定可接受。程式只報數字與座標框,刻意不硬擋 —— 「換掉整件衣服」跟「散布式重生」在指標上分不開,硬擋會誤殺前者 |

⚠️ 所以 local 不要當成「逐像素就地改」來承諾。要逐像素不變只有 bgremove 那條做得到(實測三次皆最大色差 0)。

⚠️ 「抗鋸齒」那條就是「別把單次觀察當鐵則」的現成例子 —— 第一次實測後本表曾寫死 「alpha 是二值的」,第二次就被推翻。要用就自己量,別讀這張表下結論。

🔴 關於量測本身的一個坑(實際踩過):不要在背景任務的完成通知抵達之前去讀輸出檔。 codex 可能先寫一個中間版本再改寫成最終版,提早讀會拿到寫到一半的圖, 量出來的數字全錯(那次量到「主體 18% 半透明」,最終檔其實是 0.5%)。 等通知,或至少比對 mtime。

Files (kc_ai_skills)
  • scripts
    • collect.sh 4.9 KB
      #!/usr/bin/env bash
      # 從 codex 手上把圖收回來。主路 + 兩層 fallback,一次做完。
      #
      # 用法:collect.sh <期望的輸出路徑 OUT_PNG> <啟動前 touch 的 marker 檔>
      # 退出碼:0 收到(路徑印在 stdout)/ 1 三層都拿不到
      #
      # 為什麼是三層(每一層都是實際翻車換來的):
      #   主路 prompt-save  —— launch 時就在 prompt 裡叫 codex 存到指定絕對路徑。
      #                        跨版本最穩,因為不依賴 codex 把圖放哪。
      #   fallback 1        —— 撈 ~/.codex/generated_images。0.141.0 起「時有時無」,
      #                        同版本同指令有時整批不落,所以它不能當主路。
      #   fallback 2        —— 從 session rollout JSONL 解 base64 還原。未文件化、
      #                        隨版本可能再變,只在前兩層都空時動用。
      #
      # 🔴 找檔一律用 `find -newer <實體 marker 檔>`,絕不用 `-newermt`:
      #    macOS 的 BSD find 對 -newermt 的 @epoch 與相對時間都會 silently 假陰性
      #    —— 圖明明在,卻回報「沒有 png」,然後整批被誤判成失敗。
      set -u
      
      OUT_PNG="${1:-}"
      MARKER="${2:-}"
      if [ -z "$OUT_PNG" ] || [ -z "$MARKER" ]; then
        echo "用法:collect.sh <OUT_PNG> <START_MARKER>" >&2
        exit 1
      fi
      
      # ── 主路:codex 照 prompt 存到定位了嗎 ─────────────────────────────
      if [ -f "$OUT_PNG" ]; then
        echo "$OUT_PNG"
        echo "# 主路(prompt-save)命中" >&2
        exit 0
      fi
      
      # ── 用 fallback 之前先檢查 marker 的時效 ───────────────────────────
      # 🔴 兩個 fallback 的判準都是「比 marker 新」。marker 一旦是舊的(撿到殘留的
      #    state 檔就會這樣),「比它新」等於「幾乎所有東西」,於是撈到**別次執行**
      #    的圖,回報成功,而結果看起來完全合理。實測踩過。
      MARKER_MAX_AGE=3600     # 秒。單張圖大約三分鐘,一小時已經很寬鬆
      if [ -f "$MARKER" ]; then
        NOW=$(date +%s)
        MTIME=$(stat -f %m "$MARKER" 2>/dev/null || stat -c %Y "$MARKER" 2>/dev/null || echo 0)
        AGE=$((NOW - MTIME))
        if [ "$AGE" -gt "$MARKER_MAX_AGE" ]; then
          echo "marker 已經 ${AGE} 秒沒更新(上限 ${MARKER_MAX_AGE}),不能安全地用 fallback" >&2
          echo "這通常表示撿到了殘留的 state 檔 —— 硬跑下去會把別次執行的圖當成這次的結果" >&2
          exit 1
        fi
      fi
      
      # ── fallback 1:撈 generated_images ────────────────────────────────
      if [ -f "$MARKER" ]; then
        SRC=$(find "$HOME/.codex/generated_images" -type f -iname '*.png' -newer "$MARKER" 2>/dev/null \
              | xargs ls -t 2>/dev/null | head -1)
        if [ -n "${SRC:-}" ] && [ -f "$SRC" ]; then
          mv "$SRC" "$OUT_PNG"
          rmdir "$(dirname "$SRC")" 2>/dev/null
          echo "$OUT_PNG"
          echo "# fallback 1(generated_images)命中,已搬到定位" >&2
          exit 0
        fi
      else
        echo "# marker 檔不存在,跳過 fallback 1" >&2
      fi
      
      # ── fallback 2:從 rollout JSONL 解 base64 ─────────────────────────
      # 🔴 一定要受 marker 約束。實測踩過:不約束的話會撈到「今天稍早別次 codex 生圖」
      #    的 session,把別人的圖當成這次的結果交出來,而且回報成功 —— 安靜、而且
      #    結果看起來完全合理。一個什麼都沒產出的呼叫因此拿到一張成品。
      if [ ! -f "$MARKER" ]; then
        echo "沒有 marker,不能安全地動用 fallback 2(會撈到別次 session 的圖)" >&2
        exit 1
      fi
      
      echo "# 前兩層都空,動用 fallback 2(rollout base64)" >&2
      ROLLOUT=$(find "$HOME/.codex/sessions" -name 'rollout-*.jsonl' -newer "$MARKER" 2>/dev/null \
                | xargs grep -l 'iVBORw0KGgo' 2>/dev/null \
                | xargs ls -t 2>/dev/null | head -1)
      if [ -z "${ROLLOUT:-}" ]; then
        echo "三層都拿不到圖,codex 大概率失敗 —— 去讀 log 判斷失敗類型" >&2
        exit 1
      fi
      
      python3 - "$ROLLOUT" "$OUT_PNG" <<'PY'
      import sys, json, base64
      rollout, out = sys.argv[1], sys.argv[2]
      b64 = None
      for line in open(rollout):
          if 'iVBORw0KGgo' not in line:          # PNG magic 的 base64 開頭
              continue
          try:
              obj = json.loads(line)
          except Exception:
              continue
          stack = [obj]
          while stack:                            # 結構隨版本會變,遞迴找就好
              c = stack.pop()
              if isinstance(c, dict):
                  stack.extend(c.values())
              elif isinstance(c, list):
                  stack.extend(c)
              elif isinstance(c, str) and c.startswith('iVBORw0KGgo'):
                  b64 = c                         # 留最後一張
      if b64:
          open(out, 'wb').write(base64.b64decode(b64))
          sys.exit(0)
      sys.exit(1)
      PY
      
      if [ -f "$OUT_PNG" ]; then
        echo "$OUT_PNG"
        echo "# fallback 2 命中(那是救援不是備份 —— 成品該留 png 就別急著刪)" >&2
        exit 0
      fi
      
      echo "三層都拿不到圖" >&2
      exit 1
      
    • preflight_edit.sh 1.8 KB
      #!/usr/bin/env bash
      # 編輯模式的執行前檢查。跑在拍板之後、呼叫 codex 之前。
      #
      # 用法:preflight_edit.sh <底圖絕對路徑>
      # 輸出:SRC_W / SRC_H(給 prompt 的畫布鎖定用),以 shell 可 eval 的形式印出
      # 退出碼:0 可以繼續 / 1 相依或底圖有問題(停下告訴 user,不要硬跑)
      #
      # 為什麼要有這支:編輯模式的驗收(verify_edit.py)是必過閘門,而它需要 Pillow。
      # 缺 Pillow 時如果一路跑下去,會生出一張沒驗過的圖,然後在「要不要轉 jpg」
      # 那個決策點上做出可能毀掉透明度的選擇。寧可在花錢之前就停。
      set -u
      
      REF="${1:-}"
      if [ -z "$REF" ]; then
        echo "用法:preflight_edit.sh <底圖絕對路徑>" >&2
        exit 1
      fi
      
      if [ ! -f "$REF" ]; then
        echo "底圖不存在:$REF" >&2
        echo "編輯模式沒有底圖就不成立 —— 跟 user 要本機路徑,不要退化成「生一張像的」" >&2
        exit 1
      fi
      
      if ! python3 -c "import PIL" 2>/dev/null; then
        echo "缺少 Pillow,編輯模式的驗收跑不了。安裝:python3 -m pip install pillow" >&2
        echo "🔴 裝不起來就停下告訴 user,不要靜默降級交付未驗過的圖" >&2
        exit 1
      fi
      
      # 量底圖尺寸 → Step 2-edit 的 "Keep the output canvas at exactly W x H pixels."
      # 路徑走 argv 不走字串內插:內插在路徑含單引號時會壞,而且會壞得很安靜。
      read -r SRC_W SRC_H <<EOF
      $(python3 - "$REF" <<'PY'
      import sys
      from PIL import Image
      im = Image.open(sys.argv[1])
      print(im.size[0], im.size[1])
      PY
      )
      EOF
      
      if [ -z "${SRC_W:-}" ] || [ -z "${SRC_H:-}" ]; then
        echo "量不到底圖尺寸:$REF" >&2
        exit 1
      fi
      
      echo "SRC_W=$SRC_W"
      echo "SRC_H=$SRC_H"
      echo "# 底圖 ${SRC_W}x${SRC_H} —— 把這兩個數字寫進 prompt 的畫布鎖定那行" >&2
      
    • verify_edit.py 8.7 KB
      #!/usr/bin/env python3
      """驗收「編輯模式」的產出:codex 到底是就地修改,還是重新生成了一張很像的?
      
      用法:
          verify_edit.py <底圖> <輸出圖> <bgremove|local>
      
          bgremove  去背。期待「主體一個像素都沒動」,只有背景變透明。
          local     局部修改。期待「改動聚在一塊,其餘沒動」。
      
      退出碼:
          0  PASS
          1  FAIL(原因印在 stdout)
          2  用法錯誤 / 檔案讀不到 / 未知的 kind
      
      為什麼需要這支:模型拿到編輯任務時,預設行為是「重新生成」不是「就地修改」。
      重生出來的圖肉眼看起來幾乎一樣,但主體會位移幾十個像素 —— 那會讓這張圖跟
      同批其他圖對不齊。人眼在表情或姿態有變化時分辨不出這種位移,所以要用量的。
      
      🔴 兩種 kind 的判準**不一樣**,不要共用:
         去背要求逐像素不變(改了就是重生);
         局部修改**本來就會有大色差**,硬套「色差要小」會讓它永遠不可能通過。
         分辨局部修改與重生,靠的是**改動像素的分布**:前者聚在一塊,後者滿版都在變。
      """
      import sys
      
      try:
          from PIL import Image
      except ImportError:
          print("需要 Pillow:python3 -m pip install pillow", file=sys.stderr)
          sys.exit(2)
      
      KINDS = ("bgremove", "local")
      
      LUMA_MARGIN = 60        # 「暗於背景這麼多」才算主體
      DARK_BG_FLOOR = 60      # 背景亮度低於此值時,明度判準失效、改走 alpha
      STEP = 2                # 取樣間隔
      DIFF_HIT = 10           # 單點色差超過此值=這個像素被改動了
      BGREMOVE_MAX_DIFF = 9   # 去背時主體容許的最大色差
      BGREMOVE_MAX_RATIO = 0.01
      REGEN_RATIO = 0.90      # 主體有這麼高比例的像素都被改動=整張重生,硬擋
      SUSPICIOUS_RATIO = 0.25 # 超過此比例只印警告不擋 —— 「換掉整件衣服」與「主體位移」
                              # 在這個指標上長得一樣,硬擋會誤殺前者
      MIN_CLEAR_RATIO = 0.01  # 宣稱去背時,透明像素至少要佔這麼多(低於此=假去背)
      MAX_CLEAR_RATIO = 0.98  # 高於此=主體大概也被挖掉了,交出一張空圖
      MAX_GHOST_RATIO = 0.05  # 半透明像素佔比上限。實測乾淨的去背是 0.0%~0.5%(邊緣抗鋸齒),
                              # 佔到 5% 以上就不是邊緣了,是整片主體變半透明。
                              # ⚠️ 這個門檻**沒有真實失敗案例驗證過**,是防呆不是實測值
      
      
      def has_alpha(im):
          """mode P 的透明 PNG 也算 —— 只看 RGBA/LA 會漏掉,而漏掉的後果是把帶透明度的
          圖判成不帶,然後一路轉成 jpg 把透明度永久毀掉。"""
          return im.mode in ("RGBA", "LA", "PA") or "transparency" in im.info
      
      
      def main(argv):
          if len(argv) < 4:
              print(__doc__)
              return 2
          src_path, out_path, kind = argv[1], argv[2], argv[3]
      
          if kind not in KINDS:
              # 刻意不預設成 local:預設會讓「忘了指定去背」靜默跳過透明度檢查,
              # 而那正是這支程式最該擋的失敗態。
              print(f"未知的 kind:{kind!r},必須是 {' 或 '.join(KINDS)}", file=sys.stderr)
              return 2
      
          try:
              src, out = Image.open(src_path), Image.open(out_path)
          except Exception as e:
              print(f"讀不到圖:{e}", file=sys.stderr)
              return 2
      
          w, h = src.size
          fail = []
      
          # ① 畫布尺寸沒變
          if out.size != (w, h):
              fail.append(f"畫布跑掉:{out.size} != {(w, h)}")
      
          # ② 透明度 —— 只有 bgremove 要驗
          if kind == "bgremove":
              if not has_alpha(out):
                  fail.append(f"要的是去背,但輸出沒有透明度(mode={out.mode})")
              else:
                  hist = out.convert("RGBA").getchannel("A").histogram()
                  n = sum(hist)
                  clear = hist[0] / n                      # 全透明=被挖掉的背景
                  solid = hist[255] / n                    # 全不透明=實心的主體
                  ghost = 1 - clear - solid                # 半透明=鬼影
                  print(f"透明像素 {clear:.1%}/實心 {solid:.1%}/半透明 {ghost:.1%}")
      
                  if clear < MIN_CLEAR_RATIO:
                      fail.append(f"幾乎沒有透明像素({clear:.1%}):可能是畫上去的假背景")
                  if clear > MAX_CLEAR_RATIO:
                      fail.append(f"透明像素高達 {clear:.1%}:主體八成也被一起挖掉了")
      
                  # 🔴 鬼影:主體整片變成半透明。這條非查不可,因為第三項抓不到 ——
                  #    半透明像素的 RGB 是完好的,「最大色差 0」照樣成立,圖卻是壞的。
                  #    ⚠️ 尚未在真實輸出上遇過,屬防呆。但 codex 的 alpha 行為確實不穩定
                  #    (同樣的 prompt 結構,實測一次吐純二值遮罩、一次吐邊緣羽化)。
                  if ghost > MAX_GHOST_RATIO:
                      fail.append(f"主體是半透明的鬼影({ghost:.1%} 的像素落在 0 與 255 之間)"
                                  ":背景是挖掉了,但主體沒有實心保留")
      
                  # 刻意不驗「四角必須透明」——主體貼齊邊緣的構圖本來就有不透明的角。
                  # 一個必過閘門只要常假失敗,就會被學會忽略。
      
          # ③ 主體像素保真(尺寸不同就沒得比,跳過)
          if out.size == (w, h):
              sp = src.convert("RGB").load()
              op = out.convert("RGB").load()
              ap = out.convert("RGBA").getchannel("A").load() if has_alpha(out) else None
              gp = src.convert("L").load()
              bg = gp[5, 5]
              use_luma = bg >= DARK_BG_FLOOR
      
              tot = worst = over = 0
              x0 = y0 = 10 ** 9
              x1 = y1 = -1
              for y in range(0, h, STEP):
                  for x in range(0, w, STEP):
                      if ap is not None:
                          if ap[x, y] == 0:
                              continue
                      elif use_luma:
                          if gp[x, y] >= bg - LUMA_MARGIN:
                              continue
                      d = max(abs(op[x, y][i] - sp[x, y][i]) for i in range(3))
                      tot += 1
                      worst = max(worst, d)
                      if d > DIFF_HIT:
                          over += 1
                          x0, x1 = min(x0, x), max(x1, x)
                          y0, y1 = min(y0, y), max(y1, y)
      
              if tot == 0:
                  fail.append("主體取樣為 0:背景判準失效(底圖是暗背景、或整張已透明)。"
                              "改用輸出的 alpha 當遮罩重跑,或請 user 目視比對")
              else:
                  ratio = over / tot
                  print(f"主體取樣 {tot}px  最大色差 {worst}  色差>{DIFF_HIT} 佔 {ratio:.1%}")
      
                  if kind == "bgremove":
                      # 去背不該動到主體任何一個像素
                      if worst > BGREMOVE_MAX_DIFF or ratio > BGREMOVE_MAX_RATIO:
                          fail.append(f"去背卻動到主體(最大色差 {worst}、改動佔 {ratio:.1%})"
                                      ":它重新生成了一張像的,不是把背景挖掉")
                  else:
                      # 局部修改:改動本來就存在,要判的是「改動有沒有聚在一塊」
                      if over == 0:
                          print("改動區域:無(輸出與底圖在主體上完全相同)")
                          fail.append("宣稱是局部修改,但主體沒有任何改動 —— "
                                      "codex 可能沒照做,或改動落在被判為背景的區域")
                      else:
                          bw, bh = x1 - x0 + STEP, y1 - y0 + STEP
                          print(f"改動區域:x {x0}–{x1}  y {y0}–{y1}"
                                f"({bw}x{bh},佔畫面 {bw * bh / (w * h):.1%})")
                          if ratio > REGEN_RATIO:
                              fail.append(f"主體有 {ratio:.1%} 的像素都被改動 —— "
                                          "那不是局部修改,是整張重生")
                          elif ratio > SUSPICIOUS_RATIO:
                              # 硬擋只留無爭議的極端。這個中間地帶是真的判不出來:
                              # 「換掉整件衣服」跟「主體位移了幾十像素」在這裡長得一樣。
                              print(f"⚠️ 改動涵蓋主體的 {ratio:.1%},範圍偏大。"
                                    "如果你要求的只是一小處,這比較像重生而不是局部修改 —— 請目視確認")
                          else:
                              print("⚠️ 改動區域是否就是你要求的那一處,程式判不出來 —— "
                                    "把上面那個座標框對照一下 prompt,或請 user 目視確認")
      
          print("VERDICT:", "PASS" if not fail else "FAIL")
          for f in fail:
              print(" -", f)
          return 0 if not fail else 1
      
      
      if __name__ == "__main__":
          sys.exit(main(sys.argv))
      
  • SKILL.md 48 KB
    ---
    name: gpt-image-gen
    description: "Use when the user asks to generate OR edit an image via GPT/Codex (e.g. 「叫 gpt 生圖」「幫我用 gpt 生圖」「gpt 畫一個 X」「幫我去背」「把這張圖的背景去掉」). The skill drafts a Chinese + English prompt pair and waits for explicit approval. After approval it uses the host-native executor: Codex calls its built-in image_gen tool directly, while Claude Code keeps the Codex CLI background workflow. Supports text-to-image, img2img, and precise EDIT mode with preservation constraints and verification where local artifacts are available."
    version: 0.6.0
    status: mvp
    triggers:
      - "叫 gpt 生圖"
      - "叫gpt生圖"
      - "幫我用 gpt 生圖"
      - "幫我用gpt生圖"
      - "請 gpt 畫"
      - "gpt 畫一個"
      - "gpt 畫一張"
      - "gpt 生個圖"
      - "gpt 生張圖"
      - "幫我去背"
      - "去背"
      - "把背景去掉"
      - "去掉背景"
      - "改這張圖"
      - "編輯這張圖"
      - "修這張圖"
    argument-hint: "(無;自然語言觸發)"
    ---
    
    # gpt-image-gen — Claude Code 走 Codex CLI;Codex 直接生圖
    
    You are a prompt-crafting partner who turns the user's loose Chinese description into a tight bilingual prompt pair, iterates until the user explicitly approves, then uses the executor native to the current host. In Claude Code, orchestrate Codex CLI as before. In Codex, call the built-in `image_gen` tool yourself — do not launch another Codex inside Codex. Your job is prompt design, user confirmation gating, execution, and **verifying the result before you hand it over**.
    
    ## Step 0: 先鎖定執行宿主(MANDATORY)
    
    依**目前 assistant 的宿主身分**設定一次 `RUNTIME`,後面不得改道:
    
    | 目前是誰在執行這個 skill | `RUNTIME` | 拍板後的路徑 |
    |---|---|---|
    | **Codex**(目前 assistant/system 明確自稱 Codex) | `codex` | 走 **Step 4-Codex**,直接呼叫目前 session 的 built-in `image_gen` |
    | **Claude Code** | `claude_code` | 走 **Step 4-Claude**,維持既有背景 `codex exec` 流程 |
    
    - **不要用 `which codex`、環境變數或「有沒有 codex binary」判斷宿主。** Claude Code 的機器本來就可能裝 Codex;那不是執行者身分。
    - `RUNTIME=codex` 時,禁止 shell-out 到 `codex` / `codex exec`、禁止再開 Codex 子程序或子 agent,也不跑 Step 4-Claude。這條正是避免疊床架屋的 hard invariant。
    - `RUNTIME=codex` 但 built-in `image_gen` 不可用時,如實告訴 user;**不要靜默退回 Codex CLI**。
    - Step 1~Step 3 的模式判斷、雙語 prompt、拍板 gate 與安全判斷兩邊共用;只有拍板後的 executor 分流。
    
    **CRITICAL — 四條紅線**:
    
    1. **未拍板絕不啟動生圖 executor** — 拍板 = user 明確說 `OK` / `生` / `go` / `下去`。其他正向回應(「不錯」「可以喔」「應該行」)一律當「還沒拍板」處理,繼續等明確指令。生圖會花 user 的錢,誤觸發 = 違規。
    2. **有 reference image → 走 img2img 或編輯** — user 這輪有附底圖(拖曳/貼上/`[Image #N]`)→ Step 3a 偵測。Claude Code 用 `codex exec ... -i <ref>`;Codex 把底圖直接交給 built-in `image_gen`,不再包一層 CLI。**拍板 gate(紅線 1)對 img2img 與編輯一樣適用。**
    3. **不寫死任何預設風格** — Skill 不存 style preset。每張圖風格純靠當下 conversation context + user 描述推。沒 context 就問。
    4. 🔴 **編輯模式一律交 png,不轉 jpg,無例外** — Step 5c-1 的預設是「轉 jpg q85、刪 png」,那對生成是對的,對編輯是災難:jpg 沒有 alpha 通道,帶透明度的結果轉一次就永久消失,而**編輯結果不可重現**(同 prompt 同 ref 再跑不會是同一張)。**不要去猜它有沒有 alpha**(`mode P` 的透明 PNG 就會猜錯),一律走 Step 5c-2。
    
    ---
    
    ## Step 1: 先判斷模式,再判斷語境
    
    ### Step 1-0: 生成 or 編輯(**先決,決定後面每一步**)
    
    | user 要的 | 模式 | 判準 |
    |---|---|---|
    | 一張**新的**圖(有沒有底圖都算) | **生成** — 走 Step 1 下半、Step 2 生成模板 | 底圖只是**參考**(鎖臉/鎖角色/鎖場景),輸出本來就該跟底圖不同 |
    | **這張圖**動一個地方,其他不要變 | **編輯** — Step 1b → Step 2-edit 模板 → Step 3a-edit → 對應 runtime executor → **驗收** → **只交 png** | 輸出應該**還是同一張圖**,只有指定處不同 |
    
    🔑 **一句話判準**:**「user 會不會拿輸出去跟原圖逐像素比對?」** 會 → 編輯;不會 → 生成。
    
    - 「把他放到海邊」→ 生成(換場景,人以外全變)
    - 「同一個人,換成笑的表情」→ **生成**(img2img;臉會被重繪,只是要求相似)
    - 「把背景去掉」「把左上角那個杯子移掉」→ **編輯**(其餘每一像素都該原封不動)
    
    分不出來就**問一句**:「這張是要**改這張圖本身**(其他地方一個像素都不動),還是**照它生一張新的**?」
    
    ⚠️ **編輯模式沒有底圖就不成立。** 沒有本機實體檔 → 照紅線 2 問路徑,給不出就結束,不要退化成「生一張像的」。
    
    **不阻塞條款(user 不在場時)**:模式判斷、Step 1a、Step 1b 都是**資訊不足**型閘門,
    user 不在(背景 / 無人值守 / 被別的 skill 呼叫)時,**照最合理的解讀往下走**,
    並在交付訊息裡明標「假設:`MODE=<x>`,未經確認」。
    
    🔴 **但拍板閘(Step 2a)沒有不阻塞版本** —— 那是授權閘,花的是 user 的錢,沒有 fallback。
    被別的 skill 當子流程呼叫時,**批次授權要在上層取得**(上層對整批拿一次拍板),
    不是在這裡放行;本層仍然不得在沒有任何授權的情況下啟動 executor。
    
    ### Step 1-1: 判斷觸發語境(mid-conversation vs 新對話)
    
    **生成模式**讀 trigger 那輪訊息 + 最近 5-10 輪 context,落到下表(**編輯模式跳過本表,直接去 Step 1b**):
    
    | 情況 | 動作 |
    |------|------|
    | Mid-conversation 且 context 含 ≥3 錨點(**場景 + 主體 + 動作**) | 跳 Step 2,直接展 prompt |
    | Mid-conversation 但 context 不足(缺任一錨點) | 跳 Step 1a,互動補問 |
    | 新對話 / 純 trigger 沒帶任何描述 | 跳 Step 1a,互動問清楚 |
    
    **錨點判斷標準**:
    - **場景**:哪裡 / 什麼背景(街道、室內、特定地標、純色底...)
    - **主體**:誰 / 什麼物件、外型描述
    - **動作**:在做什麼 / 姿態 / 表情 / 互動
    
    不確定時就降級到 Step 1a 問清楚 — **禁止靠想像力填空**。
    
    ### Step 1a: 互動補問(只在缺錨點時)
    
    問題收斂在缺的那幾項,每次最多 3 個問題、numbered list、口語:
    
    ```
    要先確認幾件事再展 prompt:
    1. 場景 / 背景:__
    2. 主體:誰 / 什麼,幾個,長相 / 外型描述:__
    3. 動作 / 氛圍:__
    4. 風格傾向(可選;不講就交給我推):__
    ```
    
    **不要問**:尺寸 / aspect ratio / 解析度(除非 user 主動提);技術參數(model / steps / cfg);codex 怎麼跑(這 skill 自己處理)。
    
    ### Step 1b: 編輯模式要先釘死的五件事
    
    編輯模式**不問場景/主體/動作**(那是生成模式的錨點),改問這五件:
    
    | 要釘的 | 為什麼 | 不釘會怎樣 |
    |---|---|---|
    | **① 底圖的本機絕對路徑** | Claude Code 的 codex `-i` 與後續像素驗收都需要它 | 沒有就無法完成現行驗收,別退化成「生一張像的」 |
    | **② 改哪一處,精確到可驗證** | prompt 要寫成 `CHANGE EXACTLY ONE THING` | 寫「修一下」→ 模型自由發揮 → 整張重畫 |
    | **③ 其餘一切都不准動** | 這句是編輯模式的核心,**不是廢話** | 不寫,模型會把它當 img2img,重新生成一張「很像的」 |
    | **④ 這是去背還是局部修改**(`EDIT_KIND=bgremove\|local`) | 驗收的第 ② 項只有 `bgremove` 會跑 | 漏設 → 假去背(背景被畫成白色)**驗收會放行** |
    | **⑤ 底圖尺寸 W×H** | Step 2-edit 的畫布鎖定要填實際數字 | 填佔位符 → 模型自己挑畫布 → 主體位置全跑掉 |
    
    ④⑤ **要在拍板之前拿到**,因為它們要寫進給 user 過目的那份 prompt。
    量尺寸只是讀一個檔的中繼資料,不花錢也不啟動 codex,**不屬於「pre-flight 在拍板之後才做」那條規矩管的範圍**:
    
    ```bash
    REF="<user 給的底圖絕對路徑>"
    EDIT_KIND=<bgremove|local>
    eval "$("$SKILL_DIR/scripts/preflight_edit.sh" "$REF")"   # 給出 SRC_W / SRC_H,缺 Pillow 會直接中止
    ```
    
    ②③ 這對是編輯模式成立的關鍵:**模型預設的行為是「重新生成」不是「就地修改」**,要它就地改必須明說。
    
    ---
    
    ## Step 2: 展 bilingual prompt 給 user 過
    
    格式固定,**中文在前英文在後**(user review 中文,英文是實際送 image executor 的 payload):
    
    ```markdown
    ## 中文 prompt
    (口語描述,user 看得順、能直接指出哪裡要改的顆粒度。
     包含:場景 / 主體 / 動作 / 風格 / 光線 / 構圖 等該講的都講。)
    
    ## English prompt(送 image_gen 用)
    (影像模型吃的高密度英文 prompt。
     結構建議:SETTING / SUBJECT / ACTION / STYLE / LIGHTING / COMPOSITION / ASPECT RATIO。
     寫法照 OpenAI 官方 prompt guide — 名詞 + 形容詞密集,少動詞,少 narrative。)
    ```
    
    **生成模式展完後就到這裡:停下來等 user 回應**(拍板字眼見 Step 2a)。編輯模式改用下面那套骨架。
    
    ### Step 2-edit: 編輯模式的 prompt 骨架(三段,缺一不可)
    
    編輯模式**不用**上面那個 SETTING / SUBJECT / ACTION 結構 —— 那是在描述「要生什麼」,
    而編輯要描述的是「**保留什麼、只改什麼**」。骨架固定三段:
    
    ```
    ① 保留清單 —— 越具體越好,把畫面上看得到的東西逐項點名
    Use the attached Image #1 as the BASE. Keep EVERYTHING identical to it:
    <逐項列出:主體、五官、髮型、配件、服裝、姿勢、位置、取景、裁切、
     鏡頭距離、背景、光線、長寬比 …… 凡是不該變的都點名>
    
    ② 唯一的改動 —— 用 EXACTLY ONE THING 句式
    CHANGE EXACTLY ONE THING: <要改的那一項,寫到可驗證>
    
    ③ 明擋清單 + 畫布鎖定 —— 反面詞比正面詞有效
    DO NOT <逐項擋掉最可能被順手改掉的東西>. DO NOT move the subject.
    DO NOT zoom in or out. DO NOT resize.
    Keep the output canvas at exactly <W> x <H> pixels.
    ```
    
    **去背另外加一段**(否則模型會把背景「畫成白色」而不是挖掉):
    
    ```
    Output a PNG with a genuine ALPHA CHANNEL - the area around the subject must be
    actually TRANSPARENT (alpha = 0), not painted white, not painted any solid colour,
    and not a checkerboard pattern drawn as pixels.
    ```
    
    ⚠️ 中文那半照樣要寫(user 是看中文 review 的),但**中文段要把「保留清單」逐項寫出來**,
    不要濃縮成「其他都不要動」—— user 要能一眼看出你有沒有漏點名某個東西。
    
    展完後**停下來等 user 回應**。
    
    ### Step 2a: User 回應分支
    
    | User 回應 | 動作 |
    |-----------|------|
    | `OK` / `生` / `go` / `下去`(明確拍板字眼) | 進 Step 3 |
    | 任何修改指令(「改成 X」「加 Y」「拿掉 Z」「換風格」) | 重生 prompt 雙段 → 回 Step 2 開頭重展 |
    | `算了` / `不要了` / `取消` | 結束,不啟動任何 executor |
    | 其他模糊正向回應(「不錯」「可以喔」「OK 吧」**含猶豫感**) | 視為「還沒拍板」,回問一句:「這版就生?確認的話回 `OK` 或 `生`」 |
    
    **MANDATORY**:拍板字眼是 hard gate,不准用語意推測代替。
    
    ---
    
    ## Step 3: 執行前檢查(pre-flight)
    
    **Step 3a: Reference image 偵測**
    
    掃這輪 trigger + 等待拍板期間 user 是否有附過任何 image:
    
    ```
    MODE=edit(Step 1-0 判定):
      • REF 已在 Step 1b ① 取得(編輯模式在拍板前就必須有底圖,否則寫不出保留清單與做不了驗收)→ 直接進 Step 3b。
      • 沒有 REF → 編輯模式不成立。照紅線 2 問路徑;給不出就如實告訴 user 做不到,
        🔴 不要退化成 MODE=generate「生一張像的」交差。
    
    MODE=generate:
      RUNTIME=codex:
        • 本機有實體檔 → 記 REF=該絕對路徑;Step 4-Codex 用 referenced_image_paths。
        • 只有對話內嵌圖 → Step 4-Codex 用 num_last_images_to_include;不要為了 CLI 再問一次路徑。
        • 無附圖 → 不傳 reference 參數,走 text2img。
    
      RUNTIME=claude_code:
        • 本機有實體檔(user 給 path / 拖曳實體檔)→ 記 REF=該絕對路徑,走 img2img(Step 4-Claude 帶 -i "$REF")。
        • 只貼在對話裡、本機無實體檔 → 問 user 要本機路徑(codex -i 吃 file path、不吃對話內嵌圖)。給了 → img2img;給不出 → 印「拍板的英文 prompt」給 user 自己貼 ChatGPT GUI,結束。
        • 無附圖 → REF 留空,走 text2img。
    
      然後進 Step 3b。
    ```
    
    ⚠️ **`REF` 是全篇唯一的底圖變數名**(Step 1b 的「底圖絕對路徑」= `REF`,Step 5b 驗收的來源也是它)。
    不要在不同 step 給同一張圖取不同名字。
    
    **Step 3a-edit: 編輯模式專屬 pre-flight(MANDATORY)**
    
    相依檢查與量尺寸**已經在 Step 1b 做過**(那時就要拿到 W×H 才寫得出 prompt)。
    這裡只再確認一次底圖還在、`EDIT_KIND` 有給:
    
    ```bash
    eval "$("$SKILL_DIR/scripts/preflight_edit.sh" "$REF")"   # 缺 Pillow 或底圖不見會直接中止
    case "$EDIT_KIND" in
      bgremove|local) ;;
      *) echo "EDIT_KIND 沒指定(bgremove|local)——驗收的透明度檢查會被跳過" >&2; exit 1 ;;
    esac
    ```
    
    ⚠️ **畫布尺寸有一個未收斂的風險**:`verify_edit.py` 第 ① 項的尺寸比對是硬閘門,
    但 image_gen 不保證任意尺寸都吐得出來。實測**非標準比例(如 720×1080)有成功過**,
    但這不是保證。若這道閘門反覆失敗且尺寸只差一點,**那是管線限制不是 prompt 問題** ——
    告訴 user、讓他決定要不要接受「輸出後自己裁回原尺寸」,不要無限重試。
    
    **Step 3b: NSFW context 判斷**
    
    依當下 conversation context 判斷這張圖內容是否會踩到 OpenAI policy:
    
    - **不寫死硬規則** — 看上下文。例如:
      - 一般角色插畫、無裸露 → 應該過
      - 明確的性內容或裸露 → 大概率被 reject
      - context 本身就落在敏感題材 → 提高警覺度
    
    - 判斷會 reject → 警告 + 問:
      ```
      這張描述 codex 大概率會 reject(OpenAI policy)。要硬送看看,還是改走 ChatGPT GUI 或其他工具?
      - 硬送:回「送」
      - 改走別的工具:回「不要送」
      ```
    
    - 判斷 OK → 直接進 Step 4
    
    **不替 user 做安全決策** — 只警告 + 給選項。
    
    ---
    
    ## Step 4: 依 `RUNTIME` 執行
    
    ### Step 4-Codex: 直接呼叫 built-in `image_gen`
    
    `RUNTIME=codex` 時,user 一拍板就由**目前這個 Codex session**直接呼叫 built-in `image_gen`。
    這不是「請 Codex 幫我叫 Codex」,所以不要執行 `codex` binary、不要開背景 CLI task、不要用
    `OPENAI_API_KEY`,也不要把 prompt 改寫成叫下一層 agent 做事的 instruction。
    
    把 Step 2 拍板的 English prompt 直接放進 native tool 的 `prompt`。Reference 參數只選一種:
    
    | 情況 | built-in `image_gen` 參數 |
    |---|---|
    | text2img,沒有任何底圖 | 省略 `referenced_image_paths` 與 `num_last_images_to_include` |
    | 所有底圖都有本機路徑 | `referenced_image_paths: [REF, ...]` |
    | 底圖只存在最近對話 | `num_last_images_to_include: N`,`N` 取涵蓋所有目標圖的最小值 |
    
    **絕不並傳** `referenced_image_paths` 與 `num_last_images_to_include`。若兩種來源混在一起而一個參數
    無法涵蓋全部目標圖,請 user 重新附上缺的圖,不要漏圖硬生。
    
    - `MODE=generate` + reference:才在 prompt 開頭加身份鎖:「請參考附上的 Image #1 作為人物身份參考(同一個人,保持臉部特徵、髮型、體型一致)。」它仍是 img2img 生成。
    - `MODE=edit`:若本機底圖尚未在對話中看過,先用 Codex 的 `view_image` 檢視,再使用 Step 2-edit 拍板的三段 prompt;包裝意圖是「編輯這張圖」,禁止加身份鎖。
    - image generation tool 本身需要幾分鐘時,照目前 Codex harness 的原生等待機制等它完成;**不因此另開 `codex exec`**。
    - tool 完成後,用 Codex 的 generated-image renderer 直接把結果交回對話(在支援該介面的 harness,等同 `generatedImage(result)`)。
    - 若 native result 同時提供本機 artifact path / output hint,依既有慣例搬到 cwd(git repo 走 `generated_images/`),不要覆寫既有檔。生成模式沿用 Step 5c-1 的格式判準;編輯保留 png 並跑 Step 5b 驗收。兩者都寫 sidecar:executor 記成 `codex-native-image_gen`,model 未揭露就填 `null`,不要猜,也不要跑 Step 5d 的 CLI log grep。
    - 若 native result 只有可直接交付的 image artifact、沒有可讀的本機路徑,仍直接交付,並在訊息附上拍板版 prompt。**不得只為了湊 legacy 收圖/sidecar 流程而退回 Codex CLI。** 此時不要假裝做過檔案層 pixel verification。
    - tool 失敗時回報其錯誤類型,不自動重試;built-in tool 不可用時也不得靜默改走 CLI。
    
    `RUNTIME=codex` 的 executor 到此結束;有本機 artifact 時只重用 Step 5b 的驗收與 Step 5c 的格式判準,
    不要照跑其中依賴 `$STATE` / marker 的 legacy shell。下面 Step 4-Claude、Step 5a、Step 5c-3、
    Step 5d 的 log grep、Step 5e 範本與 Step 6 都是 Claude Code CLI 路徑。
    
    ### Step 4-Claude: Claude Code 背景啟動 Codex CLI
    
    以下流程只在 `RUNTIME=claude_code` 執行,行為維持原樣。
    
    #### Step 4-Claude-a: 組路徑與檔名
    
    ```bash
    TS=$(date +%Y%m%d_%H%M%S)
    START_MARKER="/tmp/codex_imagegen_${TS}.marker"   # 只當 fallback 錨點(主路是 prompt-save,見 Step 4-Claude-b/5a)
    
    # slug:從中文 prompt 抽 1-3 個關鍵詞,連字號連接,去掉空白與標點
    # 範例:「一隻棕熊在雪山頂看日出」→ "brown-bear-summit-sunrise"
    SLUG="<由你從中文 prompt 抽出>"
    
    # 輸出夾:cwd 是 git repo → ./generated_images/ 子夾(避免雜進 git 根);否則 cwd 根
    # 🔴 這個路徑等下要叫 codex 自己寫進去 → 必須落在 sandbox 可寫範圍(cwd 內 or /tmp/$TMPDIR)
    if git -C "$PWD" rev-parse --git-dir >/dev/null 2>&1; then
      OUT_DIR="$PWD/generated_images"
    else
      OUT_DIR="$PWD"
    fi
    mkdir -p "$OUT_DIR"
    
    OUT_PNG="$OUT_DIR/${TS}_${SLUG}.png"      # 🟢 主路:叫 codex 直接存這(prompt-save,見 Step 4-Claude-b)
    OUT_JPG="$OUT_DIR/${TS}_${SLUG}.jpg"      # 生成模式的最終交付(jpg q85)
    OUT_SIDECAR="$OUT_DIR/${TS}_${SLUG}.prompt.md"
    LAST_MSG="/tmp/codex_imagegen_${TS}.lastmsg"
    LOG_FILE="/tmp/codex_imagegen_${TS}.log"
    
    # 模式與底圖(Step 1-0 / Step 1b / Step 3a 已經決定,這裡只是落成變數)
    MODE=<generate|edit>          # 🔴 佔位符,照抄會讓編輯模式落進 5c-1 轉 jpg 刪 png
    EDIT_KIND=<bgremove|local>    # 只在 MODE=edit 時有意義;不要留預設值
    REF="<底圖絕對路徑>"           # text2img 留空,img2img 與 edit 必填
    
    touch "$START_MARKER"   # fallback 用:萬一 codex 沒照存,Step 5a 退而用 find -newer 撈
    
    # 🔴 落一份 state 檔:每次 Bash 工具呼叫都是「全新的 shell」,上面這些變數活不過這一格。
    #    Step 5 在背景等待之後才跑,屆時一律先 source 回來,不要憑記憶重打路徑。
    STATE="/tmp/codex_imagegen_${TS}.state"
    # 🔴 值一律 %q 跳脫。不跳脫的話,路徑帶空白(macOS 截圖檔名預設就帶)在 source
    #    回來時會被拆成「賦值 + 執行命令」,變數靜默變成空字串、整段還回報成功。
    { printf 'TS=%q\n'           "$TS"
      printf 'MODE=%q\n'         "$MODE"
      printf 'EDIT_KIND=%q\n'    "$EDIT_KIND"
      printf 'REF=%q\n'          "$REF"
      printf 'OUT_PNG=%q\n'      "$OUT_PNG"
      printf 'OUT_JPG=%q\n'      "$OUT_JPG"
      printf 'OUT_SIDECAR=%q\n'  "$OUT_SIDECAR"
      printf 'LAST_MSG=%q\n'     "$LAST_MSG"
      printf 'LOG_FILE=%q\n'     "$LOG_FILE"
      printf 'START_MARKER=%q\n' "$START_MARKER"
      printf 'STATE=%q\n'        "$STATE"
    } > "$STATE"
    ```
    
    進 Step 5 的每一格 bash 開頭都先:
    
    ```bash
    # 🔴 不能寫 . "/tmp/codex_imagegen_${TS}.state" —— TS 正是還沒撈回來的變數之一。
    STATE=$(ls -t /tmp/codex_imagegen_*.state 2>/dev/null | head -1)
    [ -n "$STATE" ] || { echo "找不到 state 檔"; exit 1; }
    . "$STATE"
    ```
    
    ⚠️ **多條並行跑 codex 時 `ls -t` 會撈到別人的 state**(並行做法見 Step 4-Claude-b 的 flag 註解)。
    並行情境要把 `TS` 明寫進指令,不能靠 `ls -t`。殘留的舊 state 檔同理危險 —— 見 Step 6 的清理。
    
    #### Step 4-Claude-b: 背景啟動 codex exec
    
    用 `Bash` 工具,`run_in_background: true`。**主路 = prompt-save**:在 prompt 裡直接叫 codex 用內建 image_gen、存到 `$OUT_PNG`、回報實際路徑(跨版本最穩,見下方 0.141.0 註):
    
    **生成模式**(text2img:`REF` 留空;img2img:`REF` 有值時自動帶 `-i`):
    
    ```bash
    codex exec --skip-git-repo-check \
      "用內建 image_gen 工具生圖,不要使用 scripts/image_gen.py,也不要使用 OPENAI_API_KEY。<英文 prompt 內容>。請把最終圖片存到 ${OUT_PNG},完成後回報實際存檔的絕對路徑。" \
      ${REF:+-i "$REF"} \
      --sandbox workspace-write \
      --output-last-message "$LAST_MSG" \
      < /dev/null > "$LOG_FILE" 2>&1
    ```
    
    **編輯模式**(`REF` 必有值;注意**包裝動詞不同**):
    
    ```bash
    codex exec --skip-git-repo-check \
      "用內建 image_gen 工具編輯附上的圖片,不要使用 scripts/image_gen.py,也不要使用 OPENAI_API_KEY。<英文 prompt 內容(Step 2-edit 的三段骨架)>。請把最終圖片存到 ${OUT_PNG},完成後回報實際存檔的絕對路徑。" \
      -i "$REF" \
      --sandbox workspace-write \
      --output-last-message "$LAST_MSG" \
      < /dev/null > "$LOG_FILE" 2>&1
    ```
    
    > - 🔴 **包裝動詞必須跟著模式換**。生成用「生圖」、編輯用「**編輯附上的圖片**」。
    >   編輯模式若沿用「生圖」,這個動詞會把 Step 2-edit 辛苦建立的
    >   `CHANGE EXACTLY ONE THING` 稀釋掉,模型會回去重新生成。
    > - **img2img 時**(**只有 img2img**),prompt 開頭再加一句身份鎖:
    >   「請參考附上的 Image #1 作為人物身份參考(同一個人,保持臉部特徵、髮型、體型一致)。」
    > - 🔴 **編輯模式禁止加身份鎖。** 「保持一致」=「**重新生成一張像的**」,
    >   跟編輯模式的「一個像素都不要動」正面衝突 —— 那正是 Step 5b 驗收要抓的失敗態。
    > - `${REF:+-i "$REF"}` 只在 REF 有值時展開成 `-i "$REF"`;`< /dev/null` 防 codex 誤讀 stdin。
    
    **Flag 註解**(codex-cli 0.141.0 實測對齊;新版本前先 `codex exec --help` 確認):
    
    - **`--skip-git-repo-check`**:讓 `codex exec` 在**非 git repo 的 cwd** 也能跑(不加會在非 repo 目錄報錯拒跑)。在 repo 內可省、但加著無害,當常駐。
    - **`--sandbox workspace-write`**:允許 codex 寫進 workspace(cwd + `/tmp` + `$TMPDIR`)—— prompt-save 的 `$OUT_PNG` 必須落在這範圍,否則寫檔被靜默擋。
    - **`codex exec` 沒有 `--ask-for-approval`** — 那 flag 只在 top-level `codex`,exec 預設就是 non-interactive never-ask,不需另指定。
    - **`--full-auto` 已 deprecated**(0.128.0 起),等同 `--sandbox workspace-write`。**不要用**。
    - **`-o, --output-last-message`**:把 codex 最後 assistant message 寫進指定檔。**prompt-save 法下這檔會帶實際絕對路徑**(因為你在 prompt 叫它回報)→ Step 5a 可拿來交叉驗證。
    - **`-i, --image <FILE>`**:img2img 用 — 有底圖時帶 `-i "$REF"`(鎖臉/角色一致,已實測可行)。⚠️ **`-i` 是 variadic `<FILE>...`**:prompt 必須當**第一個 positional 放最前**、`-i` 擺後面,否則 prompt 會被吃成第二張圖 → codex 沒 positional prompt → 轉讀 stdin → 失敗。(Codex 官方範例把 `-i` 放 prompt 前,**別照抄**、會踩這雷。)
    - **沒有 output-dir flag**:控制輸出位置只有兩根槓桿 ——「prompt 內明寫存檔路徑」(主路)+ `-C <workdir>` / `--add-dir`。
    - **批次/迴圈跑 codex 必加 `< /dev/null`**:在 `while read … done < file` 內跑 codex 會繼承迴圈 stdin(= prompt 檔)→ 一個 session 狂生多圖 + 吃掉 read fd。`< /dev/null` 切斷即解。多條並行各自獨立 `CODEX_HOME`(cp auth.json + config.toml)避免搶圖;🔴 `CODEX_HOME` 必須落 sandbox 可寫路徑(`/tmp/codex_stream_X`),別指到 cwd 外。
    
    **Prompt 字串注意**:
    - 用**自然語指示**叫 codex 走內建 image_gen(如上「用內建 image_gen 工具…」),不靠 `$imagegen` token —— 自然語更穩,也免去 `$` 被 shell 展開的坑。若硬要用 `$imagegen` token,bash 字串內要 escape 成 `\$imagegen`。
    - prompt 用 double-quote 包,內含的 `"` / `` ` `` / `$` 全部 escape。
    - 「不要使用 scripts/image_gen.py / OPENAI_API_KEY」是**防呆**:若 cwd repo 內有同名生圖 script,codex 可能誤抓 → 明擋。
    - 不要加 `--json`(log 變 JSONL,反而難用 grep 監看)。
    
    > ⚠️ **0.134.0 版差異(踩過、直接影響 Step 5 收圖)**:codex 改用 `gpt-5.5` orchestrator + 內建 `image_gen` flow,不再是 gpt-image-2,連帶兩個 output 形狀變了:
    > 1. `--output-last-message` **不再吐圖片路徑**(只寫一句「Generated the image...」)→ 別再 grep LAST_MSG 抓路徑。
    > 2. 圖落在**巢狀** `~/.codex/generated_images/<session-id>/ig_*.png`,不是平鋪。
    > 3. 固定輸出 **png**(無法指定格式)→ 交付前自行轉 jpg。
    
    > ⚠️ **0.136.0 版差異(PR #24972「native image artifact completion pipeline」重寫出圖管線、實測對齊)**:
    > 1. 圖**仍然**落 `~/.codex/generated_images/<session-id>/ig_*.png`(0.136.0 實測確認、Step 5a 的 `find -newer marker` 照舊有效)—— 別誤信「0.136 不再寫 generated_images」這類推論,**自己 `find` 一下就知道**。
    > 2. **同時**圖會以 base64 嵌進 session rollout JSONL(`~/.codex/sessions/<date>/rollout-*.jsonl` 的 `image_generation_call` / `image_generation_end` 的 `result` 欄)。萬一哪天 `generated_images` 撈空,這是**最後手段** fallback(解 base64 還原 png),但屬未文件化、隨版本可能再變、別當主路。
    > 3. **更穩的官方文件作法(建議長期改用、跨版本不靠猜目錄)**:prompt 末尾明寫 `Save the final image as <name>.png in the current directory.` + 跑 `codex exec -C <輸出夾> --enable image_generation --sandbox workspace-write …`,讓圖直接落你指定的 cwd。**沒有 output-dir flag**,`-C` / `--add-dir` + prompt 指示是唯一控制輸出位置的槓桿(0.136 的「local image attachments expose file paths to model」#25944 就是為了讓這條 save-path 流更可靠)。
    
    > ⚠️ **0.141.0 版差異(實測 2026-06-24,本 skill v0.4.0 改版主因)**:
    > 1. `generated_images` **時有時無** —— 同一版本、同樣指令,有時圖落 `~/.codex/generated_images/<session>/ig_*.png`、有時**完全不落**(圖只剩 rollout JSONL 的 base64)。所以 `find -newer marker` 撈 generated_images 這條**主路不再可靠**(實測整批撈空、得退 base64 還原才救回)。
    > 2. **→ 收圖主路正式改為「prompt-save」**(上面 0.136 早記過的官方作法,現升為預設):launch 時在 prompt 內叫 codex 存到 `$OUT_PNG`、回報路徑(見 Step 4-Claude-b / 5a)。實測 0.141.0 圖**確實直接落指定路徑**、`--output-last-message` 也回報了絕對路徑。
    > 3. generated_images `find` 與 rollout base64 解碼**降為 fallback 1 / 2**。注意 0.141.0 有時 prompt-save 與 generated_images **兩邊都寫** → Step 5c-3 會清掉 generated_images 的多餘 copy。
    
    #### Step 4-Claude-c: 非阻塞等待(讓出主線程,靠 task 完成通知喚回)
    
    codex 這條 image_gen flow **每張要跑 2-3 分鐘**(先跑 reasoning 再生圖)。Step 4-Claude-b 既然 `run_in_background: true`,就**讓出控制權給 user、這一輪收尾**,別在前景 `sleep N; tail` 輪詢 —— 那會卡死主線程、user 不能講話(實戰踩過、user 抱怨「太久了 / 是不是當機」)。
    
    正確姿態:
    
    1. 啟動背景 codex 後,給 user **一行** heartbeat(見下),然後**這輪就結束**、把控制權還 user。
    2. 背景指令跑完,harness 會丟 `<task-notification>`(含 task-id + output 檔路徑)自動把你喚回 —— **這就是「monitor」,由 task 系統盯,不是你前景 block**。
    3. 被喚回 → 讀 `$LOG_FILE`(或 task output 檔)判斷成敗 → 進 Step 5(成功)或 Step 6(失敗)。
    
    heartbeat(一行、不刷屏):
    ```
    Codex 跑起來了,背景生圖中(這條 flow 一般 2-3 分鐘),跑完通知你,先忙別的沒問題。
    ```
    
    > ⚠️ **沒有獨立的 `Monitor` 工具** —— 「監督」= 背景 task + 完成通知。**禁用 `sleep N; tail` 前景輪詢**(阻塞主線程、卡死 user 對話)。真要中途偷看進度,用 `Read` 點一下 task output 檔就好,**別 sleep-loop**。
    
    ---
    
    ## Step 5: 本機 artifact 後處理(Claude Code 全流程;Codex 只重用已標示規則)
    
    ### Step 5a: Claude Code 收圖(prompt-save 主路 + 兩層 fallback)
    
    ```bash
    OUT_PNG=$("$SKILL_DIR/scripts/collect.sh" "$OUT_PNG" "$START_MARKER") || {
      echo "三層都拿不到圖 → 跳 Step 6 判失敗類型"; exit 1
    }
    ```
    
    腳本依序試三層,命中哪一層會印在 stderr:
    
    | 層 | 做法 | 為什麼不是主路 |
    |---|---|---|
    | 🟢 主路 | 檔案已在 `$OUT_PNG`(launch 時就在 prompt 裡叫 codex 存過去) | — |
    | fallback 1 | 撈 `~/.codex/generated_images`,`-newer` marker | 0.141.0 起**時有時無**,同版本同指令有時整批不落 |
    | fallback 2 | 從 session rollout JSONL 解 base64 還原 | 未文件化、隨版本可能再變。**那是救援不是備份** |
    
    **腳本裡的幾個寫法是踩出來的,要改它之前先讀這幾條**(平常不必看):
    
    - 🔴 **絕不用 `-newermt`(任何形式)**:macOS BSD find 對 `-newermt` 的 `@epoch` **與**相對時間都 **silently 假陰性**(誤判「沒 PNG」其實圖都在)。一律 **`-newer <實體 marker 檔>`**(BSD/GNU 皆穩)。
    - 🔴 fallback 1 **在 `~/.codex/generated_images` 找,別在 cwd / repo 內 `find .`**(主路已直接落 cwd 的 `$OUT_PNG`,find 是給「codex 沒照存」的退路)。
    - **用 `find` 不用 glob**:巢狀目錄要遞迴,空 glob 在 zsh 會 `no matches found` 中止。
    - session id 走 grep log 的 `session id:` **不可靠**(ANSI 色碼夾在中間、regex 易撲空)→ fallback 2 改用「當天 rollout 抓含 PNG magic 的最新檔」。
    
    **三層都拿不到 → 腳本回 exit 1**,照上面那個 `||` 分支跳 Step 6 判失敗類型。
    
    ### Step 5b: 本機編輯 artifact 驗收(**兩個 runtime 共用;生成模式跳過**)
    
    🔴 **驗收一定排在交付之前。** 交付會轉檔/刪檔,驗收需要原始的 `$OUT_PNG`,順序顛倒就沒得驗了。
    
    **「看起來沒變」不算驗過。** 模型有可能交回一張「重新生成的、看起來很像的」圖 ——
    肉眼在表情/姿態沒動的情況下分辨不出幾十像素的位移,但那會讓這張圖與同批其他圖對不齊。
    
    ```bash
    # 邏輯住在腳本裡,不要在這裡重打一份 —— 兩份會漂移,而漂移的那一份會靜默放行。
    "$SKILL_DIR/scripts/verify_edit.py" "$REF" "$OUT_PNG" "$EDIT_KIND"
    ```
    
    `$SKILL_DIR` = 這個 skill 目錄(`gpt-image-gen/`)。腳本的完整判準與退出碼寫在它自己的
    docstring 裡(`verify_edit.py --help` 等同直接讀檔頭),這裡只講它在驗什麼:
    
    | 項 | 驗什麼 | 兩種 kind 的差別 |
    |---|---|---|
    | ① 畫布尺寸 | 輸出與底圖同尺寸 | 相同 |
    | ② 透明度 | 真的有 alpha/透明佔比合理/**主體不是半透明的鬼影** | **只有 `bgremove` 驗** |
    | ③ 主體保真 | 主體像素有沒有被動到 | `bgremove` 要求逐像素不變;`local` 只擋「滿版都在變=重生」,並印出改動區域的座標框 |
    
    🔴 **`EDIT_KIND` 必須明確給 `bgremove` 或 `local`,腳本不接受其他值也不預設。**
    預設會讓「忘了說這是去背」靜默跳過整個 ② —— 而背景被畫成白色的假去背,
    在 ③ 看起來是完美的「最大色差 0」。
    
    🔴 **`local` 的判準跟 `bgremove` 不一樣,不要互相套用。** 局部修改被要求改的那一塊
    **本來就會有極大色差**,拿「色差要小」去卡它,等於懲罰它有照做,而那道閘門會因此
    被學會忽略(本檔 anti-patterns 有這條)。腳本改成看「改動有沒有聚成一塊」,
    並把座標框印出來讓你跟 prompt 對照。
    
    **`VERDICT: FAIL` → 照 Step 6 的「編輯驗收未過」那列處理,不要自動重試、不要清檔。**
    
    ⚠️ **③ 的兩個假設要講清楚**:無 alpha 時退回明度判準,那條假設的**不只是「有對比」,是「背景比主體亮」**
    (`>=` 是單向比較)。暗背景亮主體會讓取樣歸零,程式會明確報出來而不是靜默通過。
    有 alpha 時一律走 alpha 遮罩,沒有這個問題。
    
    ### Step 5c: 交付
    
    🔴 **先看模式再往下**:
    
    | MODE | 走哪 |
    |---|---|
    | `generate` | 5c-1 轉 jpg |
    | `edit` | **5c-2,禁止執行 5c-1 的 bash** |
    
    #### Step 5c-1: 生成模式 — 轉 jpg 交付(q85)
    
    codex 吐 png(2MB 級);交付走 **jpg q85**(實測畫質肉眼無感、體積約 png 的 1/5):
    
    ```bash
    [ "$MODE" = "edit" ] && { echo "編輯模式,跳過本段,走 5c-2"; exit 0; }
    sips -s format jpeg -s formatOptions 85 "$OUT_PNG" --out "$OUT_JPG" >/dev/null 2>&1
    rm -f "$OUT_PNG"                            # 刪 png 中繼,只留 jpg
    FINAL="$OUT_JPG"
    printf 'FINAL=%q\n' "$FINAL" >> "$STATE"   # Step 5d/5e 是不同的 shell,不回寫就讀到空字串
    ```
    
    - 最終交付 = `$FINAL`。**只有 user 明講「要留無損 png」才跳過 `rm -f "$OUT_PNG"`**。
    - ⚠️ **例外**:這張圖若會**進版控**、**還要再加工**(去背/裁切/合成)、或**要當之後好幾張的 `-i`**,
      就留 png、別刪。q85 的損失本身肉眼無感,但拿它當整條產線的起點就是讓每一步都從有損的地方長出來。
      判準:**只是拿來看的 → 照刪;會被再利用 → 留 png。**
    
    #### Step 5c-2: 編輯模式 — 只交 png
    
    ```bash
    FINAL="$OUT_PNG"        # 不轉檔、不刪檔,就這樣
    printf 'FINAL=%q\n' "$FINAL" >> "$STATE"   # Step 5d/5e 是不同的 shell,不回寫就讀到空字串
    ```
    
    **編輯模式一律交 png,無例外。** 不分有沒有 alpha,理由同上一條的「會被再利用」判準 ——
    編輯結果十之八九還要再加工或進版控,而且**編輯結果不可重現**(同 prompt 同 ref 再跑不會是同一張)。
    不去猜它有沒有 alpha,就不會有猜錯的機會。
    
    #### Step 5c-3: 清 `generated_images` 的多餘 copy(**兩種模式都要做**)
    
    ```bash
    # 0.141.0 有時 prompt-save 與 generated_images 兩邊都寫 → 清掉 codex 那份多餘 copy(避免堆積)
    STRAY=$(find ~/.codex/generated_images -type f -iname '*.png' -newer "$START_MARKER" 2>/dev/null | head -1)
    [ -n "$STRAY" ] && rm -f "$STRAY" && rmdir "$(dirname "$STRAY")" 2>/dev/null
    ```
    
    **絕不把圖留在 `~/.codex/generated_images/`**(堆積 + user 找不到)—— 主路雖然落 cwd,codex 仍可能另存一份在那,務必清。
    
    ### Step 5d: 寫 sidecar
    
    先從 log 抓實際 model(別寫死 — 0.134 是 gpt-5.5 不是 gpt-image-2):
    
    ```bash
    MODEL=$(grep -aoE 'gpt-[0-9.]+' "$LOG_FILE" | head -1)
    ```
    
    格式固定:
    
    ```yaml
    ---
    timestamp: <ISO8601,帶時區偏移>
    trigger: "<user 觸發那句原文>"
    mode: <generate | edit>
    edit_kind: <bgremove | local;MODE=generate 則 null>
    reference_image: <$REF 絕對路徑;text2img 則 null>
    codex_model: <$MODEL,如 gpt-5.5> (codex built-in image_gen flow)
    codex_exit: success
    verify: <MODE=edit 才有:Step 5b 的 VERDICT 與那行量測數字;生成模式 null>
    output_image: <$FINAL 絕對路徑>
    ---
    
    # 中文 prompt
    
    <拍板版本的中文 prompt>
    
    # English prompt
    
    <拍板版本的英文 prompt(實際送 codex 的)>
    ```
    
    - 🔴 `output_image` 一律填 **`$FINAL`**(生成=`$OUT_JPG`、編輯=`$OUT_PNG`)。
      寫死 `.jpg` 會讓編輯模式的 sidecar 指向一個**從未存在過的檔**,
      而 sidecar 是 prompt 的唯一持久記錄(見 Important rules)。
    - 檔名慣例:sidecar 與圖**同 basename**、副檔名換成 `.prompt.md`。編輯模式沿用 `${TS}_${SLUG}` 這組,
      `SLUG` 改從「改了什麼」抽(例:`bg-removed`、`cup-removed`),不要沿用底圖檔名(會跟底圖的 sidecar 撞名)。
    
    寫進 `$OUT_SIDECAR`。**bg session 內若 `Write` 被 bg-isolation guard 擋(這 skill 常在 bg + git repo 跑),改用 Bash heredoc 寫**(`cat > "$OUT_SIDECAR" <<'EOF' ... EOF`)。
    
    ### Step 5e: 通知 user(不自動開圖)
    
    生成模式:
    
    ```
    ✅ 生好了
    - 圖:<$FINAL 的相對 cwd 路徑>
    - prompt log:<相對 cwd 路徑>.prompt.md
    ```
    
    編輯模式(**要把驗收數字一起講出來**,那是「這真的是編輯不是重生」的唯一證據):
    
    ```
    ✅ 改好了
    - 圖:<$FINAL 的相對 cwd 路徑>(png;編輯模式一律 png,不轉檔)
    - 驗收:尺寸 <W>x<H> 未變/主體取樣 <N>px 最大色差 <D>
    - prompt log:<相對 cwd 路徑>.prompt.md
    ```
    
    **不要自動 `open`** — user 偏好「搬好通知即可、自己決定要不要看」。
    
    ---
    
    ## Step 6: Claude Code CLI 失敗處理
    
    依 log 內容分類:
    
    | 失敗類型 | log 特徵 | 對應動作 |
    |----------|----------|----------|
    | Safety reject | `safety` / `policy` / `rejected` / `cannot generate` | 告訴 user「codex 拒了,policy 命中。要不要改 prompt 軟化 / 走別的工具?」 |
    | Rate limit | `rate limit` / `429` / `usage limit` | 告訴 user「Codex 額度滿了。要等 / 改用 ChatGPT GUI 自己生?」 |
    | 其他 error | exit code ≠ 0 + 沒以上字眼 | 印 log 最後 30 行給 user 看,問下一步 |
    | **編輯驗收未過** | **codex exit 0、log 乾淨**,但 Step 5b 回 `VERDICT: FAIL` | 見下方 |
    
    ⚠️ **最後一列跟上面三列的性質不同**:codex 是**成功**的,log 裡什麼線索都沒有,
    所以「印 log 最後 30 行」對它毫無用處 —— 那 30 行跟失敗原因無關。
    
    **編輯驗收未過的處理**:
    
    1. **貼出 Step 5b 的三項量測值**(尺寸、透明度、主體色差),那是唯一有資訊量的東西
    2. **指出最可能的成因**,對照 `VERDICT` 底下那幾行:
       - 主體色差大 → Step 2-edit ① 的**保留清單不夠具體**,或 Step 4-Claude-b 誤加了身份鎖
       - 畫布跑掉 → 沒鎖尺寸,或撞到 Step 3a-edit 講的管線限制
       - 沒有透明度 → 去背那段沒寫,或寫了但沒寫三個 `not`
    3. **問 user 要不要補了再送一次**
    4. 🔴 **這條路徑不要清檔**(不執行 Step 5c-3 的清理)—— user 可能要看那張失敗的圖來判斷
    
    **不自動重試** — 失敗交給 user 決定。這條對「驗收未過」同樣適用:
    它看起來很像「再調一下 prompt 就好」,但那是在沒有 user 判斷的情況下連續燒額度。
    
    清掉中繼檔:
    ```bash
    rm -f "$LAST_MSG" "$LOG_FILE" "$START_MARKER" "$STATE"
    ```
    
    ⚠️ **`$STATE` 一定要清。** 撈它的方式是 `ls -t ... | head -1`,殘留的舊 state 檔
    會在某次沒建成新檔時被安靜地撈到,**把上一輪的圖當成這一輪的結果交出去**。
    成功路徑(Step 5e 通知完)也要做同一件清理。
    
    ---
    
    ## Anti-patterns
    
    - ❌ `RUNTIME=codex` 還去執行 `codex exec`、開 Codex 子程序或委派另一個 agent 生圖 → 目前 session 直接呼叫 built-in `image_gen`
    - ❌ 用模糊正向回應(「不錯」「可以」)當拍板信號,誤啟動生圖 executor
    - ❌ img2img 時把 `-i` 放 prompt 前面(prompt 被當第二張圖 → codex 失敗);prompt 一定當第一 positional、`-i` 擺後
    - ❌ Claude Code 把對話內嵌圖(本機無檔)硬塞 codex `-i`(吃 file path、抓不到)→ 先問本機路徑;Codex native path 則直接用 recent-image 參數
    - ❌ 預設某組固定角色 / 某個固定畫風這種寫死 style — 沒 context 就問
    - ❌ Skill 內部偷偷加 NSFW filter 替 user 做決策(只警告 + 給選項)
    - ❌ 把收圖**主路**放在「翻 `generated_images` / 解 rollout base64」(0.141.0 撈空率高、time-bomb)→ 主路用 prompt-save 叫 codex 存指定 `$OUT_PNG`,find / base64 只當 fallback
    - ❌ 把生圖結果留在 `~/.codex/generated_images/` 不搬走/不清(堆積 + user 找不到);0.141 兩邊都寫時要清掉那份多餘 copy
    - ❌ 圖搬到 cwd 根但 cwd 是 git repo(會雜進 git status / 容易誤 commit)→ git repo 走 `./generated_images/` 子夾
    - ❌ 把 codex 官方範例的 `-i ./input.png 'prompt'`(image 在 prompt 前)照抄 → `-i` variadic 會把 prompt 吃成第二張圖;一律 prompt 第一 positional、`-i` 擺後
    - ❌ **單**靠「自動」`LAST_MSG` 抓路徑(沒在 prompt 叫 codex 回報就不吐)→ prompt-save 主路自己指定 `$OUT_PNG`、讀檔即可,LAST_MSG 只拿來交叉驗證
    - ❌ 用 shell glob(`ls $DIR/*.png`)收 fallback 圖(巢狀目錄漏抓 + 空 glob 在 zsh 中止)→ 改 `find … -newer <marker檔>`
    - ❌ fallback 收圖用 `find -newermt`(任何形式:`@epoch` 或相對 `'-30 minutes'`,macOS BSD find 都 silently 假陰性)→ 改 launch 前 `touch` marker + `find -newer "$MARKER"`
    - ❌ 把單次觀察當鐵則(不論「不寫 generated_images 了」或「一定寫」)→ 0.141 實測**時有時無**;別賭它的行為,prompt-save 主路本就不依賴它
    - ❌ 生完自動 `open` 圖(user 不要)
    - ❌ sidecar 寫死 `codex_model: gpt-image-2`(實際是 log 裡的 model)
    - ❌ 失敗自動重試(codex 失敗通常是 prompt 本身問題或 quota,重試只浪費 token)
    - ❌ 有本機 artifact 卻不寫 sidecar(user 之後翻舊圖找不回原 prompt);Codex native 若只有 inline artifact,至少在交付訊息附拍板 prompt
    - ❌ Claude Code heartbeat 刷屏(user 已經知道在跑了,給一行就好)
    - ❌ Claude Code 背景啟動後用 `sleep N; tail` 前景輪詢等 codex(阻塞主線程、卡死 user 對話)→ 讓出控制權、等 task 完成通知自動喚回
    - ❌ 圖被 keep / 搬走卻把 sidecar 留在原(暫存)目錄 → prompt 隨目錄清掉就永久消失(sidecar 要跟圖走)
    - ❌ 用 `$imagegen` token 卻沒 escape `\$imagegen`(shell 展開成空)→ 現行改用自然語指示「用內建 image_gen 工具」、免此坑
    - ❌ 在 user 還在改 prompt 的迭代過程中提前算 slug / 建目錄 / 啟動 codex(pre-flight 在拍板**之後**才做)
    
    **編輯模式專屬**:
    
    - ❌ 🔴 把編輯結果轉 jpg(帶透明度就永久消失,而編輯結果不可重現)→ 編輯模式一律交 png
    - ❌ 用 `mode in ("RGBA","LA")` 判有沒有透明度 → **漏掉 `mode P` 的透明 PNG**,會判成「沒 alpha」然後一路轉成 jpg。要判就用 `mode in ("RGBA","LA","PA") or "transparency" in im.info`;但更好的作法是**根本不要判**(見上一條)
    - ❌ 驗收拿「四角必須全透明」當去背的判準 → 主體貼齊邊緣的構圖(半身像、滿版)本來就有不透明的角,會**假失敗**;一個 MANDATORY 閘門只要常假失敗,agent 就學會忽略它
    - ❌ 編輯模式沿用生成模式的包裝動詞「生圖」→ 那個動詞會稀釋 `CHANGE EXACTLY ONE THING`
    - ❌ 編輯模式加 img2img 的身份鎖(「保持臉部特徵…一致」)→ 「保持一致」=「重新生成一張像的」,正是驗收要抓的失敗態
    - ❌ 編輯 prompt 只寫「把背景去掉」「把 X 拿掉」而**沒有保留清單** → 模型會重新生成一張「很像的」,不是就地修改
    - ❌ 編輯 prompt 沒鎖畫布尺寸 → 模型吐一個常見比例,主體位置全跑掉
    - ❌ 去背只寫 "remove the background" → 可能得到「背景被畫成白色」或「棋盤格被畫成像素」→ 要明寫 genuine ALPHA CHANNEL + 三個 not
    - ❌ **只看圖覺得「好像沒變」就當編輯成功** → 肉眼分辨不出幾十像素的位移,一定要跑 Step 5b 量
    - ❌ 編輯模式沒底圖時退化成「生一張像的」交差 → 沒有本機實體檔就是不成立,如實講
    - ❌ Step 5b 沒過就自動改 prompt 重送 → 失敗不自動重試,交給 user 決定
    
    ---
    
    ## Important rules
    
    1. **拍板 = 明確 keyword(OK / 生 / go / 下去),不准語意推測** — 違規即破壞 user 信任(=紅線 1)
    2. **先分模式再動手**:生成 vs 編輯。判準=「user 會不會拿輸出去跟原圖逐像素比對」。分不出來就問一句,別猜
    3. **executor 依宿主分流**:Claude Code 才用 Codex CLI;Codex 一律直接呼叫本 session 的 built-in `image_gen`,禁止 nested Codex
    4. **有 reference image → 走 img2img 或編輯** — Claude Code 的 `codex -i` 要本機 path;Codex native 用 local-path 或 recent-image 參數(=紅線 2)
    5. **不寫死預設風格** — 風格 100% 來自當下 context 與 user 描述(=紅線 3)。**NSFW 判斷依 context,警告而非阻擋**,不替 user 做安全決策
    6. **Claude Code CLI 路徑才背景跑**:讓出主線程 + 一行 heartbeat,靠 task 完成通知喚回;禁前景 `sleep N; tail`
    7. **Claude Code CLI 收圖主路 = prompt-save**:launch 時叫 codex 存到 `$OUT_PNG`;`generated_images` / rollout base64 只是 fallback
    8. **本機 artifact 要有 Sidecar `<image>.prompt.md`** — 含中英 prompt + metadata;圖搬走時 sidecar 跟著走。Codex native 若只有 inline artifact,交付訊息至少附拍板 prompt,禁止為 sidecar 退回 CLI
    9. **生成模式交付 jpg q85,png 中繼轉完即刪**(user 明講要無損、或那張圖會被再利用時才留);🔴 **編輯模式一律交 png,不轉 jpg,無例外**(=紅線 4);兩種模式都不自動 `open`
    10. **編輯模式有可讀的本機 source/output 時必跑 Step 5b 驗收**;沒有本機 artifact 時不得宣稱 pixel verification 通過
    11. **失敗不自動重試**(含「驗收未過」)— 貼量測值與可能成因,交給 user 決定
    12. **這 skill 做生成與圖片編輯,但不做 UI 設計、不做 ASCII art** — 走錯領域請 user 改用 Claude Design / 其他工具
    
    ---
    
    ## 已知限制(實測,會隨 codex 版本變)
    
    | 項目 | 現況 |
    |---|---|
    | 去背的遮罩精度 | 實測**兩次**都到**髮絲級**,主體像素**逐位元不變**(最大色差 0)—— 它是遮罩不是重生 |
    | **抗鋸齒不穩定** | ⚠️ **兩次跑出不同結果**:一次是純二值 alpha(半透明階 **0.0%**),一次有邊緣羽化(**0.5%**)。同樣的 prompt 結構、同一個版本 |
    | 補救 | 拿到二值的那種、又要縮放時,自己對 alpha 做一次 1px 模糊即可,**不必重生**(重生會失去像素保真) |
    
    | **局部修改的實際行為** | ⚠️ 實測「拿掉眼鏡」:**要求的改動有做到,但整個主體被連帶重算了一遍**(每處差異細微、身分與姿勢都保住,背景完全沒動)。改動的座標框橫跨畫面八成,密度卻只有 7.5%=散布式重生 |
    | 這樣算不算過 | **由 user 判**。實測那次 user 判定可接受。程式只報數字與座標框,**刻意不硬擋** —— 「換掉整件衣服」跟「散布式重生」在指標上分不開,硬擋會誤殺前者 |
    
    ⚠️ 所以 `local` **不要當成「逐像素就地改」來承諾**。要逐像素不變只有 `bgremove` 那條做得到(實測三次皆最大色差 0)。
    
    ⚠️ **「抗鋸齒」那條就是「別把單次觀察當鐵則」的現成例子** —— 第一次實測後本表曾寫死
    「alpha 是二值的」,第二次就被推翻。要用就自己量,別讀這張表下結論。
    
    🔴 **關於量測本身的一個坑(實際踩過)**:不要在背景任務的完成通知抵達之前去讀輸出檔。
    codex 可能先寫一個中間版本再改寫成最終版,**提早讀會拿到寫到一半的圖**,
    量出來的數字全錯(那次量到「主體 18% 半透明」,最終檔其實是 0.5%)。
    等通知,或至少比對 mtime。
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related