Skill: luweizheng/xhsauto (version 1.0.0)

--- name: xhs-auto description: 小红书自动化发布流程，串联主题输入、图像生成、文案草拟与 debug 发布校验。 metadata: {"openclaw": {"emoji": "🤖", "requires": {"bins": ["bash", "curl", "jq", "base64", "xhs-kit"]}}} --- # 小红书自动化内容发布 用户输入主题和想法，自动化生成小红书文案和配图，调用 `xhs-kit` 发布（测试环境用 `xhs-kit debug-publish`）。 0. 输出内容（文案、配图等）在 `${workspace}/xhs-auto/{timestamp}` 目录下。 1. 用户输入主题和想法。 2. 判断是否有图片，如果有图片则跳过图片生成阶段；如果没有图片，构造生图 Prompt，并调用 `scripts/xhs-image.sh` 生成图片。 3. 调用 `xhs-kit debug-publish` 校验内容合规，输出详细报告。 ## 目录结构 ``` .claude/skills/xhs-auto/ ├── SKILL.md # 本文档 ├── scripts/ │ ├── xhs-image.py # 图像生成脚本（Python，推荐） │ └── xhs-image.sh # 图像生成脚本（Bash，备选） └── references/ ├── text-to-image.md # 图像生成详细指南 └── publish.md # 小红书发布指南 ``` ## 环境准备 ```bash # 安装 xhs-kit pip install xhs-kit # 安装 Playwright 浏览器（用于正式发布时登录） playwright install chromium # 安装图片生成依赖（Python 版本） pip install requests ``` ## 完整流程 ### 步骤1. 确定文案 “帮我写篇小红书笔记”、“帮我发一篇小红书”。了解用户的主题和想法。生成标题和正文。标题小于20字，正文小于950字。如果用户有图片，则跳过步骤2；如果没有图片，进入步骤2。生成内容到 `${workspace}/xhs-auto/{datetime}/{keyword}.md`： - 标题 - 正文 - tags - 图片路径，如果用户已提供图片 - 图片生成提示词，如果用户没有提供图片，每一张图一个 Prompt。 ### 步骤2. 生成图片 询问用户希望文生图还是图生图，并根据步骤1的主题和文案，生成生图 Prompt 和图片数量。 参考 `references/text-to-image.md`，调用 `scripts/xhs-image.py`（推荐）或 `scripts/xhs-image.sh` 进行图片生成。 **Python 版本示例：** ```bash python scripts/xhs-image.py \ --mode generate \ --prompt "小红书风格的下午茶摆拍" \ --ratio 1:1 \ --output workspace/xhs-auto/20260301-143022/image.png ``` ### 步骤3. 发布内容 参考 `references/publish.md` ，调用 `xhs-kit` 测试接口 `debug-publish` 发布内容。 ## 常见问题 ### 图片生成相关 - **推荐使用 Python 版本（xhs-image.py）**：跨平台兼容性更好，特别是 Windows 用户 - **ModuleNotFoundError: No module named 'requests'**：执行 `pip install requests` - **Bash 版本在 Windows 上无法运行**：建议使用 Python 版本，或安装 WSL/Git Bash - **国内网络访问模型失败**：配置国内可用的 API 中转服务，设置 `GOOGLE_API_BASE` 环境变量 ### 发布相关 - **debug-publish 报错图片不存在**：确保图片路径正确且文件存在 - **未登录错误**：正式发布前需要先执行 `xhs-kit login-qrcode --terminal` 详细说明请参考 `references/text-to-image.md` 和 `references/publish.md`。

path: scripts/xhs-image.sh
content: #!/usr/bin/env bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_PREFIX="[xhs-image]"

log_info() { echo "${LOG_PREFIX} $1" >&2; }
log_error() { echo "${LOG_PREFIX} ERROR: $1" >&2; }

