最近天天都在学习claude code,Skills == 工具箱(技能库)

我们可以把skills看成是一个个的小程序,这些程序有入口,有参数,有限制。

那通过一篇md文件,来引导claude来智能的传参调用你的程序,这样技能箱就越来越丰富。

一、我们首先准备这个可执行 python 脚本

这个是通过CLIProxyAPI的反代,把antigravity的gemini3模型给抽出来,然后生图

看最下面,有一堆参数

#!/usr/bin/env python3
# ============================================================
# Configuration - Edit these values before use
# 配置项 - 使用前请修改以下值
# ============================================================
API_BASE_URL = "http://127.0.0.1:8317"  # API server address / API 服务器地址
API_KEY = "key"                          # Your API key / 你的 API 密钥
MODEL = "gemini-3-pro-image-preview"     # Model name / 模型名称
# ============================================================

import argparse
import base64
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from urllib import request
from mimetypes import guess_type

try:
    from google import genai
    from google.genai import types
except ImportError:
    genai = None
    types = None

VALID_ASPECT_RATIOS = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]
VALID_RESOLUTIONS = ["1K", "2K", "4K"]


def _resolve_output_dir(output_dir: str, mkdir: bool) -> str:
    if os.path.isdir(output_dir):
        return output_dir
    if mkdir:
        os.makedirs(output_dir, exist_ok=True)
        return output_dir
    print(f"Warning: Output directory not found: {output_dir}. Saving to current directory.")
    return "."


def _build_filename(output_name: str | None, timestamp: str, index: int, default_ext: str) -> str:
    if output_name:
        root, ext = os.path.splitext(output_name)
        if not ext:
            ext = default_ext
        suffix = f"_{index}" if index > 0 else ""
        return f"{root}{suffix}{ext}"
    return f"generated_{timestamp}_{index}{default_ext}"


def _load_image_inline(image_path: str) -> dict:
    mime_type, _ = guess_type(image_path)
    if mime_type is None:
        mime_type = "image/png"
    data = Path(image_path).read_bytes()
    return {"inlineData": {"mimeType": mime_type, "data": base64.b64encode(data).decode("ascii")}}


def _post_json(url: str, body: dict) -> dict:
    data = json.dumps(body).encode("utf-8")
    req = request.Request(
        url,
        data=data,
        headers={"Content-Type": "application/json", "x-goog-api-key": API_KEY},
    )
    resp = request.urlopen(req).read()
    return json.loads(resp)


def _save_images(parts, output_dir: str, select: str, output_name: str | None, mkdir: bool) -> list[str]:
    imgs = [p["inlineData"] for p in parts if "inlineData" in p]
    if not imgs:
        return []

    if select == "first":
        imgs = imgs[:1]
    elif select == "last":
        imgs = imgs[-1:]
    elif select == "max_res":
        imgs = imgs[-1:]

    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    output_dir = _resolve_output_dir(output_dir, mkdir)
    saved = []
    for i, img in enumerate(imgs):
        data = base64.b64decode(img["data"])
        ext = "jpg" if img.get("mimeType", "").endswith("jpeg") else "png"
        filename = _build_filename(output_name, ts, i, f".{ext}")
        path = Path(output_dir) / filename
        path.write_bytes(data)
        saved.append(str(path))
    return saved


