Skill: loobayn/newapi-banana (version 1.0.0)

--- name: newapi-banana description: "Generate images via NewAPI Banana API (nano-banana, Gemini). Supports text-to-image and image-to-image." homepage: http://nen.baynn.com metadata: { "openclaw": { "emoji": "🎨", "requires": { "bins": ["python3", "curl"] }, "primaryEnv": "NEWAPI_API_KEY" } } --- # NewAPI Banana Skill Standard API Script: `python3 {baseDir}/scripts/newapi-banana.py` Data: `{baseDir}/data/capabilities.json` ## Persona You are **NewAPI 画家小助手** — a creative AI assistant specializing in image generation. ALL responses MUST follow: - Speak Chinese. Warm & lively: "搞定啦～"、"来啦！"、"超棒的". Never robotic. - Show cost naturally if available: "花了 ¥X.XX" (not "Cost: ¥X.XX"). - Never show internal API URLs to users — use friendly descriptions. - After delivering results, suggest next steps ("需要调整一下吗？"、"要换个风格试试吗？"). ## CRITICAL RULES 1. **ALWAYS use the script** — never call API directly. 2. **ALWAYS use `-o /tmp/openclaw/newapi-output/<name>.<ext>`** with timestamps in filenames. 3. **Deliver files via `message` tool** — you MUST call `message` tool to send media. Do NOT print file paths as text. 4. **NEVER show internal API URLs** — all API URLs are internal. Users cannot open them. 5. **NEVER use `![](url)` markdown images or print raw file paths** — ONLY the `message` tool can deliver files to users. 6. **ALWAYS report cost** — if script prints `COST:¥X.XX`, include it in your response as "花了 ¥X.XX". 7. **ALL image generation** → Read `{baseDir}/references/image-generation.md` and follow its complete flow. WAIT for user choice before running any generation script. ## API Key Setup When user needs to set up or check their API key → Read `{baseDir}/references/api-key-setup.md` and follow its instructions. Quick check: `python3 {baseDir}/scripts/newapi-banana.py --check` ## Supported Models ### Image Generation Models - `nano-banana` - Standard image generation model (recommended) - `nano-banana-2` - Enhanced version - `nano-banana-fast` - Fast generation model - `nano-banana-pro` - Image-to-image editing model - `gemini-3-pro-image-preview` - Gemini Pro model ## Routing Table | Intent | Endpoint | Notes | |--------|----------|-------| | **Text to image** | **⚠️ Read `{baseDir}/references/image-generation.md`** | MUST present model menu first | | **Image to image** | **⚠️ Read `{baseDir}/references/image-generation.md`** | MUST present model menu first | ## Script Usage ### Image Generation ```bash python3 {baseDir}/scripts/newapi-banana.py \ --task text-to-image \ --prompt "prompt text" \ --model nano-banana \ --aspect-ratio 4:3 \ --output /tmp/openclaw/newapi-output/image_$(date +%s).png ``` For image-to-image: ```bash python3 {baseDir}/scripts/newapi-banana.py \ --task image-to-image \ --prompt "prompt text" \ --image /path/to/image.png \ --model nano-banana-pro \ --output /tmp/openclaw/newapi-output/edited_$(date +%s).png ``` Optional flags: - `--host-url URL` - API host URL (default: http://nen.baynn.com) - `--api-key KEY` - API key (or use NEWAPI_API_KEY env var) - `--model MODEL` - Model name - `--aspect-ratio RATIO` - Aspect ratio (4:3, 16:9, 9:16, etc.) Discovery: `--list`, `--info TASK` ## Output For media delivery and error handling details → Read `{baseDir}/references/output-delivery.md`. Key rules (always apply): - ALWAYS call `message` tool to deliver media files, then respond `NO_REPLY`. - If `message` fails, retry once. If still fails, include `OUTPUT_FILE:<path>` and explain. - Print text results directly. Include cost if `COST:` line present. ## API Host - http://nen.baynn.com

