@acedatacloud/skills 2026.726.3 → 2026.726.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wordpress/SKILL.md +111 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.726.
|
|
3
|
+
"version": "2026.726.4",
|
|
4
4
|
"description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -9,11 +9,11 @@ when_to_use: |
|
|
|
9
9
|
This skill is for self-hosted WordPress (Application Password auth),
|
|
10
10
|
not WordPress.com.
|
|
11
11
|
connections: [wordpress]
|
|
12
|
-
allowed_tools: [Bash]
|
|
12
|
+
allowed_tools: [Bash, publish_artifact]
|
|
13
13
|
license: Apache-2.0
|
|
14
14
|
metadata:
|
|
15
15
|
author: acedatacloud
|
|
16
|
-
version: "1.
|
|
16
|
+
version: "1.1"
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
Drive the **WordPress REST API** (`/wp-json/wp/v2`) with `curl + jq`.
|
|
@@ -36,27 +36,54 @@ API="$SITE/wp-json/wp/v2"
|
|
|
36
36
|
WP=(curl -sS --fail-with-body -u "$WORDPRESS_USERNAME:$WORDPRESS_APP_PASSWORD")
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
> **Don't add `-L`/`--location` to these calls.** A redirect usually means the
|
|
40
|
+
> site URL is wrong. curl strips the `Authorization` header when a redirect
|
|
41
|
+
> crosses host or scheme (e.g. `http`→`https`, apex→`www`) — exactly the cases
|
|
42
|
+
> that occur here — so the followed request runs **unauthenticated** and can
|
|
43
|
+
> return the site's HTML page with HTTP 200: it looks like success but wrote
|
|
44
|
+
> nothing. Fix the URL instead (see the `/wp-json` gotcha below).
|
|
45
|
+
|
|
46
|
+
|
|
39
47
|
Errors come back as `{"code": "...", "message": "...", "data": {"status": 401}}` —
|
|
40
48
|
show `message` verbatim. Common codes:
|
|
41
49
|
|
|
42
50
|
| HTTP | Meaning | What to tell the user |
|
|
43
51
|
|------|---------|-----------------------|
|
|
44
52
|
| 401 | `incorrect_password` / bad Basic auth | Application Password wrong or revoked → regenerate it and reconnect the WordPress connector |
|
|
53
|
+
| 401 | `rest_not_logged_in` on a plain-`http://` site | Application Passwords are disabled without HTTPS → the user must enable HTTPS |
|
|
45
54
|
| 403 | `rest_cannot_create` / insufficient role | The user's role can't publish; needs Author/Editor/Admin, or Application Passwords are disabled on the site |
|
|
46
55
|
| 404 | `rest_no_route` | REST API disabled or a security plugin blocks `/wp-json` → the user must re-enable it |
|
|
47
56
|
| 400 | `rest_invalid_param` | Bad field (e.g. unknown category id) → fix and retry |
|
|
57
|
+
| 500 | `rest_upload_sideload_error` | `wp-content/uploads` isn't writable by the web server → the user must fix directory permissions |
|
|
58
|
+
|
|
59
|
+
> **`content` is HTML, not Markdown.** Raw Markdown renders literally. `pandoc` is
|
|
60
|
+
> **not installed** — convert with Python's `markdown` package (preinstalled in the
|
|
61
|
+
> sandbox). If the import ever fails, `pip install markdown` first, then:
|
|
62
|
+
>
|
|
63
|
+
> ```bash
|
|
64
|
+
> HTML=$(python3 -c "
|
|
65
|
+
> import sys, markdown
|
|
66
|
+
> print(markdown.markdown(sys.stdin.read(), extensions=['fenced_code','tables']))
|
|
67
|
+
> " <<'MD'
|
|
68
|
+
> ## 标题
|
|
69
|
+
>
|
|
70
|
+
> 正文 **粗体**
|
|
71
|
+
> MD
|
|
72
|
+
> )
|
|
73
|
+
> ```
|
|
48
74
|
|
|
49
|
-
> **`content` is HTML, not Markdown.** Convert Markdown to HTML first
|
|
50
|
-
> (`pandoc -f markdown -t html`, or a simple converter). Raw Markdown renders literally.
|
|
51
75
|
|
|
52
76
|
## Step 0 — verify the connection first
|
|
53
77
|
|
|
54
78
|
```bash
|
|
55
|
-
"${WP[@]}" "$API/users/me" | jq '{id, name, slug, roles
|
|
79
|
+
"${WP[@]}" "$API/users/me?context=edit" | jq '{id, name, slug, roles}'
|
|
56
80
|
```
|
|
57
81
|
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
`context=edit` is required — without it WP omits `roles`/`capabilities` entirely,
|
|
83
|
+
so you cannot tell whether the account may publish. A 200 with `roles` containing
|
|
84
|
+
`administrator`, `editor` or `author` confirms the site URL, username, Application
|
|
85
|
+
Password **and** publish permission. If this fails, stop and surface the error —
|
|
86
|
+
don't attempt writes.
|
|
60
87
|
|
|
61
88
|
## Publish or draft a post
|
|
62
89
|
|
|
@@ -87,6 +114,62 @@ jq -n --arg t "标题" --arg c "<p>正文</p>" --arg e "一句话摘要" \
|
|
|
87
114
|
- Update a post: `POST $API/posts/<id>` with any subset of fields (WP REST uses
|
|
88
115
|
POST, not PUT, for updates).
|
|
89
116
|
- Delete (trash) a post: `"${WP[@]}" -X DELETE "$API/posts/<id>"`.
|
|
117
|
+
- Schedule a post: `{"status":"future","date_gmt":"2030-01-01T00:00:00"}` (UTC,
|
|
118
|
+
no trailing `Z`).
|
|
119
|
+
|
|
120
|
+
## SEO fields that actually matter
|
|
121
|
+
|
|
122
|
+
WordPress core emits `<link rel="canonical">` on its own but ships **no meta
|
|
123
|
+
description tag at all** — that only appears if the site runs an SEO plugin
|
|
124
|
+
(Yoast, Rank Math, SEOPress) or a theme that renders one. Those consume the
|
|
125
|
+
post's `excerpt`, so setting `excerpt` is what makes a good description possible;
|
|
126
|
+
it does nothing on a bare core install. For an SEO post always set:
|
|
127
|
+
|
|
128
|
+
| Field | Why |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `slug` | The permalink. Set it explicitly to a short ASCII keyword phrase — otherwise a CJK title becomes a percent-encoded URL. |
|
|
131
|
+
| `excerpt` | Source for the SEO plugin's meta description and for list-page summaries. One sentence. |
|
|
132
|
+
| `categories` / `tags` | Internal linking + topic clustering. |
|
|
133
|
+
| `featured_media` | Social/OG card image. |
|
|
134
|
+
| media `alt_text` | Image SEO + accessibility. Set it on the media object (below). |
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
jq -n --arg s "claude-api-guide" --argjson c 3 --argjson m 9 \
|
|
139
|
+
'{title:"如何稳定调用 Claude API:完整对接指南",
|
|
140
|
+
slug:$s,
|
|
141
|
+
content:"<h2>小标题</h2><p>正文</p>",
|
|
142
|
+
excerpt:"一句话摘要,用于 meta description。",
|
|
143
|
+
status:"publish", categories:[$c], featured_media:$m}' \
|
|
144
|
+
| "${WP[@]}" -X POST "$API/posts" -H "Content-Type: application/json" -d @- \
|
|
145
|
+
| jq '{id, status, slug, link}'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Before publishing: check for a duplicate
|
|
149
|
+
|
|
150
|
+
**WordPress will NOT reject a duplicate slug — it silently appends `-2`,** so an
|
|
151
|
+
unattended/scheduled run that reposts the same article creates an endless trail of
|
|
152
|
+
near-identical URLs that compete with each other in search. Always pre-check:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
SLUG="claude-api-guide"
|
|
156
|
+
# Fail CLOSED at BOTH steps: a failed request, or a 200 that isn't a JSON array
|
|
157
|
+
# (HTML from a redirect/permalink issue, or an error object), must abort — never
|
|
158
|
+
# fall through and create the duplicate this check exists to prevent.
|
|
159
|
+
if ! LOOKUP=$("${WP[@]}" "$API/posts?slug=$SLUG&status=publish,draft,future&_fields=id,link"); then
|
|
160
|
+
echo "duplicate check failed (request error) — aborting" >&2; exit 1
|
|
161
|
+
fi
|
|
162
|
+
if ! EXISTING=$(printf '%s' "$LOOKUP" | jq -er 'if type=="array" then (.[0].id // "") else error("not a JSON array") end'); then
|
|
163
|
+
echo "duplicate check failed (unexpected response) — aborting" >&2; exit 1
|
|
164
|
+
fi
|
|
165
|
+
if [ -n "$EXISTING" ]; then
|
|
166
|
+
echo "already exists as post $EXISTING — update it instead of creating a new one"
|
|
167
|
+
# update: "${WP[@]}" -X POST "$API/posts/$EXISTING" ...
|
|
168
|
+
fi
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Prefer **updating** the existing post over creating a near-duplicate. This matters
|
|
172
|
+
most in Scheduled Tasks, where nobody is watching the output.
|
|
90
173
|
|
|
91
174
|
## List / read posts
|
|
92
175
|
|
|
@@ -125,22 +208,40 @@ MEDIA_ID=$("${WP[@]}" -X POST "$API/media" \
|
|
|
125
208
|
-H "Content-Type: image/png" \
|
|
126
209
|
--data-binary @"$FILE" | jq -r '.id')
|
|
127
210
|
echo "media id=$MEDIA_ID"
|
|
211
|
+
|
|
212
|
+
# Set alt text (image SEO + accessibility) — a separate call on the media object.
|
|
213
|
+
jq -n --arg a "Claude API 架构图" '{alt_text:$a}' \
|
|
214
|
+
| "${WP[@]}" -X POST "$API/media/$MEDIA_ID" \
|
|
215
|
+
-H "Content-Type: application/json" -d @- | jq '{id, alt_text}'
|
|
216
|
+
|
|
128
217
|
# Attach as the post's featured image:
|
|
129
218
|
# add "featured_media": <MEDIA_ID> to the post body.
|
|
130
219
|
```
|
|
131
220
|
|
|
132
221
|
## Gotchas
|
|
133
222
|
|
|
134
|
-
- **HTTPS
|
|
135
|
-
|
|
223
|
+
- **HTTPS is effectively required for Application Passwords.** WP gates them on
|
|
224
|
+
`wp_is_application_passwords_available()`, which is false on a plain-`http://`
|
|
225
|
+
production site → authenticated calls fail with `rest_not_logged_in` (401). (The
|
|
226
|
+
gate is filterable and is bypassed when `WP_ENVIRONMENT_TYPE` is `local`, so a
|
|
227
|
+
dev box may still work — but any real site the user connects must serve HTTPS.)
|
|
228
|
+
- **`$WORDPRESS_SITE_URL` must exactly match the site's configured address.** If
|
|
229
|
+
it differs (missing/extra `www`, `http` vs `https`), WP answers `/wp-json/...`
|
|
230
|
+
with a **301** to the canonical host. Fix the stored site URL in the connector
|
|
231
|
+
rather than papering over it with `-L`.
|
|
232
|
+
|
|
136
233
|
- **A security plugin / host may block `/wp-json`** (Wordfence, "disable REST
|
|
137
234
|
API" plugins, some managed hosts). Symptom: 404 `rest_no_route` or an HTML
|
|
138
235
|
login page instead of JSON. The user must allow REST API access.
|
|
236
|
+
- **Pretty permalinks may be off.** If `/wp-json/wp/v2/...` returns the site's
|
|
237
|
+
HTML instead of JSON, the site is on plain permalinks — use the always-available
|
|
238
|
+
query form instead: `$SITE/?rest_route=/wp/v2/posts`.
|
|
139
239
|
- **The Application Password contains spaces** (e.g. `abcd efgh ijkl mnop`).
|
|
140
240
|
Keep them — `curl -u` handles the spaces fine; don't strip them.
|
|
141
241
|
- **Never publish silently.** Even if the user says "post it", prefer creating a
|
|
142
242
|
draft and returning the `wp-admin` edit link unless they explicitly asked to
|
|
143
|
-
go live.
|
|
243
|
+
go live. (In an unattended Scheduled Task the user has pre-authorized the run,
|
|
244
|
+
so publishing directly is expected there — but still run the duplicate check.)
|
|
144
245
|
|
|
145
246
|
|
|
146
247
|
## Record the output
|