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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,28 +124,47 @@ python3 toolkit/cli.py themes
**Claude Code**:

```bash
# macOS/Linux
git clone --depth 1 https://github.com/oaker-io/wewrite.git ~/.claude/skills/wewrite
cd ~/.claude/skills/wewrite && bash install.sh

# Windows (PowerShell)
git clone --depth 1 https://github.com/oaker-io/wewrite.git "$env:USERPROFILE\.claude\skills\wewrite"
cd "$env:USERPROFILE\.claude\skills\wewrite"
powershell -ExecutionPolicy Bypass -File install.ps1
```

**OpenClaw**:

```bash
# macOS/Linux
git clone --depth 1 https://github.com/oaker-io/wewrite.git ~/.openclaw/skills/wewrite
cd ~/.openclaw/skills/wewrite && bash install.sh

# Windows (PowerShell)
git clone --depth 1 https://github.com/oaker-io/wewrite.git "$env:USERPROFILE\.openclaw\skills\wewrite"
cd "$env:USERPROFILE\.openclaw\skills\wewrite"
powershell -ExecutionPolicy Bypass -File install.ps1
```

**Codex**(OpenAI Codex CLI):

```bash
# macOS/Linux
git clone --depth 1 https://github.com/oaker-io/wewrite.git ~/.codex/skills/wewrite
cd ~/.codex/skills/wewrite && bash install.sh
python3 scripts/build_codex.py --install # 装自定义 prompt 到 ~/.codex/prompts/

# Windows (PowerShell)
git clone --depth 1 https://github.com/oaker-io/wewrite.git "$env:USERPROFILE\.codex\skills\wewrite"
cd "$env:USERPROFILE\.codex\skills\wewrite"
powershell -ExecutionPolicy Bypass -File install.ps1
python scripts/build_codex.py --install # 装自定义 prompt 到 ~/.codex/prompts/
```

之后在 Codex 里用 `/wewrite 写一篇关于 X 的文章` 触发完整流程。Codex 没有 SKILL.md 自动触发机制,所以通过自定义 prompt 承载;源 `SKILL.md` 更新后重跑 `build_codex.py --install` 同步。详见 [`dist/codex/README.md`](dist/codex/README.md)。

`install.sh` 会在 `.venv` 里创建隔离环境并安装依赖,自动绕过 macOS Homebrew Python 的 PEP 668 限制。skill 运行时会自动使用该 venv,无需手动 `activate`。
`install.sh`(macOS/Linux)或 `install.ps1`(Windows)会在 `.venv` 里创建隔离环境并安装依赖,自动绕过 macOS Homebrew Python 的 PEP 668 限制。skill 运行时会自动使用该 venv,无需手动 `activate`。

> 若你的环境没有 PEP 668 限制,也可以照旧直接 `pip install -r requirements.txt`。

Expand Down
13 changes: 11 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ allowed-tools:

**读取/检查约定**:本文档中 `读取: <路径>` / `检查: <路径>` = **用你环境的文件读取工具真实打开该文件、读完其全部内容,然后再继续本步**。这不是描述性注释——未读取前不得执行依赖该文件的步骤;不同 harness 的文件读取工具名不同,按你环境的对应工具执行。

**Python 解释器约定**:本文档所有 `python3` 命令优先解析为 `{skill_dir}/.venv/bin/python3`(若该文件存在),否则回退系统 `python3`。venv 由 `install.sh` 创建,用于隔离依赖并绕过 macOS Homebrew Python 的 PEP 668 限制。
**Python 解释器约定**:本文档所有 `python3` 命令优先解析为:
- **macOS/Linux**:`{skill_dir}/.venv/bin/python3`(若该文件存在)
- **Windows**:`{skill_dir}\.venv\Scripts\python.exe`(若该文件存在)

否则回退系统 `python3`(或 Windows 上的 `python`)。venv 由 `install.sh`(macOS/Linux)或 `install.ps1`(Windows)创建,用于隔离依赖并绕过 macOS Homebrew Python 的 PEP 668 限制。

**Onboard 例外**:Onboard 是交互式的(需要问用户问题),不受"全自动"约束。Onboard 完成后回到全自动管道。

Expand All @@ -70,14 +74,19 @@ allowed-tools:

