@acedatacloud/skills 2026.926.0 → 2026.930.0
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/AGENTS.md +17 -50
- package/package.json +1 -1
- package/skills/ai-chat/SKILL.md +3 -1
- package/skills/ai-chat/references/platform.generated.json +2 -1
- package/skills/ai-chat/references/platform.generated.md +2 -1
- package/skills/personal-wechat/scripts/personal_wechat.py +11 -1
- package/skills/personal-wechat/tests/test_personal_wechat.py +11 -0
- package/skills/x/scripts/x.py +14 -3
- package/skills/youtube/SKILL.md +20 -13
- package/skills/youtube/scripts/list_uploads.py +85 -0
package/AGENTS.md
CHANGED
|
@@ -1,52 +1,19 @@
|
|
|
1
1
|
# AceDataCloud Agent Skills
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- **luma-video** — Generate videos with Luma Dream Machine
|
|
21
|
-
- **sora-video** — Generate videos with OpenAI Sora
|
|
22
|
-
- **veo-video** — Generate videos with Google Veo (native audio)
|
|
23
|
-
- **kling-video** — Generate videos with Kuaishou Kling (motion control)
|
|
24
|
-
- **hailuo-video** — Generate videos with Hailuo / MiniMax
|
|
25
|
-
- **happyhorse-video** — Generate and edit videos with Happy Horse
|
|
26
|
-
- **seedance-video** — Generate dance/motion videos with ByteDance Seedance
|
|
27
|
-
- **wan-video** — Generate videos with Alibaba Wan
|
|
28
|
-
- **maestro-video** — Produce complete videos from a brief with scripting, media, voiceover, editing, captions, and multilingual variants
|
|
29
|
-
|
|
30
|
-
### AI Chat & Tools
|
|
31
|
-
- **ai-chat** — Unified LLM gateway — GPT, Claude, Gemini, Kimi, Grok (50+ models)
|
|
32
|
-
- **google-search** — Search the web, images, news, maps, places, and videos via Google
|
|
33
|
-
- **tgstat** — Discover and analyze public Telegram channels/groups (BYOC username; no login)
|
|
34
|
-
- **face-transform** — Face analysis, beautification, age/gender transform, swap, cartoon
|
|
35
|
-
- **short-url** — Create and manage short URLs
|
|
36
|
-
- **onepage-pdf** — Convert an HTML page into one tall single-page PDF (local; no API token; needs Python + pymupdf + Chrome/Edge)
|
|
37
|
-
- **apple-notes** — Manage Apple Notes on macOS: create, search, read, export, organize notes (macOS-only; local; no API token; drives Notes.app via AppleScript)
|
|
38
|
-
- **acedatacloud** — Manage your AceDataCloud account — balance, usage/spend, API keys, services, orders, announcements
|
|
39
|
-
|
|
40
|
-
## Authentication
|
|
41
|
-
|
|
42
|
-
All skills require an API token:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
export ACEDATACLOUD_API_TOKEN="your-token-here"
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Get your token at [platform.acedata.cloud](https://platform.acedata.cloud).
|
|
49
|
-
|
|
50
|
-
## Paired MCP Servers
|
|
51
|
-
|
|
52
|
-
Each skill has a corresponding MCP server for tool-use capabilities. See the main [README](README.md) for the full mapping.
|
|
3
|
+
The canonical skill files are `skills/<name>/SKILL.md`. Use `README.md`
|
|
4
|
+
or the `skills/` directory to find the current catalog; do not maintain a
|
|
5
|
+
second skill list here. `.agents/skills/` and `.github/skills/` are
|
|
6
|
+
symlinks to `skills/`, so edit the canonical path.
|
|
7
|
+
|
|
8
|
+
- Keep each skill's description short and specific to when that skill
|
|
9
|
+
should load. Put task procedures and examples in its body or relevant
|
|
10
|
+
supporting files.
|
|
11
|
+
- Reuse `skills/_shared/` references for common AceDataCloud API
|
|
12
|
+
authentication, async tasks, and MCP guidance.
|
|
13
|
+
- Skills that call AceDataCloud APIs need an AceDataCloud Bearer token.
|
|
14
|
+
Local skills such as `onepage-pdf` and `apple-notes` do not. Follow
|
|
15
|
+
the selected skill's authentication instructions; never commit or
|
|
16
|
+
print tokens.
|
|
17
|
+
- When adding or changing a skill, update its own instructions and any
|
|
18
|
+
catalog entry that describes it, then run the applicable checks from
|
|
19
|
+
`.github/workflows/validate.yml`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.930.0",
|
|
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",
|
package/skills/ai-chat/SKILL.md
CHANGED
|
@@ -56,7 +56,7 @@ models include:
|
|
|
56
56
|
|
|
57
57
|
| Family | Current examples |
|
|
58
58
|
|--------|------------------|
|
|
59
|
-
| OpenAI / reasoning | `gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`, `gpt-5.6-luna`, `gpt-5.6-terra`, `gpt-5.6-sol`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.2`, `gpt-5.1`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`, `o1`, `o3`, `o4-mini` |
|
|
59
|
+
| OpenAI / reasoning | `gpt-6-astra`, `gpt-6.1-sol`, `gpt-6-sol`, `gpt-6-luna`, `gpt-5.6-luna`, `gpt-5.6-terra`, `gpt-5.6-sol`, `gpt-5.5`, `gpt-5.5-pro`, `gpt-5.4`, `gpt-5.4-pro`, `gpt-5.2`, `gpt-5.1`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`, `o1`, `o3`, `o4-mini` |
|
|
60
60
|
| OpenAI free-tier chat-completions | `gpt-5.5:free`, `gpt-5:free`, `gpt-4.1:free`, `gpt-4o:free`, `gpt-4o-mini:free`, `gpt-oss:free` |
|
|
61
61
|
| Claude | `claude-opus-5-5` (Messages only), `claude-fable-5-1`, `claude-fable-5`, `claude-opus-5`, `claude-sonnet-5`, `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-opus-4-5-20251101`, `claude-sonnet-4-6`, `claude-sonnet-4-5-20250929`, `claude-sonnet-4-20250514`, `claude-haiku-4-5-20251001`, `claude-3-7-sonnet-20250219` |
|
|
62
62
|
| Gemini | `gemini-3.8-flash`, `gemini-3.6-flash`, `gemini-3.5-flash`, `gemini-3.1-pro`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3-pro-preview`, `gemini-2.5-flash-lite`, `gemini-2.0-flash-lite` |
|
|
@@ -65,6 +65,8 @@ models include:
|
|
|
65
65
|
| Kimi | `kimi-k3`, `kimi-k2.6`, `kimi-k2.5`, `kimi-k2-thinking-turbo`, `kimi-k2-thinking` |
|
|
66
66
|
| GLM | `glm-5.2`, `glm-5.1`, `glm-5`, `glm-5-turbo`, `glm-4.7`, `glm-4.6`, `glm-4.5`, `glm-4.5v`, `glm-3-turbo` |
|
|
67
67
|
|
|
68
|
+
`gpt-6.1-sol` supports `low`, `medium`, `high`, `xhigh`, and `max` reasoning effort; do not send `none` or `minimal`. During its first week, access requires verified ACE T1+ holdings or an access grant. Check the platform access response for the opening time.
|
|
69
|
+
|
|
68
70
|
`claude-opus-5-5` is available only through the dedicated Claude Messages API; do not send it to `/openai/chat/completions` or `/aichat2/conversations`.
|
|
69
71
|
|
|
70
72
|
`/aichat2/conversations` also accepts `model_group` values
|
|
@@ -425,6 +425,7 @@
|
|
|
425
425
|
"gpt-6-astra",
|
|
426
426
|
"gpt-6-luna",
|
|
427
427
|
"gpt-6-sol",
|
|
428
|
+
"gpt-6.1-sol",
|
|
428
429
|
"gpt-image-1",
|
|
429
430
|
"gpt-oss:free",
|
|
430
431
|
"gpt-realtime",
|
|
@@ -460,6 +461,6 @@
|
|
|
460
461
|
],
|
|
461
462
|
"source": {
|
|
462
463
|
"repository": "AceDataCloud/PlatformBackend",
|
|
463
|
-
"sha": "
|
|
464
|
+
"sha": "9ceee14efb863ca3d0889edccc45e6b014ece681"
|
|
464
465
|
}
|
|
465
466
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!-- Generated from PlatformBackend. Do not edit manually. -->
|
|
2
2
|
# Platform contract
|
|
3
3
|
|
|
4
|
-
Source: `AceDataCloud/PlatformBackend@
|
|
4
|
+
Source: `AceDataCloud/PlatformBackend@9ceee14efb863ca3d0889edccc45e6b014ece681`
|
|
5
5
|
|
|
6
6
|
## Services
|
|
7
7
|
|
|
@@ -117,6 +117,7 @@ Source: `AceDataCloud/PlatformBackend@92a3426774282880f59b72c83c7e368ddadea99c`
|
|
|
117
117
|
- `gpt-6-astra`
|
|
118
118
|
- `gpt-6-luna`
|
|
119
119
|
- `gpt-6-sol`
|
|
120
|
+
- `gpt-6.1-sol`
|
|
120
121
|
- `gpt-image-1`
|
|
121
122
|
- `gpt-oss:free`
|
|
122
123
|
- `gpt-realtime`
|
|
@@ -286,7 +286,17 @@ def main() -> None:
|
|
|
286
286
|
args = build_parser().parse_args()
|
|
287
287
|
|
|
288
288
|
if args.cmd == "status":
|
|
289
|
-
|
|
289
|
+
status = request("GET", "/api/status")
|
|
290
|
+
logged_in = status.get("logged_in") is True
|
|
291
|
+
ready = status.get("status") == "ready" or status.get("ready") is True
|
|
292
|
+
if not logged_in or not ready:
|
|
293
|
+
status["scheduled_outcome"] = {
|
|
294
|
+
"version": 1,
|
|
295
|
+
"status": "skipped",
|
|
296
|
+
"code": "WECHAT_NOT_LOGGED_IN",
|
|
297
|
+
"side_effect_performed": False,
|
|
298
|
+
}
|
|
299
|
+
_json(status)
|
|
290
300
|
elif args.cmd == "account":
|
|
291
301
|
_json(request("GET", "/api/account"))
|
|
292
302
|
elif args.cmd == "contacts":
|
|
@@ -198,3 +198,14 @@ def test_unattended_confirm_allows_a_preauthorized_skill(monkeypatch) -> None:
|
|
|
198
198
|
calls, _ = run(["send", "Alice", "hi", "--unattended-confirm"], task_result={"sent": True})
|
|
199
199
|
|
|
200
200
|
assert calls[0]["path"] == "/api/messages/send"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_status_reports_not_logged_in_as_structured_skip() -> None:
|
|
204
|
+
_, stdout = run(["status"], responses={"/api/status": {"status": "ready", "logged_in": False}})
|
|
205
|
+
|
|
206
|
+
assert stdout[0]["scheduled_outcome"] == {
|
|
207
|
+
"version": 1,
|
|
208
|
+
"status": "skipped",
|
|
209
|
+
"code": "WECHAT_NOT_LOGGED_IN",
|
|
210
|
+
"side_effect_performed": False,
|
|
211
|
+
}
|
package/skills/x/scripts/x.py
CHANGED
|
@@ -300,10 +300,21 @@ async def cmd_whoami(client, args):
|
|
|
300
300
|
if expected:
|
|
301
301
|
expected_screen_name = normalize_screen_name(expected)
|
|
302
302
|
if authenticated_screen_name.casefold() != expected_screen_name.casefold():
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
303
|
+
out(
|
|
304
|
+
{
|
|
305
|
+
"error": (
|
|
306
|
+
f"connected X account is @{authenticated_screen_name}, not @{expected_screen_name}; "
|
|
307
|
+
"stopped without performing any write"
|
|
308
|
+
),
|
|
309
|
+
"scheduled_outcome": {
|
|
310
|
+
"version": 1,
|
|
311
|
+
"status": "skipped",
|
|
312
|
+
"code": "ACCOUNT_MISMATCH_SAFE_STOP",
|
|
313
|
+
"side_effect_performed": False,
|
|
314
|
+
},
|
|
315
|
+
}
|
|
306
316
|
)
|
|
317
|
+
raise SystemExit(1)
|
|
307
318
|
u, client = await retry_flaky_not_found(
|
|
308
319
|
client,
|
|
309
320
|
lambda c: c.get_user_by_screen_name(authenticated_screen_name),
|
package/skills/youtube/SKILL.md
CHANGED
|
@@ -64,20 +64,14 @@ YouTube has no "list my videos" call directly — read the channel's
|
|
|
64
64
|
**uploads playlist**, then page its items.
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
|
|
68
|
-
UPLOADS=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
69
|
-
"https://www.googleapis.com/youtube/v3/channels?part=contentDetails&mine=true" \
|
|
70
|
-
| jq -r '.items[0].contentDetails.relatedPlaylists.uploads')
|
|
71
|
-
|
|
72
|
-
# 2. List recent uploads (50/page; follow .nextPageToken for more).
|
|
73
|
-
curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
74
|
-
-G "https://www.googleapis.com/youtube/v3/playlistItems" \
|
|
75
|
-
--data-urlencode "part=snippet,contentDetails" \
|
|
76
|
-
--data-urlencode "playlistId=$UPLOADS" \
|
|
77
|
-
--data-urlencode "maxResults=50" \
|
|
78
|
-
| jq '.items[] | {videoId: .contentDetails.videoId, title: .snippet.title, published: .snippet.publishedAt}'
|
|
67
|
+
python3 skills/youtube/scripts/list_uploads.py --max-pages 20
|
|
79
68
|
```
|
|
80
69
|
|
|
70
|
+
The helper follows every `nextPageToken` until the upload history is complete
|
|
71
|
+
(or the explicit safety limit is reached). Its compact JSON keeps `videoId`,
|
|
72
|
+
`title`, and `published` for each item, plus `complete`, `pages_fetched`, and
|
|
73
|
+
`next_page_token`. Do not claim a video is absent when `complete` is false.
|
|
74
|
+
|
|
81
75
|
Paginate by passing `--data-urlencode "pageToken=$PAGE_TOKEN"` with the
|
|
82
76
|
`.nextPageToken` from the previous response.
|
|
83
77
|
|
|
@@ -175,7 +169,20 @@ RESULT=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
|
175
169
|
-X PUT --upload-file "$FILE" "$UPLOAD_URL")
|
|
176
170
|
echo "$RESULT" | jq -e .id >/dev/null 2>&1 \
|
|
177
171
|
|| { echo "upload failed: $(echo "$RESULT" | jq -r '.error.message' 2>/dev/null || echo "$RESULT")"; exit 1; }
|
|
178
|
-
echo "$RESULT" | jq '
|
|
172
|
+
VIDEO_ID=$(echo "$RESULT" | jq -r '.id')
|
|
173
|
+
VERIFY=$(curl -sS -H "Authorization: Bearer $GOOGLE_YOUTUBE_TOKEN" \
|
|
174
|
+
-G "https://www.googleapis.com/youtube/v3/videos" \
|
|
175
|
+
--data-urlencode "part=snippet,status,processingDetails" \
|
|
176
|
+
--data-urlencode "id=$VIDEO_ID")
|
|
177
|
+
echo "$VERIFY" | jq -e '.items[0].id == "'"$VIDEO_ID"'"' >/dev/null \
|
|
178
|
+
|| { echo "upload readback failed for $VIDEO_ID"; exit 1; }
|
|
179
|
+
echo "$VERIFY" | jq '.items[0] | {
|
|
180
|
+
video_id: .id,
|
|
181
|
+
canonical_url: ("https://www.youtube.com/watch?v=" + .id),
|
|
182
|
+
privacy: .status.privacyStatus,
|
|
183
|
+
upload_status: .status.uploadStatus,
|
|
184
|
+
processing_status: .processingDetails.processingStatus
|
|
185
|
+
}'
|
|
179
186
|
```
|
|
180
187
|
|
|
181
188
|
Delete a downloaded temp file only **after** you've confirmed an id came back
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""List uploaded YouTube videos while preserving pagination evidence."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Any
|
|
11
|
+
from urllib import error, parse, request
|
|
12
|
+
|
|
13
|
+
API_ROOT = "https://www.googleapis.com/youtube/v3"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def api_get(path: str, token: str, params: dict[str, str]) -> dict[str, Any]:
|
|
17
|
+
url = f"{API_ROOT}/{path}?{parse.urlencode(params)}"
|
|
18
|
+
req = request.Request(url, headers={"Authorization": f"Bearer {token}"})
|
|
19
|
+
try:
|
|
20
|
+
with request.urlopen(req, timeout=30) as response:
|
|
21
|
+
return json.load(response)
|
|
22
|
+
except error.HTTPError as exc:
|
|
23
|
+
body = exc.read().decode("utf-8", errors="replace")
|
|
24
|
+
raise RuntimeError(f"YouTube API HTTP {exc.code}: {body}") from exc
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def list_uploads(token: str, *, max_pages: int = 20) -> dict[str, Any]:
|
|
28
|
+
channel = api_get("channels", token, {"part": "contentDetails", "mine": "true"})
|
|
29
|
+
items = channel.get("items") or []
|
|
30
|
+
if not items:
|
|
31
|
+
raise RuntimeError("YouTube channel not found for the connected account")
|
|
32
|
+
uploads = items[0]["contentDetails"]["relatedPlaylists"]["uploads"]
|
|
33
|
+
|
|
34
|
+
videos: list[dict[str, Any]] = []
|
|
35
|
+
page_token = ""
|
|
36
|
+
pages = 0
|
|
37
|
+
while pages < max_pages:
|
|
38
|
+
params = {
|
|
39
|
+
"part": "snippet,contentDetails",
|
|
40
|
+
"playlistId": uploads,
|
|
41
|
+
"maxResults": "50",
|
|
42
|
+
}
|
|
43
|
+
if page_token:
|
|
44
|
+
params["pageToken"] = page_token
|
|
45
|
+
payload = api_get("playlistItems", token, params)
|
|
46
|
+
pages += 1
|
|
47
|
+
for item in payload.get("items") or []:
|
|
48
|
+
videos.append(
|
|
49
|
+
{
|
|
50
|
+
"videoId": item.get("contentDetails", {}).get("videoId"),
|
|
51
|
+
"title": item.get("snippet", {}).get("title"),
|
|
52
|
+
"published": item.get("snippet", {}).get("publishedAt"),
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
page_token = str(payload.get("nextPageToken") or "")
|
|
56
|
+
if not page_token:
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
"uploads_playlist": uploads,
|
|
61
|
+
"pages_fetched": pages,
|
|
62
|
+
"complete": not page_token,
|
|
63
|
+
"next_page_token": page_token or None,
|
|
64
|
+
"items": videos,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def main() -> int:
|
|
69
|
+
parser = argparse.ArgumentParser()
|
|
70
|
+
parser.add_argument("--max-pages", type=int, default=20)
|
|
71
|
+
args = parser.parse_args()
|
|
72
|
+
token = os.environ.get("GOOGLE_YOUTUBE_TOKEN", "")
|
|
73
|
+
if not token:
|
|
74
|
+
print(json.dumps({"error": "GOOGLE_YOUTUBE_TOKEN is not set"}))
|
|
75
|
+
return 2
|
|
76
|
+
try:
|
|
77
|
+
print(json.dumps(list_uploads(token, max_pages=max(1, min(args.max_pages, 100))), ensure_ascii=False))
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
print(json.dumps({"error": str(exc)}, ensure_ascii=False))
|
|
80
|
+
return 1
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
sys.exit(main())
|