path: _meta.json
content: ownerId: kn70vr1zkfvz80p19qkyddbsgn8298a3
slug: newapi-banana2
version: 1.0.0
publishedAt: 1773496102803

path: install.md
content: # NewAPI Banana - OPENCLAW Skill

支持图片生成的 OPENCLAW SKILLS，包含文生图和图生图功能。

## 🚀 快速开始

### 1. 配置 API Key

#### 方法一：使用配置脚本（最简单）

在正常运行的OPENCLAW中，开启聊天，把你的APIKEY直接发给小龙虾，让他帮你配置好，就可以用了
```

#### 方法二：手动编辑配置文件

1. 打开配置文件：`C:\Users\你的用户名\.openclaw\openclaw.json`
2. 如果文件不存在，创建它并添加：

```json
{
  "skills": {
    "entries": {
      "newapi-banana": {
        "apiKey": "your_api_key_here"
      }
    }
  }
}
```

#### 方法三：设置环境变量

在 PowerShell 中：

```powershell
$env:NEWAPI_API_KEY = "your_api_key_here"
```

### 2. 验证配置

```powershell
python3 scripts\newapi-banana.py --check
```

如果配置正确，会显示：

```json
{
  "status": "ready",
  "key_prefix": "xxxx****",
  "host": "http://nen.baynn.com",
  "message": "API key is valid"
}
```

## 📖 使用示例

### 生成图片

```powershell
python3 scripts\newapi-banana.py `
  --task text-to-image `
  --prompt "一只可爱的橘猫" `
  --model nano-banana `
  --aspect-ratio 4:3 `
  -o output.png
```

## 📚 更多文档

- [API Key 设置指南](references/api-key-setup.md)
- [图片生成指南](references/image-generation.md)
- [输出交付指南](references/output-delivery.md)

## 🔧 支持的功能

- ✅ 文生图（Text-to-Image）
- ✅ 图生图（Image-to-Image）

## 📝 支持的模型

### 图片模型
- `nano-banana` - 标准图片生成模型（推荐）
- `nano-banana-2` - 增强版本
- `nano-banana-fast` - 快速生成模型
- `nano-banana-pro` - 图生图编辑模型
- `gemini-3-pro-image-preview` - Gemini Pro 模型

## 🌐 API 地址

- http://nen.baynn.com

## 📞 获取 API Key

请联系您的 NewAPI Banana 服务提供商获取 API Key。


path: data/capabilities.json
content: {
  "version": "2024-01-01",
  "total": 2,
  "endpoints": [
    {
      "endpoint": "text-to-image",
      "name_cn": "文生图",
      "name_en": "Text to Image",
      "task": "text-to-image",
      "output_type": "image",
      "category": "image",
      "popularity": 1,
      "tags": ["image", "generation"],
      "params": [
        {
          "key": "prompt",
          "type": "STRING",
          "required": true
        },
        {
          "key": "model",
          "type": "STRING",
          "required": false,
          "default": "nano-banana",
          "options": ["nano-banana", "nano-banana-2", "nano-banana-fast", "nano-banana-pro", "gemini-3-pro-image-preview"]
        },
        {
          "key": "aspect_ratio",
          "type": "STRING",
          "required": false,
          "default": "auto",
          "options": ["auto", "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "5:4", "4:5", "21:9"]
        }
      ]
    },
    {
      "endpoint": "image-to-image",
      "name_cn": "图生图",
      "name_en": "Image to Image",
      "task": "image-to-image",
      "output_type": "image",
      "category": "image",
      "popularity": 2,
      "tags": ["image", "edit"],
      "params": [
        {
          "key": "prompt",
          "type": "STRING",
          "required": true
        },
        {
          "key": "image",
          "type": "IMAGE",
          "required": true
        },
        {
          "key": "model",
          "type": "STRING",
          "required": false,
          "default": "nano-banana-pro"
        }
      ]
    }
  ]
}


path: scripts/newapi-banana.py
content: #!/usr/bin/env python3
"""
NewAPI Banana image generation client for OpenClaw.

Supports image generation via nano-banana and Gemini APIs.
Uses only Python stdlib and curl.

Modes:
  --check                          Account health check
  --list                           List available tasks
  --info TASK                      Show task details
  --task TASK --prompt "..." ...   Execute a generation task
"""

from __future__ import annotations

import argparse
import base64
import json
import mimetypes
import os
import subprocess
import sys
import tempfile
import time
from pathlib import Path

# API host（只使用主地址，避免切到你没有权限的备份域名）
DEFAULT_HOST = "http://nen.baynn.com"

SCRIPT_DIR = Path(__file__).resolve().parent
DATA_DIR = SCRIPT_DIR.parent / "data"
CAPABILITIES_PATH = DATA_DIR / "capabilities.json"


# ---------------------------------------------------------------------------
# API key resolution
# ---------------------------------------------------------------------------

def read_key_from_openclaw_config() -> str | None:
    cfg_path = Path.home() / ".openclaw" / "openclaw.json"
    if not cfg_path.exists():
        return None
    try:
        cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
    except Exception:
        return None
    entry = cfg.get("skills", {}).get("entries", {}).get("newapi-banana", {})
    [REDACTED_SECRET]"apiKey")
    if isinstance(api_key, str) and api_key.strip():
        return api_key.strip()
    env_val = entry.get("env", {}).get("NEWAPI_API_KEY")
    if isinstance(env_val, str) and env_val.strip():
        return env_val.strip()
    return None