usage() {
  cat <<'USAGE'
Usage: xhs-image.sh --mode generate|edit --prompt PROMPT [options]
       xhs-image.sh --mode edit --base-image PATH --prompt PROMPT [options]

Options:
  --mode MODE            generate (text-to-image) or edit (image-to-image)
  --prompt TEXT          prompt text for generation / editing
  --base-image PATH      base image path (required for edit mode)
  --provider NAME        google (nano-banana) | seed (bytedance seed), default: google
  --seed NUMBER          random seed (optional)
  --ratio RATIO          aspect ratio, e.g. 1:1 (default), 3:4, 4:3, 9:16, 16:9
  --output PATH          output file path (default: /tmp/xhs-image-<timestamp>.png)

Environment variables:
  Google nano banana (openai-compatible gateway expected)
    GOOGLE_API_KEY       required
    GOOGLE_API_BASE      optional, default: https://generativelanguage.googleapis.com/openai
    GOOGLE_IMAGE_MODEL   optional, default: gemini-3.1-flash-image-preview

  ByteDance Seed (openai-compatible gateway expected)
    SEED_API_KEY         required
    SEED_API_BASE        optional, default: https://seed.bytedanceapi.com
    SEED_IMAGE_MODEL     optional, default: bytedance/seed-v1

Dependencies: curl, jq, base64

The script expects the provider gateways to expose OpenAI-compatible endpoints:
  POST $BASE/v1/images/generations  (text-to-image, JSON)
  POST $BASE/v1/images/edits        (image editing, multipart form)
USAGE
}

MODE=""
PROMPT=""
BASE_IMAGE=""
PROVIDER="google"
SEED_VALUE=""
RATIO="1:1"
OUTPUT=""

while [[ $# -gt 0 ]]; do
  case "$1" in
    --mode) MODE="$2"; shift 2;;
    --prompt|-p) PROMPT="$2"; shift 2;;
    --base-image|-b) BASE_IMAGE="$2"; shift 2;;
    --provider) PROVIDER="$2"; shift 2;;
    --seed) SEED_VALUE="$2"; shift 2;;
    --ratio) RATIO="$2"; shift 2;;
    --output|-o) OUTPUT="$2"; shift 2;;
    --help|-h) usage; exit 0;;
    *) log_error "Unknown argument: $1"; usage; exit 1;;
  esac
done

if [[ -z "$MODE" ]]; then
  log_error "--mode is required"
  usage
  exit 1
fi
if [[ -z "$PROMPT" ]]; then
  log_error "--prompt is required"
  usage
  exit 1
fi
if [[ "$MODE" == "edit" && -z "$BASE_IMAGE" ]]; then
  log_error "--base-image is required for edit mode"
  usage
  exit 1
fi
if [[ "$MODE" != "generate" && "$MODE" != "edit" ]]; then
  log_error "Invalid mode: $MODE"
  usage
  exit 1
fi

case "$RATIO" in
  1:1) SIZE="1024x1024" ;;
  3:4) SIZE="1024x1365" ;;
  4:3) SIZE="1365x1024" ;;
  9:16) SIZE="1024x1820" ;;
  16:9) SIZE="1820x1024" ;;
  *) log_error "Unsupported ratio: $RATIO"; exit 1;;
esac

case "$PROVIDER" in
  google)
    [REDACTED_SECRET]"
    API_BASE="${GOOGLE_API_BASE:-${GEMINI_API_BASE:-https://generativelanguage.googleapis.com/openai}}"
    MODEL="${GOOGLE_IMAGE_MODEL:-${GEMINI_IMAGE_MODEL:-gemini-3.1-flash-image-preview}}"
    ;;
  seed)
    [REDACTED_SECRET]"
    API_BASE="${SEED_API_BASE:-https://seed.bytedanceapi.com}"
    MODEL="${SEED_IMAGE_MODEL:-bytedance/seed-v1}"
    ;;
  *)
    log_error "Unknown provider: $PROVIDER"
    exit 1
    ;;
esac

if [[ -z "$API_KEY" ]]; then
  log_error "API key not set for provider $PROVIDER"
  exit 1
fi