```bash
# 优先用 venv 解释器(PEP 668 环境下依赖装在 .venv 里);后续所有 python3 调用同此规则
# macOS/Linux:
PY="{skill_dir}/.venv/bin/python3"; [ -x "$PY" ] || PY="python3"
"$PY" -c "import markdown, bs4, cssutils, requests, yaml, pygments, PIL" 2>&1

# Windows (PowerShell):
# $venvPython = "{skill_dir}\.venv\Scripts\python.exe"
# if (Test-Path $venvPython) { & $venvPython -c "import markdown, bs4, cssutils, requests, yaml, pygments, PIL" } else { python -c "import markdown, bs4, cssutils, requests, yaml, pygments, PIL" }
```

| 检查项 | 通过 | 不通过 |
|--------|------|--------|
| `config.yaml` 存在 | 静默 | 引导创建,或设 `skip_publish = true` |
| Python 依赖 | 静默 | 引导执行 `bash {skill_dir}/install.sh`(自动建 .venv 装依赖,解决 macOS PEP 668 报错);若环境无此限制也可 `pip install -r requirements.txt` |
| Python 依赖 | 静默 | 引导执行 `bash {skill_dir}/install.sh`(macOS/Linux)或 `powershell -ExecutionPolicy Bypass -File {skill_dir}\install.ps1`(Windows)(自动建 .venv 装依赖,解决 macOS PEP 668 报错);若环境无此限制也可 `pip install -r requirements.txt` |
| `wechat.appid` + `secret` | 静默 | 设 `skip_publish = true` |
| `image.api_key` 或 `image.providers` 至少一项有效 | 静默 | 设 `skip_image_gen = true` |
| `references/exemplars/index.yaml` | 静默 | 提示:"范文库为空。如果你有已发布的文章(markdown),可以说**'导入范文'**建立风格库,写出来的文章会更像你。没有也不影响使用。" |
Expand Down
4 changes: 4 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ wechat:
# │ gemini │ https://aistudio.google.com/apikey │ 免费额度较多 │
# │ openrouter │ https://openrouter.ai/settings/keys │ 多模型代理 │
# │ replicate │ https://replicate.com/account/api-tokens │ 开源模型丰富 │
# │ agnes │ https://agnes-ai.com/ │ 新模型,免费额度 │
# └─────────────────┴────────────────────────────────────────────────┴────────────────────┘
#
# 支持两种配置方式:
Expand Down Expand Up @@ -65,6 +66,9 @@ image:
# - provider: replicate
# api_key: "r8_..."
# # model: "google/nano-banana-pro"
# - provider: agnes
# api_key: "your_agnes_key"
# # model: "agnes-image-2.1-flash"

# 默认排版主题
theme: "professional-clean"
59 changes: 59 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# WeWrite 依赖安装脚本 (Windows PowerShell 版本)
# 用法:powershell -ExecutionPolicy Bypass -File install.ps1

$ErrorActionPreference = "Stop"
Set-Location $PSScriptRoot

$PYTHON = "python"
if ($env:PYTHON) { $PYTHON = $env:PYTHON }

if (-not (Get-Command $PYTHON -ErrorAction SilentlyContinue)) {
Write-Host "找不到 $PYTHON,请先安装 Python 3.11+" -ForegroundColor Red
exit 1
}

$pythonVersion = & $PYTHON --version 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "无法获取 Python 版本" -ForegroundColor Red
exit 1
}

$versionMatch = [regex]::Match($pythonVersion, "Python (\d+)\.(\d+)")
if ($versionMatch.Success) {
$major = [int]$versionMatch.Groups[1].Value
$minor = [int]$versionMatch.Groups[2].Value
if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 11)) {
Write-Host "Python 版本过低:$pythonVersion,需要 Python 3.11+" -ForegroundColor Red
exit 1
}
}

if (-not (Test-Path ".venv")) {
Write-Host "创建虚拟环境 .venv ..." -ForegroundColor Cyan
& $PYTHON -m venv .venv
if ($LASTEXITCODE -ne 0) {
Write-Host "创建虚拟环境失败" -ForegroundColor Red
exit 1
}
} else {
Write-Host "复用已有的 .venv" -ForegroundColor Cyan
}

Write-Host "安装依赖到 .venv ..." -ForegroundColor Cyan
$venvPython = ".venv\Scripts\python.exe"