def resolve_api_key(provided_key: str | None) -> str | None:
    """Resolve API key without exiting. Returns None if not found."""
    if provided_key:
        normalized = provided_key.strip()
        placeholders = {
            "your_api_key_here", "<your_api_key>",
            "YOUR_API_KEY", "NEWAPI_API_KEY",
        }
        if normalized and normalized not in placeholders:
            return normalized

    env_key = os.environ.get("NEWAPI_API_KEY", "").strip()
    if env_key:
        return env_key

    return read_key_from_openclaw_config()


def require_api_key(provided_key: str | None) -> str:
    key = resolve_api_key(provided_key)
    if key:
        return key
    result = {
        "error": "NO_API_KEY",
        "message": "No API key configured",
        "steps": [
            "1. Get API key from your NewAPI Banana service provider",
            "2. Send the key in chat or add to ~/.openclaw/openclaw.json: skills.entries.newapi-banana.apiKey",
        ],
    }
    print(json.dumps(result, ensure_ascii=False))
    sys.exit(1)


# ---------------------------------------------------------------------------
# HTTP helpers (curl-based, stdlib only)
# ---------------------------------------------------------------------------

def curl_post_json(url: str, payload: dict, headers: dict, timeout: int = 60) -> subprocess.CompletedProcess:
    with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
        json.dump(payload, f)
        tmp_path = f.name
    try:
        cmd = ["curl", "-s", "-S", "--fail-with-body", "-X", "POST", url,
               "--max-time", str(timeout), "-d", f"@{tmp_path}"]
        for k, v in headers.items():
            cmd += ["-H", f"{k}: {v}"]
        return subprocess.run(cmd, capture_output=True, text=True)
    finally:
        os.unlink(tmp_path)


def curl_post_form_data(url: str, form_data: dict, headers: dict, timeout: int = 120) -> subprocess.CompletedProcess:
    """Post multipart/form-data using curl.

    支持直接传入 Path / 文件路径，会自动按文件处理：
    - key=Path(...) 或 key=\"C:/path/to/file.png\" => -F key=@C:/path/to/file.png
    """
    cmd = ["curl", "-s", "-S", "--fail-with-body", "-X", "POST", url,
           "--max-time", str(timeout)]
    for k, v in headers.items():
        cmd += ["-H", f"{k}: {v}"]

    for key, value in form_data.items():
        # 多值（暂时不需要文件列表，这里保持简单）
        if isinstance(value, (list, tuple)):
            for item in value:
                if isinstance(item, Path):
                    cmd += ["-F", f"{key}=@{str(item)}"]
                elif isinstance(item, str) and os.path.exists(item):
                    cmd += ["-F", f"{key}=@{item}"]
                else:
                    cmd += ["-F", f"{key}={item}"]
        else:
            # 单值：优先按文件处理
            if isinstance(value, Path):
                cmd += ["-F", f"{key}=@{str(value)}"]
            elif isinstance(value, str) and os.path.exists(value):
                cmd += ["-F", f"{key}=@{value}"]
            else:
                cmd += ["-F", f"{key}={value}"]

    return subprocess.run(cmd, capture_output=True, text=True)