OUTPUT_PATH="${OUTPUT:-$(mktemp /tmp/xhs-image-XXXX.png)}"
ENDPOINT_BASE="${API_BASE%/}"

OUTPUT_DIR="$(dirname "$OUTPUT_PATH")"
if [[ ! -d "$OUTPUT_DIR" ]]; then
  mkdir -p "$OUTPUT_DIR" || {
    log_error "Failed to create output directory: $OUTPUT_DIR"
    exit 1
  }
fi

if ! command -v jq >/dev/null; then
  log_error "jq is required"
  exit 1
fi
if ! command -v curl >/dev/null; then
  log_error "curl is required"
  exit 1
fi

perform_generate() {
  local payload
  payload=$(jq -n \
    --arg model "$MODEL" \
    --arg prompt "$PROMPT" \
    --arg size "$SIZE" \
    --arg mode "$MODE" \
    --arg seed "$SEED_VALUE" \
    '{model: $model, prompt: $prompt, size: $size, n: 1} | if $seed != "" then .seed = ($seed|tonumber) else . end')

  local response
  response=$(curl -sS -X POST "${ENDPOINT_BASE}/v1/images/generations" \
    -H "Authorization: Bearer ${API_KEY}" \
    -H "Content-Type: application/json" \
    -d "$payload") || {
      log_error "Request failed"; exit 1; }

  local b64
  b64=$(echo "$response" | jq -r '.data[0].b64_json // empty')
  if [[ -z "$b64" ]]; then
    log_error "Failed to parse image data. Raw response:\n$response"
    exit 1
  fi
  echo "$b64" | base64 --decode >"$OUTPUT_PATH"
}

perform_edit() {
  local args=(
    -sS -X POST "${ENDPOINT_BASE}/v1/images/edits"
    -H "Authorization: Bearer ${API_KEY}"
    -F "model=${MODEL}"
    -F "prompt=${PROMPT}"
    -F "size=${SIZE}"
    -F "image=@${BASE_IMAGE}"
  )
  if [[ -n "$SEED_VALUE" ]]; then
    args+=( -F "seed=${SEED_VALUE}" )
  fi
  local response
  response=$(curl "${args[@]}") || {
      log_error "Request failed"; exit 1; }
  local b64
  b64=$(echo "$response" | jq -r '.data[0].b64_json // empty')
  if [[ -z "$b64" ]]; then
    log_error "Failed to parse image data. Raw response:\n$response"
    exit 1
  fi
  echo "$b64" | base64 --decode >"$OUTPUT_PATH"
}

case "$MODE" in
  generate)
    log_info "Generating image via ${PROVIDER} (${MODEL}) size ${SIZE}"
    perform_generate
    ;;
  edit)
    log_info "Editing image via ${PROVIDER} (${MODEL}) size ${SIZE}"
    perform_edit
    ;;
  *)
    log_error "Unsupported mode: ${MODE}"
    exit 1
    ;;
esac

log_info "Image saved to ${OUTPUT_PATH}"

jq -n \
  --arg success "true" \
  --arg mode "$MODE" \
  --arg provider "$PROVIDER" \
  --arg prompt "$PROMPT" \
  --arg base_image "$BASE_IMAGE" \
  --arg ratio "$RATIO" \
  --arg output "$OUTPUT_PATH" \
  '{success: ($success == "true"), mode: $mode, provider: $provider, prompt: $prompt, base_image: ($base_image // null), ratio: $ratio, output_path: $output}'


path: scripts/xhs-image.py
content: #!/usr/bin/env python3
"""
小红书图片生成工具 - 跨平台 Python 版本
支持文生图和图编辑，兼容 Google Gemini 和 ByteDance Seed
"""

import argparse
import base64
import json
import os
import sys
from pathlib import Path
from typing import Optional

try:
    import requests
except ImportError:
    print("Error: requests library not found. Install with: pip install requests", file=sys.stderr)
    sys.exit(1)


def log_info(msg: str):
    print(f"[xhs-image] {msg}", file=sys.stderr)


