@acedatacloud/skills 2026.620.1 → 2026.621.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acedatacloud/skills",
3
- "version": "2026.620.1",
3
+ "version": "2026.621.1",
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,74 @@
1
+ ---
2
+ name: discord
3
+ description: Read your Discord identity and the list of servers (guilds) you belong to via the Discord API. Use when the user mentions Discord, asks which servers/guilds they are in, or wants their Discord account info.
4
+ when_to_use: |
5
+ Trigger when the user wants to read their Discord account identity
6
+ (username, avatar, email) or list the servers (guilds) their connected
7
+ Discord account belongs to. This connection is read-only identity +
8
+ guild list; it CANNOT read or send channel messages.
9
+ connections: [discord]
10
+ allowed_tools: [Bash]
11
+ license: Apache-2.0
12
+ metadata:
13
+ author: acedatacloud
14
+ version: "1.0"
15
+ ---
16
+
17
+ We drive the [Discord API](https://discord.com/developers/docs/reference)
18
+ with `curl + jq`. The user's OAuth bearer token is in `$DISCORD_TOKEN`;
19
+ every call needs it as `Authorization: Bearer $DISCORD_TOKEN`. Use the
20
+ versioned base URL `https://discord.com/api/v10`.
21
+
22
+ Discord returns standard JSON. Errors look like
23
+ `{"code": <n>, "message": "<reason>"}`. A `401 Unauthorized` means the
24
+ token expired or the connection was revoked — tell the user to re-connect
25
+ Discord at `auth.acedata.cloud/user/connections`. A `429` carries a
26
+ `retry_after` (seconds) field — sleep that long, then retry; never
27
+ parallelize.
28
+
29
+ **Scope is read-only `identify` + `email` + `guilds`.** This OAuth
30
+ connection can ONLY read the account's identity and the list of guilds it
31
+ belongs to. It CANNOT read/send channel messages, list a guild's channels
32
+ or members, or manage anything — those require a **Discord Bot** (bot
33
+ token + gateway), which this connector does not provide. Do not call
34
+ `/guilds/{id}/channels`, `/channels/...`, or `/guilds/{id}/members` — they
35
+ return 401/403 with a user OAuth token. If the user asks for those, say it
36
+ needs a Discord bot integration, which isn't set up.
37
+
38
+ ## Recipes
39
+
40
+ ### Verify auth + identity (always run first)
41
+
42
+ ```sh
43
+ curl -sS -H "Authorization: Bearer $DISCORD_TOKEN" \
44
+ "https://discord.com/api/v10/users/@me" \
45
+ | jq '{id, username, global_name, email, avatar}'
46
+ ```
47
+
48
+ ### List the servers (guilds) the user is in
49
+
50
+ ```sh
51
+ curl -sS -H "Authorization: Bearer $DISCORD_TOKEN" \
52
+ "https://discord.com/api/v10/users/@me/guilds" \
53
+ | jq 'map({id, name, owner, approximate_member_count})'
54
+ ```
55
+
56
+ Add `?with_counts=true` to include `approximate_member_count` /
57
+ `approximate_presence_count`:
58
+
59
+ ```sh
60
+ curl -sS -H "Authorization: Bearer $DISCORD_TOKEN" \
61
+ "https://discord.com/api/v10/users/@me/guilds?with_counts=true" \
62
+ | jq 'map({id, name, owner, members: .approximate_member_count})'
63
+ ```
64
+
65
+ ## Notes
66
+
67
+ - A "server" in the UI is a "guild" in the API. `owner: true` means the
68
+ user owns that guild.
69
+ - Guild icon URL (when `icon` is non-null):
70
+ `https://cdn.discordapp.com/icons/<guild_id>/<icon>.png` (use `.gif` if
71
+ the icon hash starts with `a_`).
72
+ - The guild list paginates at 200; the typical user is in far fewer, so a
73
+ single call is usually enough. If you ever hit 200, paginate with
74
+ `?after=<last_guild_id>`.
@@ -0,0 +1,195 @@
1
+ ---
2
+ name: telegram
3
+ description: Read, search and send personal Telegram messages — list recent chats / contacts / groups, pull a conversation's history, search messages, and send a message — driven by the Telethon MTProto client with the user's own account. Use when the user mentions Telegram, a Telegram chat/group/contact, "我的 Telegram", reading or replying to Telegram messages, or summarizing Telegram conversations.
4
+ when_to_use: |
5
+ Trigger when the user wants to do anything with their personal Telegram
6
+ account: list recent conversations, read / summarize the history of a chat
7
+ or group, search their messages for a keyword, look up a contact, or send /
8
+ reply to a message. This drives the user's OWN account over MTProto (not a
9
+ bot), so it can see everything the user can see.
10
+ connections: [telegram]
11
+ allowed_tools: [Bash]
12
+ license: Apache-2.0
13
+ metadata:
14
+ author: acedatacloud
15
+ version: "1.0"
16
+ ---
17
+
18
+ We drive **personal** Telegram over the MTProto protocol with the
19
+ [Telethon](https://docs.telethon.dev/) Python library — this acts as the user's own
20
+ account (a "userbot"), so unlike the Bot API it can read full chat history, list every
21
+ conversation, and message anyone the user can message.
22
+
23
+ The user's credentials are injected as environment variables by the connector:
24
+
25
+ - `TELEGRAM_API_ID` — the app id (from my.telegram.org)
26
+ - `TELEGRAM_API_HASH` — the app hash — **secret, never echo it**
27
+ - `TELEGRAM_SESSION_STRING` — a Telethon `StringSession` = **full account access. Never log,
28
+ echo, or print it.** Treat it like the account password.
29
+
30
+ ## Setup — write the helper once per session
31
+
32
+ `telethon` is preinstalled in the sandbox image. The helper is written to `./tg.py` **in the
33
+ current working directory** (the per-session workdir) — not a shared global path like `/tmp` —
34
+ so concurrent sessions never race on or reuse each other's file.
35
+
36
+ ```sh
37
+ # telethon is preinstalled; the `|| pip install` is a best-effort fallback only
38
+ # (the sandbox is non-root, so a runtime install may not succeed — rely on the
39
+ # preinstalled package).
40
+ python3 -c "import telethon" 2>/dev/null || pip install --user --quiet telethon 2>/dev/null || true
41
+
42
+ cat > ./tg.py <<'PY'
43
+ import os, sys, json, asyncio
44
+ from telethon import TelegramClient
45
+ from telethon.sessions import StringSession
46
+
47
+ API_ID = int(os.environ["TELEGRAM_API_ID"])
48
+ API_HASH = os.environ["TELEGRAM_API_HASH"]
49
+ SESSION = os.environ["TELEGRAM_SESSION_STRING"]
50
+
51
+
52
+ async def resolve(client, target):
53
+ # Accept a numeric id, @username, phone, or an exact chat display name.
54
+ try:
55
+ return await client.get_entity(int(target))
56
+ except (ValueError, TypeError):
57
+ pass
58
+ try:
59
+ return await client.get_entity(target)
60
+ except Exception:
61
+ async for d in client.iter_dialogs():
62
+ if d.name == target:
63
+ return d.entity
64
+ raise ValueError(f"could not resolve target: {target}")
65
+
66
+
67
+ async def main():
68
+ cmd = sys.argv[1]
69
+ async with TelegramClient(StringSession(SESSION), API_ID, API_HASH) as client:
70
+ if cmd == "whoami":
71
+ me = await client.get_me()
72
+ print(json.dumps({"id": me.id, "username": me.username,
73
+ "name": ((me.first_name or "") + " " + (me.last_name or "")).strip()},
74
+ ensure_ascii=False))
75
+ elif cmd == "list-chats":
76
+ limit = int(sys.argv[2]) if len(sys.argv) > 2 else 20
77
+ out = []
78
+ async for d in client.iter_dialogs(limit=limit):
79
+ out.append({"name": d.name, "id": d.id, "group": d.is_group,
80
+ "channel": d.is_channel, "user": d.is_user, "unread": d.unread_count})
81
+ print(json.dumps(out, ensure_ascii=False))
82
+ elif cmd == "get-messages":
83
+ target = sys.argv[2]
84
+ n = int(sys.argv[3]) if len(sys.argv) > 3 else 50
85
+ ent = await resolve(client, target)
86
+ out = []
87
+ async for m in client.iter_messages(ent, limit=n):
88
+ out.append({"id": m.id, "date": str(m.date), "out": m.out,
89
+ "sender_id": m.sender_id, "text": m.message})
90
+ out.reverse()
91
+ print(json.dumps(out, ensure_ascii=False))
92
+ elif cmd == "search":
93
+ target, query = sys.argv[2], sys.argv[3]
94
+ n = int(sys.argv[4]) if len(sys.argv) > 4 else 30
95
+ ent = await resolve(client, target)
96
+ out = []
97
+ async for m in client.iter_messages(ent, search=query, limit=n):
98
+ out.append({"id": m.id, "date": str(m.date), "sender_id": m.sender_id, "text": m.message})
99
+ print(json.dumps(out, ensure_ascii=False))
100
+ elif cmd == "send":
101
+ # Gated: without --confirm this only DRY-RUNS (prints the intended
102
+ # target + text and sends nothing). Pass --confirm to actually send.
103
+ target, text = sys.argv[2], sys.argv[3]
104
+ confirm = "--confirm" in sys.argv[4:]
105
+ ent = await resolve(client, target)
106
+ if not confirm:
107
+ name = getattr(ent, "title", None) or getattr(ent, "first_name", None) or str(target)
108
+ print(json.dumps({"dry_run": True, "would_send_to": name, "text": text,
109
+ "note": "re-run with --confirm to actually send"}, ensure_ascii=False))
110
+ return
111
+ msg = await client.send_message(ent, text)
112
+ print(json.dumps({"sent": True, "id": msg.id}, ensure_ascii=False))
113
+ else:
114
+ print(json.dumps({"error": f"unknown command: {cmd}"}))
115
+ sys.exit(1)
116
+
117
+
118
+ asyncio.run(main())
119
+ PY
120
+ echo "helper ready"
121
+ ```
122
+
123
+ ## Verify the connection (run this first)
124
+
125
+ ```sh
126
+ python3 ./tg.py whoami
127
+ # → {"id": 8367450178, "username": "GermeyAce", "name": "Germey"}
128
+ ```
129
+
130
+ If this errors with an auth/session message, the stored session is dead (revoked or expired) —
131
+ tell the user to reconnect the Telegram connector at https://auth.acedata.cloud/user/connections.
132
+
133
+ ## Recipes
134
+
135
+ ### List recent conversations
136
+
137
+ ```sh
138
+ python3 ./tg.py list-chats 20
139
+ # → [{"name":"Ace <> ConduitOS","id":-5287630726,"group":true,...,"unread":0}, ...]
140
+ ```
141
+
142
+ Use the returned `id` (or the exact `name`) as the target for the next calls.
143
+
144
+ ### Read a conversation's history (oldest→newest)
145
+
146
+ ```sh
147
+ # target = numeric id, @username, phone, or exact chat name; second arg = how many messages
148
+ python3 ./tg.py get-messages -5287630726 50
149
+ python3 ./tg.py get-messages @some_username 30
150
+ ```
151
+
152
+ `out: true` means the message was sent BY the user; `sender_id` is the author. Summarize from
153
+ the returned JSON.
154
+
155
+ ### Search inside a conversation
156
+
157
+ ```sh
158
+ python3 ./tg.py search -5287630726 "keyword" 30
159
+ ```
160
+
161
+ (Server-side search is scoped to one chat. To search broadly, list chats first, then search the
162
+ relevant ones.)
163
+
164
+ ### Send / reply to a message — TWO-STEP, confirm first
165
+
166
+ Sending posts a **real message as the user**, so it is gated:
167
+
168
+ ```sh
169
+ # Step 1 — DRY RUN (default, sends nothing). Show this preview to the user.
170
+ python3 ./tg.py send -5287630726 "Hi, following up on this."
171
+ # → {"dry_run": true, "would_send_to": "Ace <> ConduitOS", "text": "Hi, following up on this.", ...}
172
+
173
+ # Step 2 — only after the user explicitly says yes in the conversation, add --confirm:
174
+ python3 ./tg.py send -5287630726 "Hi, following up on this." --confirm
175
+ # → {"sent": true, "id": 4502}
176
+ ```
177
+
178
+ **Always run the dry run first, show the user exactly who + what, and require an explicit "yes"
179
+ before re-running with `--confirm`** — even if the original instruction said "just send it".
180
+ Never bulk-send.
181
+
182
+ ## Gotchas — surface these before the user is surprised
183
+
184
+ - **This is the user's real account.** Sending posts as them; reading exposes all their private
185
+ chats. Be conservative.
186
+ - **`FloodWaitError`**: Telegram rate-limits userbots. If a call fails with a flood-wait of N
187
+ seconds, tell the user to retry after N seconds — do not loop/retry aggressively (it escalates
188
+ toward an account ban).
189
+ - **Dead session**: a `session_string` can be revoked by the user from Telegram → Settings →
190
+ Devices. On an `AuthKeyError` / unauthorized error, the fix is reconnecting the connector, not
191
+ retrying.
192
+ - **Never print `TELEGRAM_SESSION_STRING` or `TELEGRAM_API_HASH`** — they are full-account
193
+ secrets. The helper never prints them; keep it that way.
194
+ - **Resolving targets**: prefer the numeric `id` from `list-chats` (most reliable). Names work
195
+ only on an exact match; usernames need the leading `@`.