def api_request_with_backup(api_key: str, endpoint_suffix: str, payload: dict,
                            method: str = "POST", timeout: int = 60,
                            is_form_data: bool = False) -> dict:
    """单一主机请求封装（历史上支持多备份，这里只用 DEFAULT_HOST）。

    这样可以避免脚本自动切到你没有权限的其它域名（比如 api.gptbest.vip）。
    """
    url = f"{DEFAULT_HOST}{endpoint_suffix}"
    headers = {
        "Authorization": f"Bearer {api_key}",
    }

    if not is_form_data:
        headers["Content-Type"] = "application/json"

    max_retries = 3
    last_error: dict | None = None

    for retry in range(max_retries):
        try:
            if is_form_data:
                result = curl_post_form_data(url, payload, headers, timeout)
            else:
                if method == "POST":
                    result = curl_post_json(url, payload, headers, timeout)
                else:  # GET
                    cmd = [
                        "curl", "-s", "-S", "--fail-with-body", "-X", "GET", url,
                        "--max-time", str(timeout),
                    ]
                    for k, v in headers.items():
                        cmd += ["-H", f"{k}: {v}"]
                    result = subprocess.run(cmd, capture_output=True, text=True)

            if result.returncode == 0:
                try:
                    return json.loads(result.stdout)
                except json.JSONDecodeError:
                    print(f"Error: Invalid JSON response from {url}", file=sys.stderr)
                    print(f"Response: {result.stdout[:500]}", file=sys.stderr)
                    sys.exit(1)
            else:
                error_body = result.stdout or result.stderr
                try:
                    err = json.loads(error_body)
                    msg = err.get("msg", error_body)
                except (json.JSONDecodeError, TypeError):
                    msg = error_body

                last_error = {
                    "error": "API_ERROR",
                    "message": f"API request failed: {msg}",
                    "host": DEFAULT_HOST,
                    "url": url,
                }

        except Exception as e:
            last_error = {
                "error": "REQUEST_ERROR",
                "message": str(e),
                "host": DEFAULT_HOST,
            }

        if retry < max_retries - 1:
            wait_time = 1000 * (retry + 1)
            time.sleep(wait_time / 1000)
            continue

    # 所有重试失败
    if last_error:
        print(json.dumps(last_error, ensure_ascii=False), file=sys.stderr)
    else:
        print(json.dumps({
            "error": "ALL_HOSTS_FAILED",
            "message": "Primary host failed to respond"
        }, ensure_ascii=False), file=sys.stderr)
    sys.exit(1)


# ---------------------------------------------------------------------------
# --check: account health check
# ---------------------------------------------------------------------------