def log_error(msg: str):
    print(f"[xhs-image] ERROR: {msg}", file=sys.stderr)


class ImageGenerator:
    RATIO_TO_SIZE = {
        "1:1": "1024x1024",
        "3:4": "1024x1365",
        "4:3": "1365x1024",
        "9:16": "1024x1820",
        "16:9": "1820x1024",
    }

    def __init__(self, provider: str, mode: str, ratio: str):
        self.provider = provider
        self.mode = mode
        self.ratio = ratio
        self.size = self.RATIO_TO_SIZE.get(ratio)
        
        if not self.size:
            raise ValueError(f"Unsupported ratio: {ratio}")
        
        self._setup_api_config()

    def _setup_api_config(self):
        """根据 provider 设置 API 配置"""
        if self.provider == "google":
            self.[REDACTED_SECRET]"GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY")
            self.api_base = os.getenv("GOOGLE_API_BASE") or os.getenv("GEMINI_API_BASE") or \
                           "https://generativelanguage.googleapis.com/openai"
            self.model = os.getenv("GOOGLE_IMAGE_MODEL") or os.getenv("GEMINI_IMAGE_MODEL") or \
                        "gemini-3.1-flash-image-preview"
        elif self.provider == "seed":
            self.[REDACTED_SECRET]"SEED_API_KEY")
            self.api_base = os.getenv("SEED_API_BASE") or "https://seed.bytedanceapi.com"
            self.model = os.getenv("SEED_IMAGE_MODEL") or "bytedance/seed-v1"
        else:
            raise ValueError(f"Unknown provider: {self.provider}")
        
        if not self.api_key:
            raise ValueError(f"API key not set for provider {self.provider}")

    def generate(self, prompt: str, seed: Optional[int] = None) -> bytes:
        """文生图"""
        url = f"{self.api_base.rstrip('/')}/v1/images/generations"
        
        payload = {
            "model": self.model,
            "prompt": prompt,
            "size": self.size,
            "n": 1,
            "response_format": "b64_json"
        }
        
        if seed is not None:
            payload["seed"] = seed
        
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        log_info(f"Generating image via {self.provider} ({self.model}) size {self.size}")
        
        try:
            response = requests.post(url, json=payload, headers=headers, timeout=120)
            response.raise_for_status()
            
            result = response.json()
            b64_data = result["data"][0]["b64_json"]
            return base64.b64decode(b64_data)
        
        except requests.exceptions.RequestException as e:
            log_error(f"API request failed: {e}")
            if hasattr(e, 'response') and e.response is not None:
                log_error(f"Response: {e.response.text}")
            raise

    def edit(self, prompt: str, base_image_path: str, seed: Optional[int] = None) -> bytes:
        """图编辑"""
        url = f"{self.api_base.rstrip('/')}/v1/images/edits"
        
        base_path = Path(base_image_path)
        if not base_path.exists():
            raise FileNotFoundError(f"Base image not found: {base_image_path}")
        
        with open(base_path, "rb") as f:
            image_data = f.read()
        
        files = {
            "image": (base_path.name, image_data, "image/png")
        }
        
        data = {
            "model": self.model,
            "prompt": prompt,
            "size": self.size,
            "n": 1,
            "response_format": "b64_json"
        }
        
        if seed is not None:
            data["seed"] = seed
        
        headers = {
            "Authorization": f"Bearer {self.api_key}"
        }
        
        log_info(f"Editing image via {self.provider} ({self.model}) size {self.size}")
        
        try:
            response = requests.post(url, data=data, files=files, headers=headers, timeout=120)
            response.raise_for_status()
            
            result = response.json()
            b64_data = result["data"][0]["b64_json"]
            return base64.b64decode(b64_data)
        
        except requests.exceptions.RequestException as e:
            log_error(f"API request failed: {e}")
            if hasattr(e, 'response') and e.response is not None:
                log_error(f"Response: {e.response.text}")
            raise