& $venvPython -m pip install --upgrade pip --quiet
if ($LASTEXITCODE -ne 0) {
Write-Host "升级 pip 失败" -ForegroundColor Red
exit 1
}

& $venvPython -m pip install -r requirements.txt
if ($LASTEXITCODE -ne 0) {
Write-Host "安装依赖失败" -ForegroundColor Red
exit 1
}

Write-Host ""
Write-Host "完成!依赖已装入 $PWD\.venv" -ForegroundColor Green
Write-Host "无需手动 activate,skill 运行时会自动使用 .venv\Scripts\python.exe" -ForegroundColor Green
42 changes: 42 additions & 0 deletions toolkit/image_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- azure_openai — Azure-hosted DALL-E
- openrouter — multi-model proxy
- jimeng (ByteDance) — good for Chinese prompts
- agnes (Sapiens AI) — agnes-image-2.1-flash, free credits available
- Custom providers via ImageProvider base class

Usage as CLI:
Expand Down Expand Up @@ -74,21 +75,25 @@ def _load_config() -> dict:
"doubao": "2952x1256", "openai": _DEFAULT, "gemini": _DEFAULT,
"dashscope": _DEFAULT, "minimax": _DEFAULT, "replicate": _DEFAULT,
"azure_openai": _DEFAULT, "openrouter": _DEFAULT, "jimeng": _DEFAULT,
"agnes": _DEFAULT,
},
"article": {
"doubao": "2560x1440", "openai": _DEFAULT, "gemini": _DEFAULT,
"dashscope": _DEFAULT, "minimax": _DEFAULT, "replicate": _DEFAULT,
"azure_openai": _DEFAULT, "openrouter": _DEFAULT, "jimeng": _DEFAULT,
"agnes": _DEFAULT,
},
"vertical": {
"doubao": "1088x2560", "openai": _DEFAULT_V, "gemini": _DEFAULT_V,
"dashscope": _DEFAULT_V, "minimax": _DEFAULT_V, "replicate": _DEFAULT_V,
"azure_openai": _DEFAULT_V, "openrouter": _DEFAULT_V, "jimeng": _DEFAULT_V,
"agnes": _DEFAULT_V,
},
"square": {
"doubao": "2048x2048", "openai": _DEFAULT_SQ, "gemini": _DEFAULT_SQ,
"dashscope": _DEFAULT_SQ, "minimax": _DEFAULT_SQ, "replicate": _DEFAULT_SQ,
"azure_openai": _DEFAULT_SQ, "openrouter": _DEFAULT_SQ, "jimeng": _DEFAULT_SQ,
"agnes": _DEFAULT_SQ,
},
}

Expand Down Expand Up @@ -612,6 +617,42 @@ def generate(self, prompt: str, size: str) -> bytes:
raise ValueError("Jimeng polling timeout")


class AgnesProvider(ImageProvider):
"""Agnes Image 2.1 Flash via Sapiens AI APIhub.

NOTE: Agnes requires `response_format` to be placed inside `extra_body`,
not at the top level of the request body.
"""

provider_key = "agnes"

def __init__(self, api_key: str, model: str = "agnes-image-2.1-flash",
base_url: str = "https://apihub.agnes-ai.com", **_kw):
self._api_key = api_key
self._model = model
self._base_url = base_url.rstrip("/")

def generate(self, prompt: str, size: str) -> bytes:
resp = requests.post(
f"{self._base_url}/v1/images/generations",
headers={"Content-Type": "application/json",
"Authorization": f"Bearer {self._api_key}"},
json={"model": self._model, "prompt": prompt, "size": size,
"extra_body": {"response_format": "url"}},
timeout=120,
)
data = resp.json()
if resp.status_code != 200:
err = data.get("error", {})
msg = err.get("message") if isinstance(err, dict) else str(err)
raise ValueError(f"Agnes error ({resp.status_code}): "
f"{msg or str(data)}")
url = data.get("data", [{}])[0].get("url")
if not url:
raise ValueError(f"No image URL: {data}")
return _download_image(url)


# --- Provider registry ---

PROVIDERS = {
Expand All @@ -624,6 +665,7 @@ def generate(self, prompt: str, size: str) -> bytes:
"azure_openai": AzureOpenAIProvider,
"openrouter": OpenRouterProvider,
"jimeng": JimengProvider,
"agnes": AgnesProvider,
}


Expand Down