def cmd_check(api_key_arg: str | None, host_url: str | None):
    key = resolve_api_key(api_key_arg)
    host = host_url or DEFAULT_HOST
    
    if not key:
        print(json.dumps({
            "status": "no_key",
            "message": "No API key configured",
            "steps": [
                "1. Get API key from your NewAPI Banana service provider",
                "2. Send the key in chat or add to ~/.openclaw/openclaw.json: skills.entries.newapi-banana.apiKey",
            ],
        }, ensure_ascii=False))
        return
    
    key_prefix = key[:4] + "****"
    
    # Try to check API by making a simple request directly (avoid sys.exit from wrapper)
    try:
        cmd = [
            "curl", "-s", "-S", "--fail-with-body", "-X", "GET",
            f"{host}/v1/models",
            "--max-time", "10",
            "-H", f"Authorization: Bearer {key}",
        ]
        result = subprocess.run(cmd, capture_output=True, text=True)
        if result.returncode == 0:
            print(json.dumps({
                "status": "ready",
                "key_prefix": key_prefix,
                "host": host,
                "message": "API key is valid"
            }, ensure_ascii=False))
        else:
            print(json.dumps({
                "status": "invalid_key",
                "key_prefix": key_prefix,
                "host": host,
                "message": "API key verification failed"
            }, ensure_ascii=False))
    except Exception as e:
        print(json.dumps({
            "status": "error",
            "key_prefix": key_prefix,
            "host": host,
            "message": str(e)
        }, ensure_ascii=False))


# ---------------------------------------------------------------------------
# Media handling
# ---------------------------------------------------------------------------

def image_to_data_uri(file_path: str) -> str:
    mime_type = mimetypes.guess_type(file_path)[0] or "image/png"
    with open(file_path, "rb") as f:
        encoded = base64.b64encode(f.read()).decode()
    return f"data:{mime_type};base64,{encoded}"


# ---------------------------------------------------------------------------
# Image generation
# ---------------------------------------------------------------------------

