关于 wordpress
通过 `curl` 和 `jq` 调用 WordPress REST API 管理内容的 skill。它涵盖连接检查、文章与媒体操作,并提供凭据、重复文章和发布安全措施。
主要功能
- 01验证站点认证与用户角色
- 02创建、更新、定时发布和列出文章
- 03管理分类、标签与媒体
- 04发布前检查重复文章
- 05处理 HTML 内容与 SEO 字段
适用场景
- 01创建草稿并返回编辑链接
- 02更新或定时发布现有文章
- 03上传特色图片并设置 SEO 字段
- 04查看文章和分类标签 ID
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ acedatacloud/ skills --skill wordpress安装后在 Claude Code 中输入/wordpress确认已加载。
详细介绍
使用 curl + jq 操作 WordPress REST API(/wp-json/wp/v2)。
用户自托管的 WordPress 凭据会作为环境变量注入:
$WORDPRESS_SITE_URL— 站点根地址,例如https://blog.example.com$WORDPRESS_USERNAME— WordPress 登录用户名$WORDPRESS_APP_PASSWORD— 应用程序密码(WP 5.6+ 核心功能),不是登录密码。请将其视为机密——切勿记录或回显。
通过 HTTPS 使用 HTTP Basic(username:app_password)进行身份验证。先设置一次可复用的基础配置,之后每次调用都复用它:
# Normalize the site URL (strip a trailing slash) and build the API base.
SITE="${WORDPRESS_SITE_URL%/}"
API="$SITE/wp-json/wp/v2"
# -u sends HTTP Basic auth; --fail-with-body surfaces the JSON error body on 4xx/5xx.
WP=(curl -sS --fail-with-body -u "$WORDPRESS_USERNAME:$WORDPRESS_APP_PASSWORD")
不要在这些调用中添加
-L/--location。 重定向通常意味着站点 URL 有误。若重定向跨越主机或协议(例如http→https、根域名→www),curl 会移除Authorization标头——这里恰好就可能出现这种情况——因此后续请求会以未通过身份验证的状态执行,并可能返回站点的 HTML 页面和 HTTP 200:看起来像是成功了,实际上什么也没写入。请改正 URL(参见下方/wp-json的注意事项)。
错误会以 {"code": "...", "message": "...", "data": {"status": 401}} 的形式返回——原样显示 message。常见代码:
| HTTP | 含义 | 告知用户的内容 |
|---|---|---|
| 401 | incorrect_password / Basic 身份验证错误 | 应用程序密码错误或已撤销 → 重新生成密码并重新连接 WordPress 连接器 |
| 401 | 纯 http:// 站点上的 rest_not_logged_in | 未启用 HTTPS 时会禁用应用程序密码 → 用户必须启用 HTTPS |
| 403 | rest_cannot_create / 权限不足 | 用户角色无法发布;需要 Author/Editor/Admin,或者站点已禁用应用程序密码 |
| 404 | rest_no_route | REST API 已禁用,或安全插件阻止了 /wp-json → 用户必须重新启用它 |
| 400 | rest_invalid_param | 字段无效(例如未知的分类 ID)→ 修正后重试 |
| 500 | rest_upload_sideload_error | Web 服务器无法写入 wp-content/uploads → 用户必须修正目录权限 |
content是 HTML,不是 Markdown。 原始 Markdown 会原样显示。未安装pandoc——请使用 Python 的markdown包进行转换(沙盒中已预装)。如果导入失败,请先运行pip install markdown,然后:HTML=$(python3 -c " import sys, markdown print(markdown.markdown(sys.stdin.read(), extensions=['fenced_code','tables'])) " <<'MD' ## 标题 正文 **粗体** MD )
步骤 0 — 先验证连接
"${WP[@]}" "$API/users/me?context=edit" | jq '{id, name, slug, roles}'
必须使用 context=edit——否则 WP 会完全省略 roles/capabilities,因此无法判断账户是否有权发布。若返回 200,且 roles 包含 administrator、editor 或 author,则表示站点 URL、用户名、应用程序密码以及发布权限均已确认。如果此步骤失败,请停止并展示错误——不要尝试写入。
发布文章或保存为草稿
发布会公开显示且很难撤销——使用 status=publish 前,请先与用户确认。 默认使用 status=draft,并提供编辑链接。
jq -n --arg t "国内如何稳定调用 Claude API" \
--arg c "<p>正文 HTML……</p>" \
--arg s "draft" \
'{title:$t, content:$c, status:$s}' \
| "${WP[@]}" -X POST "$API/posts" \
-H "Content-Type: application/json" -d @- \
| jq '{id, status, link, edit: "\(env.WORDPRESS_SITE_URL)/wp-admin/post.php?action=edit&post=\(.id)"}'
添加分类 / 标签 / 摘要(ID 来自下方的端点):
jq -n --arg t "标题" --arg c "<p>正文</p>" --arg e "一句话摘要" \
'{title:$t, content:$c, excerpt:$e, status:"draft",
categories:[5], tags:[12,34]}' \
| "${WP[@]}" -X POST "$API/posts" -H "Content-Type: application/json" -d @- \
| jq '{id, status, link}'
- 发布现有草稿:请求体为
{"status":"publish"},调用POST $API/posts/<id>。 - 更新文章:调用
POST $API/posts/<id>,传入任意字段子集(WP REST 更新使用 POST,而不是 PUT)。 - 删除(移至回收站)文章:
"${WP[@]}" -X DELETE "$API/posts/<id>"。 - 定时发布文章:
{"status":"future","date_gmt":"2030-01-01T00:00:00"}(UTC,不带结尾的Z)。
真正重要的 SEO 字段
WordPress 核心会自行输出 <link rel="canonical">,但完全不会提供 meta description 标签——只有网站运行了 SEO 插件(Yoast、Rank Math、SEOPress)或会渲染此标签的主题时,才会出现该标签。这些插件和主题会使用文章的 excerpt,因此设置 excerpt 才能生成优质描述;在未安装插件或主题的基础 WordPress 中,设置它不会起任何作用。发布 SEO 文章时,务必设置:
| 字段 | 原因 |
|---|---|
slug | 固定链接。请明确设置为简短的 ASCII 关键词短语——否则,包含 CJK 字符的标题会变成百分号编码的 URL。 |
excerpt | SEO 插件的 meta description 和列表页摘要的来源。请写一个句子。 |
categories / tags | 用于内部链接和主题聚类。 |
featured_media | 社交媒体/OG 卡片图片。 |
媒体的 alt_text | 图片 SEO 和无障碍访问。请在媒体对象上设置(见下文)。 |
jq -n --arg s "claude-api-guide" --argjson c 3 --argjson m 9 \
'{title:"如何稳定调用 Claude API:完整对接指南",
slug:$s,
content:"<h2>小标题</h2><p>正文</p>",
excerpt:"一句话摘要,用于 meta description。",
status:"publish", categories:[$c], featured_media:$m}' \
| "${WP[@]}" -X POST "$API/posts" -H "Content-Type: application/json" -d @- \
| jq '{id, status, slug, link}'
发布前:检查是否有重复内容
**WordPress 不会拒绝重复的 slug——而是会静默追加 -2,**因此,无人值守或定时运行时,如果重复发布同一篇文章,就会不断产生彼此竞争搜索排名的近似 URL。请务必预先检查:
SLUG="claude-api-guide"
# Fail CLOSED at BOTH steps: a failed request, or a 200 that isn't a JSON array
# (HTML from a redirect/permalink issue, or an error object), must abort — never
# fall through and create the duplicate this check exists to prevent.
if ! LOOKUP=$("${WP[@]}" "$API/posts?slug=$SLUG&status=publish,draft,future&_fields=id,link"); then
echo "duplicate check failed (request error) — aborting" >&2; exit 1
fi
if ! EXISTING=$(printf '%s' "$LOOKUP" | jq -er 'if type=="array" then (.[0].id // "") else error("not a JSON array") end'); then
echo "duplicate check failed (unexpected response) — aborting" >&2; exit 1
fi
if [ -n "$EXISTING" ]; then
echo "already exists as post $EXISTING — update it instead of creating a new one"
# update: "${WP[@]}" -X POST "$API/posts/$EXISTING" ...
fi
优先更新现有文章,而不是创建近似重复内容。这一点在定时任务中尤为重要,因为届时没有人查看输出。
列出/读取文章
"${WP[@]}" "$API/posts?per_page=10&status=publish,draft&_fields=id,title,status,link,date" \
| jq '.[] | {id, title: .title.rendered, status, link, date}'
使用 &page=2 分页;总页数位于 X-WP-TotalPages 响应头中(添加 -D - 可查看响应头)。
分类和标签(获取或创建 ID)
# List existing
"${WP[@]}" "$API/categories?per_page=100&_fields=id,name,slug" | jq '.[] | {id, name}'
"${WP[@]}" "$API/tags?per_page=100&_fields=id,name,slug" | jq '.[] | {id, name}'
# Create one (returns its id)
jq -n --arg n "AI 教程" '{name:$n}' \
| "${WP[@]}" -X POST "$API/categories" -H "Content-Type: application/json" -d @- \
| jq '{id, name}'
如果要创建的术语已存在,会返回
{"code":"term_exists", ... "data":{"status":400,"term_id":<id>}}——请复用
.data.term_id,不要因此失败。
上传媒体(特色图片/正文图片)
FILE="./cover.png"
NAME="$(basename "$FILE")"
MEDIA_ID=$("${WP[@]}" -X POST "$API/media" \
-H "Content-Disposition: attachment; filename=\"$NAME\"" \
-H "Content-Type: image/png" \
--data-binary @"$FILE" | jq -r '.id')
echo "media id=$MEDIA_ID"
# Set alt text (image SEO + accessibility) — a separate call on the media object.
jq -n --arg a "Claude API 架构图" '{alt_text:$a}' \
| "${WP[@]}" -X POST "$API/media/$MEDIA_ID" \
-H "Content-Type: application/json" -d @- | jq '{id, alt_text}'
# Attach as the post's featured image:
# add "featured_media": <MEDIA_ID> to the post body.
注意事项
-
Application Passwords 实际上必须使用 HTTPS。 WP 通过
wp_is_application_passwords_available()控制其可用性;在使用纯http://的生产网站上,该函数会返回 false → 已认证的调用会因rest_not_logged_in(401) 而失败。(此限制可通过筛选器配置;当WP_ENVIRONMENT_TYPE为local时也会绕过,因此开发环境可能仍然可用——但用户连接的任何实际网站都必须支持 HTTPS。) -
$WORDPRESS_SITE_URL必须与网站配置的地址完全一致。 如果两者不同(缺少或多了www,或者http与https不一致),WP 会将/wp-json/...请求重定向(301)到规范主机。请修正连接器中保存的网站 URL,不要用-L来掩盖问题。 -
安全插件/主机可能会屏蔽
/wp-json(Wordfence、“disable REST API” 插件、某些托管主机)。症状包括:返回 404rest_no_route,或返回 HTML 登录页面而不是 JSON。用户必须允许访问 REST API。 -
可能未启用固定链接美化。 如果
/wp-json/wp/v2/...返回的是网站 HTML 而不是 JSON,说明网站使用的是普通固定链接——请改用始终可用的查询形式:$SITE/?rest_route=/wp/v2/posts。 -
Application Password 中包含空格(例如
abcd efgh ijkl mnop)。 请保留空格——curl -u可以正常处理;不要删除空格。 -
绝不要静默发布。 即使用户说“发出去”,也应优先创建草稿并返回
wp-admin编辑链接,除非用户明确要求正式发布。(在无人值守的定时任务中,用户已预先授权执行,因此预期会直接发布——但仍须检查是否有重复内容。)
记录输出
成功发布并获得实际结果 URL 后,请调用内置
publish_artifact 工具一次,以便用户在我的输出中跟踪此交付内容:
publish_artifact(kind="article", channel="wordpress", title="<title>", url="<the REAL returned URL>", status="delivered")
请使用实际返回的 URL——绝不要伪造。每个已发布项目调用一次,且仅在确认交付后调用;如果发布失败,请跳过(或使用 status="failed")。
请参阅 _shared/artifacts.md。