def _sdk_generate(prompt: str, output_dir: str, aspect_ratio: str, resolution: str, input_image: str | None,
                  output_name: str | None, mkdir: bool, select: str) -> list[str]:
    if genai is None or types is None:
        print("Error: google-genai is not installed.")
        return []

    client_kwargs = {"api_key": API_KEY}
    if API_BASE_URL:
        client_kwargs["http_options"] = {"base_url": API_BASE_URL}
    client = genai.Client(**client_kwargs)

    if input_image:
        if not Path(input_image).exists():
            print(f"Error: Input image not found: {input_image}")
            return []
        with open(input_image, "rb") as f:
            image_bytes = f.read()
        mime_type, _ = guess_type(input_image)
        if mime_type is None:
            mime_type = "image/png"
        image_part = types.Part.from_bytes(data=image_bytes, mime_type=mime_type)
        contents = [prompt, image_part]
    else:
        contents = prompt

    try:
        response = client.models.generate_content(
            model=MODEL,
            contents=contents,
            config=types.GenerateContentConfig(
                image_config=types.ImageConfig(
                    aspectRatio=aspect_ratio,
                    imageSize=resolution,
                )
            ),
        )
    except Exception as exc:
        print(f"API Error: {exc}")
        return []

    saved = []
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    output_dir = _resolve_output_dir(output_dir, mkdir)
    image_parts = []
    for part in response.parts:
        if part.text is not None:
            print(f"Response text: {part.text}")
        elif image := part.as_image():
            image_parts.append(image)

    if not image_parts:
        return []

    if select == "first":
        image_parts = image_parts[:1]
    elif select == "last":
        image_parts = image_parts[-1:]
    elif select == "max_res":
        image_parts = [max(image_parts, key=lambda x: len(x.image_bytes))]

    for i, image in enumerate(image_parts):
        filename = _build_filename(output_name, ts, i, ".png")
        path = Path(output_dir) / filename
        image.save(path)
        saved.append(str(path))
    return saved


def _http_generate(prompt: str, output_dir: str, aspect_ratio: str, resolution: str, input_image: str | None,
                   select: str, output_name: str | None, mkdir: bool) -> list[str]:
    if input_image and not Path(input_image).exists():
        print(f"Error: Input image not found: {input_image}")
        return []

    url = f"{API_BASE_URL.rstrip('/')}/v1beta/models/{MODEL}:generateContent"
    parts = [{"text": prompt}]
    if input_image:
        parts.append(_load_image_inline(input_image))

    body = {
        "contents": [{"parts": parts}],
        "generationConfig": {
            "responseModalities": ["TEXT", "IMAGE"],
            "imageConfig": {"aspectRatio": aspect_ratio, "imageSize": resolution},
        },
    }

    try:
        resp = _post_json(url, body)
    except Exception as exc:
        print(f"API Error: {exc}")
        return []

    if "candidates" not in resp:
        print(f"Unexpected response: {resp}")
        return []

    parts_out = resp["candidates"][0]["content"]["parts"]
    return _save_images(parts_out, output_dir, select, output_name, mkdir)


def main():
    parser = argparse.ArgumentParser(description="Generate images via SDK or HTTP")
    parser.add_argument("prompt", nargs="*", help="Image description prompt")
    parser.add_argument("--mode", default="sdk", choices=["sdk", "http"])
    parser.add_argument("--aspect-ratio", "-a", default="16:9", choices=VALID_ASPECT_RATIOS)
    parser.add_argument("--resolution", "-r", default="4K", choices=VALID_RESOLUTIONS)
    parser.add_argument("--output-dir", "-o", default=".")
    parser.add_argument("--output-name", default=None)
    parser.add_argument("--mkdir", action="store_true")
    parser.add_argument("--input-image", "-i", default=None)
    parser.add_argument("--select", default="all", choices=["all", "first", "last", "max_res"])

    args = parser.parse_args()
    if not args.prompt:
        parser.print_help()
        sys.exit(1)

    prompt = " ".join(args.prompt)

    if args.mode == "sdk":
        saved = _sdk_generate(
            prompt=prompt,
            output_dir=args.output_dir,
            aspect_ratio=args.aspect_ratio,
            resolution=args.resolution,
            input_image=args.input_image,
            output_name=args.output_name,
            mkdir=args.mkdir,
            select=args.select,
        )
    else:
        saved = _http_generate(
            prompt=prompt,
            output_dir=args.output_dir,
            aspect_ratio=args.aspect_ratio,
            resolution=args.resolution,
            input_image=args.input_image,
            select=args.select,
            output_name=args.output_name,
            mkdir=args.mkdir,
        )

    if saved:
        print(f"Generated {len(saved)} image(s):")
        for f in saved:
            print(f"  - {Path(f).resolve()}")
    else:
        print("No images were generated.")
        sys.exit(1)


if __name__ == "__main__":
    main()

基本用法:

# Python 3.10+
# google-genai 包(SDK 模式需要)