def cmd_generate_image(args):
    """Generate image (text-to-image or image-to-image)."""
    [REDACTED_SECRET])

    if not args.prompt:
        print("Error: --prompt is required", file=sys.stderr)
        sys.exit(1)

    model = args.model or "nano-banana"
    aspect_ratio = args.aspect_ratio or "auto"

    if "gemini" in model.lower():
        # Gemini via NewAPI relay — same host, Bearer auth, Gemini request format
        endpoint = f"/v1beta/models/{model}:generateContent"
        url = f"{DEFAULT_HOST}{endpoint}"

        parts = [{"text": args.prompt}]

        if args.image:
            image_path = Path(args.image)
            if not image_path.exists():
                print(f"Error: image file not found: {args.image}", file=sys.stderr)
                sys.exit(1)
            mime_type = mimetypes.guess_type(str(image_path))[0] or "image/png"
            with open(image_path, "rb") as f:
                b64_data = base64.b64encode(f.read()).decode()
            parts.append({
                "inlineData": {
                    "mimeType": mime_type,
                    "data": b64_data
                }
            })

        payload = {
            "contents": [{"parts": parts}],
            "generationConfig": {"responseModalities": ["TEXT", "IMAGE"]}
        }

        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}",
        }
        result = curl_post_json(url, payload, headers, timeout=120)

        if result.returncode != 0:
            error_body = result.stdout or result.stderr
            print(f"Gemini API error: {error_body[:500]}", file=sys.stderr)
            sys.exit(1)

        try:
            resp = json.loads(result.stdout)
        except json.JSONDecodeError:
            print(f"Error: Invalid JSON from Gemini API", file=sys.stderr)
            print(result.stdout[:500], file=sys.stderr)
            sys.exit(1)

        # Extract inline image from response
        image_b64 = None
        image_mime = "image/png"
        candidates = resp.get("candidates", [])
        if candidates:
            for part in candidates[0].get("content", {}).get("parts", []):
                inline = part.get("inlineData")
                if inline:
                    image_b64 = inline.get("data")
                    image_mime = inline.get("mimeType", "image/png")
                    break

        if not image_b64:
            print("Error: No image data in Gemini response", file=sys.stderr)
            print(json.dumps(resp, indent=2, ensure_ascii=False), file=sys.stderr)
            sys.exit(1)

        ext = image_mime.split("/")[-1].replace("jpeg", "jpg")
        output_path = args.output or f"/tmp/openclaw/newapi-output/image_{int(time.time())}.{ext}"
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)
        with open(output_path, "wb") as f:
            f.write(base64.b64decode(image_b64))

        print(f"OUTPUT_FILE:{Path(output_path).resolve()}")
        return

    # nano-banana series: unified endpoint for text-to-image and image-to-image
    endpoint = "/v1/images/generations"
    payload = {
        "model": model,
        "prompt": args.prompt,
        "aspectRatio": aspect_ratio,
        "imageSize": "1K",
        "shutProgress": False,
    }

    # If image provided, encode as base64 and add to payload
    if args.image:
        image_path = Path(args.image)
        if not image_path.exists():
            print(f"Error: image file not found: {args.image}", file=sys.stderr)
            sys.exit(1)
        mime_type = mimetypes.guess_type(str(image_path))[0] or "image/png"
        with open(image_path, "rb") as f:
            b64_data = base64.b64encode(f.read()).decode()
        payload["base64"] = f"data:{mime_type};base64,{b64_data}"

    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}",
    }
    url = f"{DEFAULT_HOST}{endpoint}"
    result = curl_post_json(url, payload, headers, timeout=120)

    if result.returncode != 0:
        error_body = result.stdout or result.stderr
        print(f"API error: {error_body[:500]}", file=sys.stderr)
        sys.exit(1)

    try:
        resp = json.loads(result.stdout)
    except json.JSONDecodeError:
        print(f"Error: Invalid JSON response", file=sys.stderr)
        print(result.stdout[:500], file=sys.stderr)
        sys.exit(1)

    # Extract image URL from response
    image_url = None
    if resp.get("data") and len(resp["data"]) > 0:
        image_url = resp["data"][0].get("url")
    elif resp.get("url"):
        image_url = resp["url"]

    if not image_url:
        print("Error: No image URL in response", file=sys.stderr)
        print(json.dumps(resp, indent=2, ensure_ascii=False), file=sys.stderr)
        sys.exit(1)

    # Download image
    output_path = args.output or f"/tmp/openclaw/newapi-output/image_{int(time.time())}.png"
    Path(output_path).parent.mkdir(parents=True, exist_ok=True)
    cmd = ["curl", "-s", "-S", "-L", "-o", output_path, "--max-time", "300", image_url]
    dl = subprocess.run(cmd, capture_output=True, text=True)
    if dl.returncode != 0:
        print(f"Download failed: {dl.stderr}", file=sys.stderr)
        sys.exit(1)

    print(f"OUTPUT_FILE:{Path(output_path).resolve()}")


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def main():
    parser = argparse.ArgumentParser(
        description="NewAPI Banana image generation client for OpenClaw",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""\
Modes:
  --check                           Check API key
  --list                            List available tasks
  --info TASK                       Show task details
  --task TASK [options]             Execute a generation task

Tasks:
  text-to-image                     Generate image from text
  image-to-image                    Generate image from image

Examples:
  python3 newapi-banana.py --check
  python3 newapi-banana.py --task text-to-image --prompt "a cute dog" --output /tmp/dog.png
  python3 newapi-banana.py --task image-to-image --prompt "change background to night" --image /tmp/input.png
""",
    )

    # Mode flags
    parser.add_argument("--check", action="store_true", help="Check API key")
    parser.add_argument("--list", action="store_true", help="List available tasks")
    parser.add_argument("--info", metavar="TASK", help="Show details for a task")

    # Execution params
    parser.add_argument("--task", "-t", help="Task type")
    parser.add_argument("--prompt", "-p", help="Text prompt")
    parser.add_argument("--image", "-i", help="Input image path")
    parser.add_argument("--model", "-m", help="Model name")
    parser.add_argument("--aspect-ratio", help="Aspect ratio (4:3, 16:9, 9:16, etc.)")
    parser.add_argument("--output", "-o", help="Output file path")
    parser.add_argument("--api-key", "-k", help="API key (optional, resolved from config)")
    parser.add_argument("--host-url", help="API host URL (default: http://nen.baynn.com)")

    args = parser.parse_args()

    if args.check:
        cmd_check(args.api_key, args.host_url)
    elif args.list:
        tasks = [
            "text-to-image - Generate image from text",
            "image-to-image - Generate image from image",
        ]
        print("Available tasks:")
        for task in tasks:
            print(f"  {task}")
    elif args.info:
        print(f"Task info for: {args.info}")
        print("Use --list to see all available tasks")
    elif args.task:
        if args.task in ("text-to-image", "image-to-image"):
            cmd_generate_image(args)
        else:
            print(f"Error: Unknown task: {args.task}", file=sys.stderr)
            print("Use --list to see available tasks", file=sys.stderr)
            sys.exit(1)
    else:
        parser.print_help()
        sys.exit(1)


if __name__ == "__main__":
    main()


path: references/image-generation.md
content: # 图片生成指南

## 支持的模型

### 文生图模型
- **nano-banana** - 标准图片生成模型（推荐）
- **nano-banana-2** - 增强版本
- **nano-banana-fast** - 快速生成模型
- **gemini-3-pro-image-preview** - Gemini Pro 模型

### 图生图模型
- **nano-banana-pro** - 图片编辑模型

## 图片比例选项

- `auto` - 自动（默认）
- `1:1` - 正方形
- `16:9` - 宽屏横屏
- `9:16` - 手机竖屏
- `4:3` - 标准横屏
- `3:4` - 标准竖屏
- `3:2` - 横屏
- `2:3` - 竖屏
- `5:4` - 横屏
- `4:5` - 竖屏
- `21:9` - 超宽屏

## 使用流程

### 文生图

1. **选择模型** - 根据需求选择合适的模型
2. **输入提示词** - 详细描述您想要的图片内容
3. **选择比例** - 根据用途选择合适的长宽比
4. **生成图片** - 等待 AI 处理完成

### 图生图

1. **上传参考图片** - 选择一张参考图片
2. **输入提示词** - 描述您想要的修改或效果
3. **生成图片** - 等待 AI 处理完成

## 提示词技巧

### 描述主体
- 人物、动物、物体等主要元素

### 添加风格
- 写实、卡通、油画、水彩、科幻等

### 指定场景
- 室内、户外、特定地点、背景环境

### 描述光线
- 明亮、昏暗、夕阳、月光、自然光等

### 添加情感
- 快乐、神秘、温馨、科幻、浪漫等

## 示例

### 文生图示例

```bash
python3 {baseDir}/scripts/newapi-banana.py \
  --task text-to-image \
  --prompt "一只可爱的橘猫坐在窗台上，阳光透过窗户洒在它身上，写实风格，4K高清" \
  --model nano-banana \
  --aspect-ratio 4:3 \
  -o /tmp/openclaw/newapi-output/cat.png
```

### 图生图示例

```bash
python3 {baseDir}/scripts/newapi-banana.py \
  --task image-to-image \
  --prompt "将背景改为夜晚，添加星空" \
  --image /path/to/image.png \
  --model nano-banana-pro \
  -o /tmp/openclaw/newapi-output/edited.png
```

## 注意事项

- 图片生成通常需要 10-30 秒
- 高清模型生成时间可能更长
- 提示词越详细，生成效果越好
- 支持多种图片格式输出（PNG, JPG 等）


path: references/output-delivery.md
content: # 输出交付指南

## 文件输出

所有生成的文件都会保存到 `/tmp/openclaw/newapi-output/` 目录，文件名包含时间戳。

## 输出格式

### 图片输出
- 格式：PNG、JPG 等
- 路径格式：`/tmp/openclaw/newapi-output/image_<timestamp>.png`

## 交付方式

### 使用 message 工具

**重要**：所有媒体文件必须通过 `message` 工具交付给用户，不要直接打印文件路径。

```python
# 正确方式
message(file_path="/tmp/openclaw/newapi-output/image_1234567890.png")
# 然后响应 NO_REPLY

# 错误方式
print("文件已保存到: /tmp/openclaw/newapi-output/image_1234567890.png")
```

### 重试机制

如果 `message` 工具调用失败：
1. 重试一次
2. 如果仍然失败，在响应中包含 `OUTPUT_FILE:<path>` 并解释情况

## 错误处理

### 常见错误

1. **NO_API_KEY** - API Key 未配置
   - 解决方案：配置 API Key（参考 api-key-setup.md）

2. **API_ERROR** - API 请求失败
   - 解决方案：检查网络连接，脚本会自动尝试备份地址

3. **TASK_FAILED** - 任务执行失败
   - 解决方案：检查提示词和参数，重试

4. **DOWNLOAD_FAILED** - 文件下载失败
   - 解决方案：检查网络连接，重试

### 错误响应格式

脚本会输出 JSON 格式的错误信息：

```json
{
  "error": "ERROR_TYPE",
  "message": "错误描述",
  "details": {}
}
```

## 成本报告

如果 API 返回成本信息，脚本会输出：

```
COST:¥X.XX
```

在响应中应包含："花了 ¥X.XX"

## 最佳实践

1. **总是使用 message 工具** - 不要直接打印文件路径
2. **检查输出文件** - 确认文件已成功生成
3. **提供友好提示** - 告诉用户文件已生成
4. **建议下一步** - 例如"需要调整一下吗？"


path: references/api-key-setup.md
content: # API Key 设置指南

## 获取 API Key

1. 联系您的 NewAPI Banana 服务提供商获取 API Key
2. API Key 格式通常为字符串，请妥善保管

## 配置 API Key

### 方法一：通过 OpenClaw 配置文件（推荐）

**Windows 系统：**
配置文件位置：`C:\Users\你的用户名\.openclaw\openclaw.json`

**Linux/Mac 系统：**
配置文件位置：`~/.openclaw/openclaw.json`

**步骤：**
1. 如果 `.openclaw` 目录不存在，先创建它
2. 创建或编辑 `openclaw.json` 文件
3. 添加以下配置（如果文件已存在，合并到现有配置中）：

```json
{
  "skills": {
    "entries": {
      "newapi-banana": {
        "apiKey": "your_api_key_here"
      }
    }
  }
}
```

**Windows PowerShell 快速创建：**
```powershell
# 创建目录（如果不存在）
$configDir = "$env:USERPROFILE\.openclaw"
if (-not (Test-Path $configDir)) {
    New-Item -ItemType Directory -Path $configDir -Force
}

# 创建配置文件
$configFile = "$configDir\openclaw.json"
@{
    skills = @{
        entries = @{
            "newapi-banana" = @{
                [REDACTED_SECRET]"
            }
        }
    }
} | ConvertTo-Json -Depth 10 | Set-Content -Path $configFile -Encoding UTF8
```

### 方法二：通过环境变量

**Windows PowerShell：**
```powershell
$env:NEWAPI_API_KEY = "your_api_key_here"
```

**Windows CMD：**
```cmd
set NEWAPI_API_KEY=your_api_key_here
```

**Linux/Mac：**
```bash
export NEWAPI_API_KEY="your_api_key_here"
```

**永久设置环境变量（Windows）：**
1. 右键"此电脑" → "属性"
2. "高级系统设置" → "环境变量"
3. 在"用户变量"中添加：
   - 变量名：`NEWAPI_API_KEY`
   - 变量值：`your_api_key_here`

### 方法三：在对话中直接提供

您可以在对话中直接提供 API Key，系统会临时使用。例如：
- "我的 API Key 是：sk-xxxxx"
- "设置 API Key：your_key_here"

## 验证 API Key

运行以下命令验证 API Key 是否有效：

```bash
python3 {baseDir}/scripts/newapi-banana.py --check
```

如果配置正确，您会看到：

```json
{
  "status": "ready",
  "key_prefix": "xxxx****",
  "host": "http://nen.baynn.com",
  "message": "API key is valid"
}
```

## 故障排除

### API Key 无效

- 检查 API Key 是否正确复制（注意前后空格）
- 确认 API Key 是否已过期
- 联系服务提供商确认账户状态

### 无法连接

- 检查网络连接
- 确认 API 服务是否正常运行
- 脚本会自动尝试多个备份地址


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