def main():
    parser = argparse.ArgumentParser(
        description="小红书图片生成工具 - 文生图/图编辑",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
环境变量:
  Google/Gemini:
    GOOGLE_API_KEY 或 GEMINI_API_KEY       (必需)
    GOOGLE_API_BASE 或 GEMINI_API_BASE     (可选, 默认: https://generativelanguage.googleapis.com/openai)
    GOOGLE_IMAGE_MODEL 或 GEMINI_IMAGE_MODEL (可选, 默认: gemini-3.1-flash-image-preview)
  
  ByteDance Seed:
    SEED_API_KEY        (必需)
    SEED_API_BASE       (可选, 默认: https://seed.bytedanceapi.com)
    SEED_IMAGE_MODEL    (可选, 默认: bytedance/seed-v1)

示例:
  # 文生图
  python xhs-image.py --mode generate --prompt "一只可爱的猫" --output cat.png
  
  # 图编辑
  python xhs-image.py --mode edit --prompt "添加温暖的光线" --base-image input.png --output output.png
"""
    )
    
    parser.add_argument("--mode", required=True, choices=["generate", "edit"],
                       help="模式: generate (文生图) 或 edit (图编辑)")
    parser.add_argument("--prompt", required=True,
                       help="图像生成/编辑提示词")
    parser.add_argument("--base-image",
                       help="底图路径 (edit 模式必需)")
    parser.add_argument("--provider", default="google", choices=["google", "seed"],
                       help="图像提供方 (默认: google)")
    parser.add_argument("--ratio", default="1:1",
                       choices=["1:1", "3:4", "4:3", "9:16", "16:9"],
                       help="图片比例 (默认: 1:1)")
    parser.add_argument("--seed", type=int,
                       help="随机种子 (可选)")
    parser.add_argument("--output", required=True,
                       help="输出图片路径")
    
    args = parser.parse_args()
    
    # 验证参数
    if args.mode == "edit" and not args.base_image:
        parser.error("--base-image is required for edit mode")
    
    try:
        # 创建生成器
        generator = ImageGenerator(args.provider, args.mode, args.ratio)
        
        # 生成图片
        if args.mode == "generate":
            image_data = generator.generate(args.prompt, args.seed)
        else:
            image_data = generator.edit(args.prompt, args.base_image, args.seed)
        
        # 保存图片
        output_path = Path(args.output)
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        with open(output_path, "wb") as f:
            f.write(image_data)
        
        log_info(f"Image saved to {output_path.absolute()}")
        
        # 输出 JSON 结果（供脚本解析）
        result = {
            "success": True,
            "output": str(output_path.absolute()),
            "mode": args.mode,
            "provider": args.provider,
            "size": generator.size
        }
        print(json.dumps(result, ensure_ascii=False))
        
        return 0
    
    except Exception as e:
        log_error(str(e))
        result = {
            "success": False,
            "error": str(e)
        }
        print(json.dumps(result, ensure_ascii=False))
        return 1


if __name__ == "__main__":
    sys.exit(main())


path: references/publish.md
content: # 小红书内容发布与校验

本文档说明如何使用 `xhs-kit` 进行内容发布前的校验（debug）和正式发布。

## 安装

```
# 使用 pip 安装
pip install -U xhs-kit

# 安装 Playwright 浏览器（必需）
playwright install chromium
```

## Debug 模式：发布前校验

在正式发布前，使用 `debug-publish` 命令验证内容是否符合小红书规范，**无需登录，不会实际发布**。

### 命令格式

```bash
xhs-kit debug-publish \
  --title "标题" \
  --content "正文内容" \
  --image /path/to/image1.png \
  --image /path/to/image2.png \
  --tag "标签1" \
  --tag "标签2" \
  [--verbose]
```

### 参数说明

| 参数 | 简写 | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | `-t` | ✅ | 文章标题（最多 20 字符） |
| `--content` | `-c` | ✅ | 正文内容（建议 900 字符内） |
| `--image` | `-i` | ✅ | 图片路径（可多次指定，最多 16 张） |
| `--tag` | 无 | ❌ | 话题标签（可多次指定） |
| `--verbose` | `-v` | ❌ | 显示详细信息（错误、警告、图片详情） |

### 输出模式

**简洁模式（默认）：**
```
✅ 验证通过
```
或
```
❌ 验证失败
```

**Verbose 模式（`--verbose`）：**
```
✅ 验证通过

详细信息:
{
  "title_length": 8,
  "content_length": 120,
  "total_images": 2,
  "valid_images": 2,
  "image_details": [
    {
      "index": 1,
      "path": "/path/to/image1.png",
      "exists": true,
      "format_valid": true,
      "format": ".png",
      "size_mb": 2.5,
      "width": 1024,
      "height": 1024,
      "mode": "RGB"
    }
  ]
}
```

### 校验规则

**必须通过的验证（errors）：**
- ❌ 标题为空
- ❌ 标题长度超过 20 字符
- ❌ 图片文件不存在
- ❌ 图片格式不支持（仅支持 jpg/png/webp/heic）
- ❌ 图片数量超过 16 张

**建议性警告（warnings）：**
- ⚠️ 标题接近长度限制（17-20 字符）
- ⚠️ 正文为空
- ⚠️ 正文过长（超过 1000 字符）
- ⚠️ 正文接近上限（900-1000 字符）
- ⚠️ 图片分辨率较低（小于 480x480）
- ⚠️ 图片文件较大（超过 20MB）
- ⚠️ 标签数量过多（超过 10 个）
- ⚠️ 单个标签过长（超过 20 字符）

### 在自动化流程中使用

```bash
# 在脚本中调用 debug-publish
xhs-kit debug-publish \
  --title "$TITLE" \
  --content "$CONTENT" \
  --image "$IMAGE_PATH" \
  --verbose > validation.log

# 检查退出码
if [ $? -eq 0 ]; then
  echo "验证通过，可以继续发布"
else
  echo "验证失败，请检查 validation.log"
  exit 1
fi
```

## 正式发布

**⚠️ 重要：正式发布需要先登录小红书账号。**

### 登录步骤

1. 检查登录状态：
```bash
xhs-kit status
```

2. 如果未登录，执行登录（推荐终端二维码方式）：
```bash
xhs-kit login-qrcode --terminal
```

或保存二维码图片：
```bash
xhs-kit login-qrcode --save /tmp/qrcode.png
```

3. 扫码后按回车完成登录

### 发布图文内容

```bash
xhs-kit publish \
  --title "标题" \
  --content "正文内容" \
  --image /path/to/image1.png \
  --image /path/to/image2.png \
  --tag "标签1" \
  --tag "标签2"
```

### 参数说明

| 参数 | 简写 | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | `-t` | ✅ | 文章标题（最多 20 字符） |
| `--content` | `-c` | ✅ | 正文内容 |
| `--image` | `-i` | ✅ | 图片路径（可多次指定，最多 16 张） |
| `--tag` | 无 | ❌ | 话题标签（可多次指定） |
| `--schedule-at` | 无 | ❌ | 定时发布时间（ISO8601 格式） |

### 发布成功输出

```
🎉 发布成功
标题: 标题
笔记 ID: 6789abcdef123456
笔记链接: https://www.xiaohongshu.com/explore/6789abcdef123456
```

## 完整工作流示例

```bash
#!/bin/bash
set -e

TITLE="我的小红书分享"
CONTENT="今天分享一些生活小技巧..."
IMAGE="/path/to/image.png"

# 步骤 1: Debug 校验
echo "正在校验内容..."
xhs-kit debug-publish \
  --title "$TITLE" \
  --content "$CONTENT" \
  --image "$IMAGE" \
  --verbose

# 步骤 2: 检查登录状态
echo "检查登录状态..."
if ! xhs-kit status; then
  echo "未登录，请先登录"
  xhs-kit login-qrcode --terminal
fi

# 步骤 3: 正式发布
echo "正在发布..."
xhs-kit publish \
  --title "$TITLE" \
  --content "$CONTENT" \
  --image "$IMAGE"

echo "发布完成！"
```

## 常见问题

1. **debug-publish 报错"图片不存在"**：确认图片路径是绝对路径且文件存在。
2. **publish 报错"未登录"**：先执行 `xhs-kit status` 检查，如未登录则执行 `xhs-kit login-qrcode`。
3. **发布后内容不可见**：可能触发平台审核，等待 1-2 小时后查看。
4. **Cookies 过期**：执行 `xhs-kit status --verify` 真实校验，如失败则重新登录。


path: references/text-to-image.md
content: ---
name: xhs-image
description: 小红书图片生成工具文生图 / 图编辑工具，封装 Google Gemini "nano banana" 与字节跳动 Seed 的 OpenAI 兼容接口，用于生成小红书内容配图。
metadata: {"openclaw": {"emoji": "🖼️"}}
---

# 文生图和图编辑

面向小红书自媒体运营的文生图和图编辑工具，提供两个能力：
1. **文生图**：根据 Prompt 生成图片，支持常见比例（默认 1:1）。
2. **图编辑 / 优化**：基于已有底图与 Prompt 生成优化后的图片。

基于 OpenAI 兼容的图片生成接口，支持以下提供方：
- `google`：Google Gemini "nano banana"（需配置 Google API 网关）
- `seed`：字节跳动 Seed（需配置 Seed API 网关）

## 实现版本

提供两个版本的实现：

### Python 版本（推荐）✅
- **脚本路径**：`{baseDir}/scripts/xhs-image.py`
- **优势**：跨平台兼容（Windows/macOS/Linux），依赖简单，易于维护
- **依赖**：`pip install requests`
- **推荐场景**：所有场景，特别是 Windows 用户

### Bash 版本
- **脚本路径**：`{baseDir}/scripts/xhs-image.sh`
- **优势**：Unix 系统原生支持，无需 Python 环境
- **依赖**：`curl`、`jq`、`base64`
- **推荐场景**：纯 Unix/Linux 环境，或不希望安装 Python 依赖的场景

## 环境变量

### Google Gemini（nano banana）
| 变量名 | 说明 | 默认值 |
| --- | --- | --- |
| `GOOGLE_API_KEY` / `GEMINI_API_KEY` | ✅ 必填，Google/Gemini API Key（任意其一） | 无 |
| `GOOGLE_API_BASE` / `GEMINI_API_BASE` | 接口 Base URL | `https://generativelanguage.googleapis.com/openai` |
| `GOOGLE_IMAGE_MODEL` / `GEMINI_IMAGE_MODEL` | 图片模型名称 | `gemini-3.1-flash-image-preview` |

### ByteDance Seed
| 变量名 | 说明 | 默认值 |
| --- | --- | --- |
| `SEED_API_KEY` | ✅ 必填，Seed API Key | 无 |
| `SEED_API_BASE` | 接口 Base URL | `https://seed.bytedanceapi.com` |
| `SEED_IMAGE_MODEL` | 图片模型名称 | `bytedance/seed-v1` |

## 命令行参数

两个版本的参数完全一致：

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `--mode` | 枚举 | ✅ | `generate`（文生图）或 `edit`（图编辑） |
| `--prompt` | 字符串 | ✅ | 提示词；用于生成或编辑指导 |
| `--base-image` | 路径 | ⚠️ | 仅在 `--mode edit` 下必填，作为底图输入 |
| `--provider` | 枚举 | ❌ | `google`（默认）或 `seed`，决定使用哪套环境变量 |
| `--seed` | 整数 | ❌ | 随机种子 |
| `--ratio` | 枚举 | ❌ | 输出比例，可选 `1:1`（默认）、`3:4`、`4:3`、`9:16`、`16:9` |
| `--output` | 路径 | ✅ | 指定输出文件路径 |

> **返回值**：
> 1. 标准错误输出（stderr）：日志信息
> 2. 标准输出（stdout）：JSON 结果，包含 `success`、`output`、`mode`、`provider` 等字段，方便 Agent 解析

## 依赖

### Python 版本（推荐）
```bash
pip install requests
```

### Bash 版本
- `curl`：向 OpenAI 兼容接口发起 HTTP 请求
- `jq`：解析接口返回的 JSON，提取 b64 数据
- `base64`：将返回的 Base64 图片内容解码为 PNG

安装示例：
- **macOS**：`brew install curl jq coreutils`
- **Ubuntu / Debian**：`sudo apt-get install curl jq coreutils`
- **CentOS / Rocky / Alma**：`sudo yum install curl jq coreutils`
- **Windows**：需要 Git Bash 或 WSL 环境

## 使用方式

### Python 版本（推荐）

#### 文生图
```bash
# 设置环境变量
export GOOGLE_API_KEY=xxx
export GOOGLE_API_BASE=https://...

# 生成图片
python {baseDir}/scripts/xhs-image.py \
  --mode generate \
  --prompt "小红书风格的下午茶摆拍" \
  --ratio 1:1 \
  --provider google \
  --output /path/to/output.png
```

#### 图编辑（基于已有底图）
```bash
python {baseDir}/scripts/xhs-image.py \
  --mode edit \
  --base-image /path/to/base.png \
  --prompt "添加温暖的光线和生活气息" \
  --ratio 1:1 \
  --output /path/to/output.png
```

**输出示例：**
```json
{
  "success": true,
  "output": "/path/to/output.png",
  "mode": "generate",
  "provider": "google",
  "size": "1024x1024"
}
```

> 如果 `--output` 指定的目录不存在，脚本会自动创建。

### Bash 版本

#### 文生图
```bash
# 赋予执行权限（首次使用）
chmod +x {baseDir}/scripts/xhs-image.sh

# 生成图片
GOOGLE_API_KEY=xxx GOOGLE_API_BASE=https://... {baseDir}/scripts/xhs-image.sh \
  --mode generate \
  --prompt "小红书风格的下午茶摆拍" \
  --ratio 1:1 \
  --provider google \
  --output /path/to/output.png
```

#### 图编辑
```bash
{baseDir}/scripts/xhs-image.sh \
  --mode edit \
  --base-image /path/to/base.png \
  --prompt "添加温暖的光线和生活气息" \
  --ratio 1:1 \
  --output /path/to/output.png
```

## 输出
- 控制台日志（stderr）：流程与错误信息
- 控制台结果（stdout）：图片文件路径
- JSON（stdout 尾部）：
```json
{
  "success": true,
  "mode": "generate",
  "provider": "google",
  "prompt": "...",
  "base_image": null,
  "ratio": "1:1",
  "output_path": "/tmp/xhs-image-XXXX.png"
}
```

## 常见问题

### Python 版本
1. **ModuleNotFoundError: No module named 'requests'**  
   安装依赖：`pip install requests`

2. **返回 401 Unauthorized**  
   确认 API Key 是否正确且写入对应环境变量（`GOOGLE_API_KEY` 或 `GEMINI_API_KEY`）

3. **FileNotFoundError: Base image not found**  
   确认 `--base-image` 路径正确且文件存在

4. **国内网络不可达 / 连接超时**  
   国内直连国际模型常被阻断，建议购买具备 OpenAI 兼容出口的国内中转站或代理网关，再将 `GOOGLE_API_BASE` / `GEMINI_API_BASE` 指向该中转服务

5. **Windows 上路径问题**  
   使用正斜杠 `/` 或双反斜杠 `\\`，例如：`C:/path/to/image.png` 或 `C:\\path\\to\\image.png`

### Bash 版本
1. **提示缺少必要依赖项（curl/jq/base64）**  
   请先安装依赖，参考上方"依赖"部分

2. **Windows 上无法运行**  
   Bash 脚本需要 Unix 环境，建议在 Windows 上使用 Python 版本，或安装 WSL/Git Bash

3. **返回 empty data**  
   检查 API Base URL 是否指向 OpenAI 兼容的网关


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)