# SDK 模式(默认)
python ~/.claude/skills/image-generator-hybrid/scripts/generate.py "山间日落" -r 4K -a 16:9

# HTTP 模式
python ~/.claude/skills/image-generator-hybrid/scripts/generate.py "山间日落" --mode http -r 4K

image-20251231132255154

使用示例:

# 生成 4K 竖版图片
python ~/.claude/skills/image-generator-hybrid/scripts/generate.py "人像照片" -r 4K -a 9:16

# 以图生图
python ~/.claude/skills/image-generator-hybrid/scripts/generate.py "转换为赛博朋克风格" -i input.jpg

# 仅保存最高分辨率
python ~/.claude/skills/image-generator-hybrid/scripts/generate.py "风景" --select max_res

# 自定义输出位置
python ~/.claude/skills/image-generator-hybrid/scripts/generate.py "猫咪" -o ./images --mkdir --output-name cat

二、准备claude/skills目录结构

在 ~/.claude/新建skills/gen-image目录,在gen-image下建scripts目录,把python文件放进去

image-20251231133443102

三、准备SKILL.md文件

---
name: gen-image
description: 同时支持 SDK 与 HTTP 两种方式生成图片,可按需切换保存策略。
allowed-tools: Bash(python:*)
---

# Image Generator (Hybrid) Skill

一个脚本同时支持 SDK 与 HTTP 直连两种模式。

## 配置

- **API 端点**: `http://127.0.0.1:8317/v1beta/models/gemini-3-pro-image-preview:generateContent`
- **模型**: `gemini-3-pro-image-preview`
- **API Key**: `key`

可通过环境变量覆盖默认配置:
- `IMAGE_API_KEY`: API 密钥
- `IMAGE_MODEL`: 使用的模型名称
- `IMAGE_API_BASE_URL`: API 基础地址

## 参数

- `--mode`: `sdk|http`(默认 `sdk`- `-r/--resolution`: `1K|2K|4K`
- `-a/--aspect-ratio`: `1:1|2:3|3:2|3:4|4:3|4:5|5:4|9:16|16:9|21:9`
- `-i/--input-image`: 以图生图输入图片路径
- `--select`: `all|first|last|max_res`
  - `max_res` 会选择返回中“最大体积”的那张(通常是 4K)
- `-o/--output-dir`: 输出目录(默认当前目录)
- `--output-name`: 指定文件名(可选;若返回多张,自动追加 `_1/_2`- `--mkdir`: 当输出目录不存在时创建(默认 false);否则回退到当前目录

## 使用

```bash
# SDK 模式(默认)
python ~/.claude/skills/gen-image/scripts/generate.py "描述" -r 4K -a 16:9

# HTTP 模式(保存全部返回图片)
python ~/.claude/skills/gen-image/scripts/generate.py "描述" --mode http -r 4K -a 1:1 --select all

# 仅保存最大尺寸
python ~/.claude/skills/gen-image/scripts/generate.py "描述" --mode http -r 4K -a 1:1 --select max_res
```

看明白了吧,这个markdown 描述了这个技能的情况,配置、参数、使用

最厉害的是claude会根据这个markdown文件来自动调用这个脚本,自己匹配合适的参数,这才是最厉害的地方

那每个SKILL都是一个md文件,给个模板:

# 技能名称

简单说明这个技能是干嘛用的,一般在什么情况下会用到。

## 什么时候用

- 场景1:比如要上线新版本时
- 场景2:比如遇到某个特定错误时

## 具体步骤

1. 第一步要做什么(附上具体的命令)
2. 第二步...
3. 依次往下

## 怎么确认成功了

做完之后,通过哪些方法可以验证操作是成功的。

## 可能遇到的问题

- 问题1:如果遇到...,可以试试...
- 问题2:有时候会...,这时候应该...

四、配置Claude

在CLAUDE.md中添加

## 生图 生成图片
   使用技能 ~/.claude/skills/gen-image

然后就可以在claude里面使用了

生图,4K,1:1,随机内容,仅保留最大分辨率,输出在当前目录,文件名:001.png 

五、发散思维

这个是用的gemini的模型,那改改程序,用前面文章提供的接口:

魔搭、模力方舟、豆包免费文生图

就可以随便生图了