@acedatacloud/skills 2026.703.5 → 2026.703.7
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/hashnode/SKILL.md +147 -0
- package/skills/wordpress/SKILL.md +143 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.703.
|
|
3
|
+
"version": "2026.703.7",
|
|
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",
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hashnode
|
|
3
|
+
description: Publish, update and read blog posts on Hashnode via its GraphQL API. Use when the user mentions Hashnode, publishing a blog post to Hashnode, cross-posting an article to their Hashnode blog, saving a Hashnode draft, updating a published Hashnode post, or listing their Hashnode publications and posts.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to publish a Markdown article to their
|
|
6
|
+
Hashnode blog, save it as a draft, update a previously published
|
|
7
|
+
post, or list / inspect their own Hashnode publications and posts.
|
|
8
|
+
The connector stores a Hashnode Personal Access Token with full
|
|
9
|
+
account access — confirm before publishing publicly (you can save a
|
|
10
|
+
draft first with the createDraft mutation).
|
|
11
|
+
connections: [hashnode]
|
|
12
|
+
allowed_tools: [Bash]
|
|
13
|
+
license: Apache-2.0
|
|
14
|
+
metadata:
|
|
15
|
+
author: acedatacloud
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
Call the **Hashnode Public GraphQL API** with `curl + jq`. The user's Personal
|
|
20
|
+
Access Token is in `$HASHNODE_TOKEN`. Single endpoint: `https://gql.hashnode.com`
|
|
21
|
+
(always `POST` a JSON body `{query, variables}`).
|
|
22
|
+
|
|
23
|
+
Every request needs these headers:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Authorization: $HASHNODE_TOKEN # raw token — NO "Bearer " prefix
|
|
27
|
+
Content-Type: application/json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
GraphQL always returns HTTP `200`; real failures live in the JSON `errors`
|
|
31
|
+
array — always inspect it and show it verbatim. An `errors` entry mentioning
|
|
32
|
+
`Unauthorized` / `not authenticated` means the token is invalid → the user must
|
|
33
|
+
re-connect the Hashnode connector.
|
|
34
|
+
|
|
35
|
+
Helper — send a query/mutation (`$1` = query string, `$2` = variables JSON):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gql() {
|
|
39
|
+
jq -n --arg q "$1" --argjson v "${2:-null}" '{query:$q, variables:$v}' \
|
|
40
|
+
| curl -sS -X POST https://gql.hashnode.com \
|
|
41
|
+
-H "Authorization: $HASHNODE_TOKEN" \
|
|
42
|
+
-H "Content-Type: application/json" \
|
|
43
|
+
-d @-
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Always start by confirming the token and finding the publication id
|
|
48
|
+
|
|
49
|
+
`publishPost` / `createDraft` need a `publicationId`. Fetch the account and its
|
|
50
|
+
publications first (a user can have several blogs):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
gql 'query { me { id username publications(first: 10) { edges { node { id title url } } } } }' \
|
|
54
|
+
| jq '{me: .data.me.username, publications: [.data.me.publications.edges[].node | {id, title, url}], errors: .errors}'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Pick the target blog's `id` (that is the `publicationId`). If the user has
|
|
58
|
+
exactly one publication, use it; otherwise ask which blog to post to.
|
|
59
|
+
|
|
60
|
+
## Publish a post
|
|
61
|
+
|
|
62
|
+
**Confirm with the user before publishing publicly.** If they are not sure, save
|
|
63
|
+
a draft first (see below). `tags` is required — supply 1–5 tags, each as
|
|
64
|
+
`{name, slug}` (slug lowercase, no spaces).
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
PUB_ID="PUBLICATION_ID" # from the me query above
|
|
68
|
+
TITLE="My title"
|
|
69
|
+
BODY_MD="$(cat article.md)" # full Markdown body
|
|
70
|
+
|
|
71
|
+
VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
|
|
72
|
+
input: {
|
|
73
|
+
publicationId: $p,
|
|
74
|
+
title: $t,
|
|
75
|
+
contentMarkdown: $b,
|
|
76
|
+
tags: [{name:"AI", slug:"ai"}, {name:"Web Development", slug:"web-development"}]
|
|
77
|
+
}
|
|
78
|
+
}')
|
|
79
|
+
|
|
80
|
+
gql 'mutation PublishPost($input: PublishPostInput!) {
|
|
81
|
+
publishPost(input: $input) { post { id slug url title } }
|
|
82
|
+
}' "$VARS" | jq '{post: .data.publishPost.post, errors: .errors}'
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Useful optional `input` fields:
|
|
86
|
+
|
|
87
|
+
- `subtitle` — post subtitle.
|
|
88
|
+
- `slug` — custom URL slug (otherwise derived from the title).
|
|
89
|
+
- `canonicalUrl` / `originalArticleURL` — when cross-posting, point SEO back to
|
|
90
|
+
the original source. Set this whenever the same article also lives elsewhere.
|
|
91
|
+
- `coverImageOptions: { coverImageURL: "https://..." }` — cover image.
|
|
92
|
+
- `publishedAt` — ISO-8601 timestamp to backdate; `disableComments` etc. live
|
|
93
|
+
under `settings`.
|
|
94
|
+
|
|
95
|
+
## Save a draft (safe, non-public)
|
|
96
|
+
|
|
97
|
+
Use this when the user has not explicitly approved public publishing:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
VARS=$(jq -n --arg p "$PUB_ID" --arg t "$TITLE" --arg b "$BODY_MD" '{
|
|
101
|
+
input: { publicationId: $p, title: $t, contentMarkdown: $b }
|
|
102
|
+
}')
|
|
103
|
+
|
|
104
|
+
gql 'mutation CreateDraft($input: CreateDraftInput!) {
|
|
105
|
+
createDraft(input: $input) { draft { id slug title } }
|
|
106
|
+
}' "$VARS" | jq '{draft: .data.createDraft.draft, errors: .errors}'
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Update a published post
|
|
110
|
+
|
|
111
|
+
`updatePost` needs the post `id` (from `publishPost`, or the list query below):
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
VARS=$(jq -n --arg id "POST_ID" --arg b "$(cat article.md)" '{
|
|
115
|
+
input: { id: $id, contentMarkdown: $b }
|
|
116
|
+
}')
|
|
117
|
+
|
|
118
|
+
gql 'mutation UpdatePost($input: UpdatePostInput!) {
|
|
119
|
+
updatePost(input: $input) { post { id url title } }
|
|
120
|
+
}' "$VARS" | jq '{post: .data.updatePost.post, errors: .errors}'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## List / inspect my posts
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
gql 'query { me { posts(pageSize: 20, page: 1) { nodes { id title slug url views reactionCount responseCount publishedAt } } } }' \
|
|
127
|
+
| jq '[.data.me.posts.nodes[] | {id, title, url, views, reactions: .reactionCount, responses: .responseCount}]'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Single post with full content + stats:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
gql 'query GetPost($id: ObjectId!) { post(id: $id) { title url views reactionCount responseCount content { markdown } } }' \
|
|
134
|
+
"$(jq -n --arg id "POST_ID" '{id:$id}')" | jq '.data.post | {title, url, views, reactions: .reactionCount}'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Gotchas
|
|
138
|
+
|
|
139
|
+
- **No `Bearer` prefix** — the `Authorization` header carries the raw token.
|
|
140
|
+
- **`publicationId` is mandatory** for publishing/drafting — never guess it, read
|
|
141
|
+
it from the `me` query.
|
|
142
|
+
- **`tags` is required on `publishPost`** — supply at least one `{name, slug}`;
|
|
143
|
+
slug must be lowercase with hyphens (e.g. `machine-learning`).
|
|
144
|
+
- **`errors` on HTTP 200** — GraphQL reports failures in the `errors` array, not
|
|
145
|
+
via status codes; always surface them.
|
|
146
|
+
- **Idempotency** — re-running `publishPost` creates a *new* post each time; to
|
|
147
|
+
change an existing one use `updatePost` with its `id`.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wordpress
|
|
3
|
+
description: Publish and manage posts on a self-hosted WordPress site via the WordPress REST API. Use when the user mentions WordPress, wp-admin, publishing / updating a blog post, managing categories or tags, or uploading media to their own WordPress site.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to do anything with their self-hosted
|
|
6
|
+
WordPress site: turn a chat conversation into a published or draft
|
|
7
|
+
post, update an existing post, list recent posts, create / list
|
|
8
|
+
categories and tags, or upload a media file for use inside a post.
|
|
9
|
+
This skill is for self-hosted WordPress (Application Password auth),
|
|
10
|
+
not WordPress.com.
|
|
11
|
+
connections: [wordpress]
|
|
12
|
+
allowed_tools: [Bash]
|
|
13
|
+
license: Apache-2.0
|
|
14
|
+
metadata:
|
|
15
|
+
author: acedatacloud
|
|
16
|
+
version: "1.0"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
Drive the **WordPress REST API** (`/wp-json/wp/v2`) with `curl + jq`.
|
|
20
|
+
|
|
21
|
+
The user's self-hosted WordPress credentials are injected as env vars:
|
|
22
|
+
|
|
23
|
+
- `$WORDPRESS_SITE_URL` — site root, e.g. `https://blog.example.com`
|
|
24
|
+
- `$WORDPRESS_USERNAME` — the WordPress login username
|
|
25
|
+
- `$WORDPRESS_APP_PASSWORD` — an **Application Password** (WP 5.6+ core), NOT the
|
|
26
|
+
login password. Treat it like a secret — **never log or echo it.**
|
|
27
|
+
|
|
28
|
+
Auth is HTTP Basic (`username:app_password`) over HTTPS. Set up a reusable base
|
|
29
|
+
once, then every call reuses it:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Normalize the site URL (strip a trailing slash) and build the API base.
|
|
33
|
+
SITE="${WORDPRESS_SITE_URL%/}"
|
|
34
|
+
API="$SITE/wp-json/wp/v2"
|
|
35
|
+
# -u sends HTTP Basic auth; --fail-with-body surfaces the JSON error body on 4xx/5xx.
|
|
36
|
+
WP=(curl -sS --fail-with-body -u "$WORDPRESS_USERNAME:$WORDPRESS_APP_PASSWORD")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Errors come back as `{"code": "...", "message": "...", "data": {"status": 401}}` —
|
|
40
|
+
show `message` verbatim. Common codes:
|
|
41
|
+
|
|
42
|
+
| HTTP | Meaning | What to tell the user |
|
|
43
|
+
|------|---------|-----------------------|
|
|
44
|
+
| 401 | `incorrect_password` / bad Basic auth | Application Password wrong or revoked → regenerate it and reconnect the WordPress connector |
|
|
45
|
+
| 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
|
+
| 404 | `rest_no_route` | REST API disabled or a security plugin blocks `/wp-json` → the user must re-enable it |
|
|
47
|
+
| 400 | `rest_invalid_param` | Bad field (e.g. unknown category id) → fix and retry |
|
|
48
|
+
|
|
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
|
+
|
|
52
|
+
## Step 0 — verify the connection first
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
"${WP[@]}" "$API/users/me" | jq '{id, name, slug, roles: (.roles // [])}'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A 200 with your user object confirms the site URL, username, and Application
|
|
59
|
+
Password all work. If this fails, stop and surface the error — don't attempt writes.
|
|
60
|
+
|
|
61
|
+
## Publish or draft a post
|
|
62
|
+
|
|
63
|
+
**Publishing is public and hard to undo — confirm with the user before using
|
|
64
|
+
`status=publish`.** Default to `status=draft` and hand back the edit link.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
jq -n --arg t "国内如何稳定调用 Claude API" \
|
|
68
|
+
--arg c "<p>正文 HTML……</p>" \
|
|
69
|
+
--arg s "draft" \
|
|
70
|
+
'{title:$t, content:$c, status:$s}' \
|
|
71
|
+
| "${WP[@]}" -X POST "$API/posts" \
|
|
72
|
+
-H "Content-Type: application/json" -d @- \
|
|
73
|
+
| jq '{id, status, link, edit: "\(env.WORDPRESS_SITE_URL)/wp-admin/post.php?action=edit&post=\(.id)"}'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
With categories / tags / excerpt (ids come from the endpoints below):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
jq -n --arg t "标题" --arg c "<p>正文</p>" --arg e "一句话摘要" \
|
|
80
|
+
'{title:$t, content:$c, excerpt:$e, status:"draft",
|
|
81
|
+
categories:[5], tags:[12,34]}' \
|
|
82
|
+
| "${WP[@]}" -X POST "$API/posts" -H "Content-Type: application/json" -d @- \
|
|
83
|
+
| jq '{id, status, link}'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- Publish an existing draft: `POST $API/posts/<id>` body `{"status":"publish"}`.
|
|
87
|
+
- Update a post: `POST $API/posts/<id>` with any subset of fields (WP REST uses
|
|
88
|
+
POST, not PUT, for updates).
|
|
89
|
+
- Delete (trash) a post: `"${WP[@]}" -X DELETE "$API/posts/<id>"`.
|
|
90
|
+
|
|
91
|
+
## List / read posts
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
"${WP[@]}" "$API/posts?per_page=10&status=publish,draft&_fields=id,title,status,link,date" \
|
|
95
|
+
| jq '.[] | {id, title: .title.rendered, status, link, date}'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Paginate with `&page=2`; the total page count is in the `X-WP-TotalPages`
|
|
99
|
+
response header (add `-D -` to see headers).
|
|
100
|
+
|
|
101
|
+
## Categories & tags (get or create ids)
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# List existing
|
|
105
|
+
"${WP[@]}" "$API/categories?per_page=100&_fields=id,name,slug" | jq '.[] | {id, name}'
|
|
106
|
+
"${WP[@]}" "$API/tags?per_page=100&_fields=id,name,slug" | jq '.[] | {id, name}'
|
|
107
|
+
|
|
108
|
+
# Create one (returns its id)
|
|
109
|
+
jq -n --arg n "AI 教程" '{name:$n}' \
|
|
110
|
+
| "${WP[@]}" -X POST "$API/categories" -H "Content-Type: application/json" -d @- \
|
|
111
|
+
| jq '{id, name}'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Creating a term that already exists returns
|
|
115
|
+
`{"code":"term_exists", ... "data":{"status":400,"term_id":<id>}}` — reuse
|
|
116
|
+
`.data.term_id` instead of failing.
|
|
117
|
+
|
|
118
|
+
## Upload media (featured image / in-body image)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
FILE="./cover.png"
|
|
122
|
+
NAME="$(basename "$FILE")"
|
|
123
|
+
MEDIA_ID=$("${WP[@]}" -X POST "$API/media" \
|
|
124
|
+
-H "Content-Disposition: attachment; filename=\"$NAME\"" \
|
|
125
|
+
-H "Content-Type: image/png" \
|
|
126
|
+
--data-binary @"$FILE" | jq -r '.id')
|
|
127
|
+
echo "media id=$MEDIA_ID"
|
|
128
|
+
# Attach as the post's featured image:
|
|
129
|
+
# add "featured_media": <MEDIA_ID> to the post body.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Gotchas
|
|
133
|
+
|
|
134
|
+
- **HTTPS + Application Passwords are required.** On plain `http://`, WordPress
|
|
135
|
+
disables Application Passwords → every call 401s. Tell the user to enable HTTPS.
|
|
136
|
+
- **A security plugin / host may block `/wp-json`** (Wordfence, "disable REST
|
|
137
|
+
API" plugins, some managed hosts). Symptom: 404 `rest_no_route` or an HTML
|
|
138
|
+
login page instead of JSON. The user must allow REST API access.
|
|
139
|
+
- **The Application Password contains spaces** (e.g. `abcd efgh ijkl mnop`).
|
|
140
|
+
Keep them — `curl -u` handles the spaces fine; don't strip them.
|
|
141
|
+
- **Never publish silently.** Even if the user says "post it", prefer creating a
|
|
142
|
+
draft and returning the `wp-admin` edit link unless they explicitly asked to
|
|
143
|
+
go live.
|