@acedatacloud/skills 2026.621.8 → 2026.622.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 +1 -1
- package/skills/bilibili/SKILL.md +17 -0
- package/skills/bilibili/scripts/bilibili.py +54 -0
- package/skills/figma/SKILL.md +7 -7
- package/skills/google-ads/SKILL.md +3 -3
- package/skills/google-analytics/SKILL.md +4 -4
- package/skills/google-docs/SKILL.md +5 -5
- package/skills/google-search-console/SKILL.md +5 -5
- package/skills/google-sheets/SKILL.md +7 -7
- package/skills/medium/SKILL.md +3 -1
- package/skills/medium/scripts/medium.py +9 -1
- package/skills/microsoft-excel/SKILL.md +7 -7
- package/skills/microsoft-teams/SKILL.md +4 -4
- package/skills/microsoft-todo/SKILL.md +5 -5
- package/skills/tiktok/SKILL.md +4 -4
- package/skills/vercel/SKILL.md +6 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.622.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",
|
package/skills/bilibili/SKILL.md
CHANGED
|
@@ -38,6 +38,7 @@ BILI=$SKILL_DIR/scripts/bilibili.py
|
|
|
38
38
|
python3 $BILI whoami # who is logged in (mid, name)
|
|
39
39
|
python3 $BILI articles --limit 20 # my 专栏 articles + stats
|
|
40
40
|
python3 $BILI article <cvid> # one article's stats (cv id)
|
|
41
|
+
python3 $BILI drafts --limit 50 # list saved drafts (aid + title)
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
Stats come straight from Bilibili: `view` (阅读), `like` (点赞), `reply` (评论),
|
|
@@ -70,6 +71,22 @@ python3 $BILI publish --title "标题" --content-file a.html --confirm
|
|
|
70
71
|
risk-control (HTTP 412). When that happens the CLI reports the saved draft +
|
|
71
72
|
edit URL so the user can publish from the web editor. Default to `--draft-only`.
|
|
72
73
|
|
|
74
|
+
## Managing drafts (the 999-draft cap)
|
|
75
|
+
|
|
76
|
+
Bilibili caps 专栏 drafts at **999**; once full, saving a new draft fails with
|
|
77
|
+
`code 37106 草稿数已达最大上限`. List drafts and delete the ones you don't need:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
python3 $BILI drafts --limit 50 # list (aid + title)
|
|
81
|
+
python3 $BILI delete-draft <aid> <aid2> ... # dry-run (shows what would delete)
|
|
82
|
+
python3 $BILI delete-draft <aid> <aid2> ... --confirm # PERMANENTLY delete those drafts
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `delete-draft` is **GATED** (dry-run unless trailing `--confirm`) and deletion
|
|
86
|
+
is **permanent** — always show the dry-run + the titles and get an explicit
|
|
87
|
+
"yes" before `--confirm`. Pass multiple aids to batch a few per call.
|
|
88
|
+
- Never bulk-delete blindly: list first, confirm the titles are junk/duplicates.
|
|
89
|
+
|
|
73
90
|
## Gotchas
|
|
74
91
|
|
|
75
92
|
- **This is the user's real Bilibili account.** Confirm before any publish.
|
|
@@ -354,11 +354,60 @@ def cmd_publish(jar, args):
|
|
|
354
354
|
f"web editor. Detail: {(r or {}).get('message') if r else text[:200]}"})
|
|
355
355
|
|
|
356
356
|
|
|
357
|
+
def _drafts_of(d: dict) -> list:
|
|
358
|
+
al = (d.get("data") or {}).get("artlist") or d.get("artlist") or {}
|
|
359
|
+
return al.get("drafts") or []
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def cmd_drafts(jar, args):
|
|
363
|
+
# 专栏 drafts are capped at 999; this lists them so they can be pruned.
|
|
364
|
+
d = get_json(f"{API}/x/article/creative/draft/list?pn={args.page}&ps={args.limit}",
|
|
365
|
+
jar, referer="https://member.bilibili.com/")
|
|
366
|
+
if d.get("code"): # 0 = ok; anything truthy is an auth/API error
|
|
367
|
+
die(f"draft list error (code={d.get('code')}): {d.get('message')} — "
|
|
368
|
+
f"cookie may be expired; reconnect at https://auth.acedata.cloud/user/connections.")
|
|
369
|
+
items = _drafts_of(d)
|
|
370
|
+
out({"page": args.page, "count": len(items), "drafts": [{
|
|
371
|
+
"aid": x.get("id"),
|
|
372
|
+
"title": x.get("title"),
|
|
373
|
+
"edit_url": f"https://member.bilibili.com/article-text/home?aid={x.get('id')}",
|
|
374
|
+
} for x in items]})
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def cmd_delete_draft(jar, args):
|
|
378
|
+
if not args.aids:
|
|
379
|
+
die("provide one or more draft aids: delete-draft <aid> [<aid> ...] --confirm")
|
|
380
|
+
bad = [a for a in args.aids if not str(a).isdigit()]
|
|
381
|
+
if bad:
|
|
382
|
+
die(f"invalid draft aid(s) — must be numeric: {bad}")
|
|
383
|
+
csrf = cookie_value(jar, "bili_jct")
|
|
384
|
+
if not csrf:
|
|
385
|
+
die("no bili_jct cookie (CSRF token) — reconnect Bilibili.")
|
|
386
|
+
if not CONFIRM:
|
|
387
|
+
out({"dry_run": True, "command": "delete-draft", "platform": "bilibili",
|
|
388
|
+
"aids": args.aids, "note": "Deletion is PERMANENT. Re-run with --confirm "
|
|
389
|
+
"as the LAST argument to actually delete these draft(s)."})
|
|
390
|
+
return
|
|
391
|
+
results = []
|
|
392
|
+
for aid in args.aids:
|
|
393
|
+
_, text = request("POST", f"{API}/x/article/creative/draft/delete", jar,
|
|
394
|
+
referer="https://member.bilibili.com/", form={"aid": aid, "csrf": csrf})
|
|
395
|
+
try:
|
|
396
|
+
code = json.loads(text).get("code")
|
|
397
|
+
except json.JSONDecodeError:
|
|
398
|
+
code = None
|
|
399
|
+
results.append({"aid": aid, "deleted": code == 0, "code": code})
|
|
400
|
+
out({"command": "delete-draft", "deleted": sum(1 for r in results if r["deleted"]),
|
|
401
|
+
"results": results})
|
|
402
|
+
|
|
403
|
+
|
|
357
404
|
COMMANDS = {
|
|
358
405
|
"whoami": cmd_whoami,
|
|
359
406
|
"articles": cmd_articles,
|
|
360
407
|
"article": cmd_article,
|
|
361
408
|
"publish": cmd_publish,
|
|
409
|
+
"drafts": cmd_drafts,
|
|
410
|
+
"delete-draft": cmd_delete_draft,
|
|
362
411
|
}
|
|
363
412
|
|
|
364
413
|
|
|
@@ -375,6 +424,11 @@ def main() -> None:
|
|
|
375
424
|
sp.add_argument("--content", help="HTML content inline")
|
|
376
425
|
sp.add_argument("--content-file", help="path to an HTML file")
|
|
377
426
|
sp.add_argument("--draft-only", action="store_true", help="save a draft; do NOT submit")
|
|
427
|
+
sp = sub.add_parser("drafts", help="list 专栏 drafts (id+title); use to prune the 999-draft cap")
|
|
428
|
+
sp.add_argument("--limit", type=int, default=50)
|
|
429
|
+
sp.add_argument("--page", type=int, default=1)
|
|
430
|
+
sp = sub.add_parser("delete-draft", help="delete draft(s) by aid (GATED by trailing --confirm)")
|
|
431
|
+
sp.add_argument("aids", nargs="*", help="one or more draft aids to delete")
|
|
378
432
|
args = p.parse_args(ARGV)
|
|
379
433
|
jar = load_cookies()
|
|
380
434
|
COMMANDS[args.command](jar, args)
|
package/skills/figma/SKILL.md
CHANGED
|
@@ -27,11 +27,11 @@ segment of a pasted URL. A **node id** is in `?node-id=1-23` (Figma shows `1:23`
|
|
|
27
27
|
the API also accepts `1:23`).
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
F="https://api.figma.com/v1"; AUTH=
|
|
30
|
+
F="https://api.figma.com/v1"; AUTH="Authorization: Bearer $FIGMA_TOKEN"
|
|
31
31
|
# Who am I (account card)
|
|
32
|
-
curl -sS "$
|
|
32
|
+
curl -sS -H "$AUTH" "$F/me" | jq '{handle, email}'
|
|
33
33
|
# File document tree (name + top-level frames). Big files: prefer /nodes below.
|
|
34
|
-
curl -sS "$
|
|
34
|
+
curl -sS -H "$AUTH" "$F/files/FILE_KEY?depth=2" \
|
|
35
35
|
| jq '{name, pages: [.document.children[] | {name, frames: [.children[]?.name]}]}'
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -40,11 +40,11 @@ curl -sS "${AUTH[@]}" "$F/files/FILE_KEY?depth=2" \
|
|
|
40
40
|
```bash
|
|
41
41
|
KEY="FILE_KEY"
|
|
42
42
|
# Just the nodes you care about (faster than the whole file)
|
|
43
|
-
curl -sS "$
|
|
43
|
+
curl -sS -H "$AUTH" "$F/files/$KEY/nodes?ids=1:23,1:45" \
|
|
44
44
|
| jq '.nodes | to_entries[] | {id: .key, name: .value.document.name, type: .value.document.type}'
|
|
45
45
|
|
|
46
46
|
# Render nodes to images — returns temporary CDN URLs (this is the "see it" tool)
|
|
47
|
-
curl -sS "$
|
|
47
|
+
curl -sS -H "$AUTH" "$F/images/$KEY?ids=1:23&format=png&scale=2" \
|
|
48
48
|
| jq '.images' # { "1:23": "https://...png" }
|
|
49
49
|
```
|
|
50
50
|
|
|
@@ -54,10 +54,10 @@ For design-to-code, render the frame to PNG (to view) and read its node JSON
|
|
|
54
54
|
## Comments & projects
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
curl -sS "$
|
|
57
|
+
curl -sS -H "$AUTH" "$F/files/FILE_KEY/comments" \
|
|
58
58
|
| jq '.comments[] | {user: .user.handle, at: .created_at, message}'
|
|
59
59
|
# Team projects → files (needs a team id from the Figma URL /team/<id>/...)
|
|
60
|
-
curl -sS "$
|
|
60
|
+
curl -sS -H "$AUTH" "$F/teams/TEAM_ID/projects" | jq '.projects'
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
## Gotchas
|
|
@@ -35,16 +35,16 @@ say so rather than calling the API (it would 401/DEVELOPER_TOKEN_NOT_APPROVED).
|
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
37
|
VER="v18"; BASE="https://googleads.googleapis.com/$VER"
|
|
38
|
-
AUTH=
|
|
38
|
+
AUTH="Authorization: Bearer $GOOGLE_ADS_TOKEN"; DEV="developer-token: $GOOGLE_ADS_DEVELOPER_TOKEN"
|
|
39
39
|
# Customers the OAuth user can access (ids are returned as customers/<id>)
|
|
40
|
-
curl -sS "$
|
|
40
|
+
curl -sS -H "$AUTH" -H "$DEV" "$BASE/customers:listAccessibleCustomers" | jq '.resourceNames'
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## Report with GAQL (searchStream)
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
CID="1234567890" # target customer id, digits only
|
|
47
|
-
curl -sS "$
|
|
47
|
+
curl -sS -H "$AUTH" -H "$DEV" -H "login-customer-id: ${GOOGLE_ADS_LOGIN_CUSTOMER_ID:-$CID}" \
|
|
48
48
|
-H "Content-Type: application/json" -d '{
|
|
49
49
|
"query":"SELECT campaign.name, metrics.cost_micros, metrics.clicks, metrics.conversions FROM campaign WHERE segments.date DURING LAST_30_DAYS ORDER BY metrics.cost_micros DESC"
|
|
50
50
|
}' "$BASE/customers/$CID/googleAds:searchStream" \
|
|
@@ -24,9 +24,9 @@ Failures are `{"error":{"code","message","status"}}` — show verbatim. `401` =
|
|
|
24
24
|
re-install.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
AUTH=
|
|
27
|
+
AUTH="Authorization: Bearer $GOOGLE_ANALYTICS_TOKEN"
|
|
28
28
|
# List the GA4 properties the user can access (via their account summaries)
|
|
29
|
-
curl -sS "$
|
|
29
|
+
curl -sS -H "$AUTH" "https://analyticsadmin.googleapis.com/v1beta/accountSummaries" \
|
|
30
30
|
| jq '.accountSummaries[]?.propertySummaries[]? | {property, displayName}'
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -36,7 +36,7 @@ curl -sS "${AUTH[@]}" "https://analyticsadmin.googleapis.com/v1beta/accountSumma
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
PID="123456789"
|
|
39
|
-
curl -sS -X POST "$
|
|
39
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" -d '{
|
|
40
40
|
"dateRanges":[{"startDate":"28daysAgo","endDate":"today"}],
|
|
41
41
|
"dimensions":[{"name":"pagePath"}],
|
|
42
42
|
"metrics":[{"name":"screenPageViews"},{"name":"activeUsers"}],
|
|
@@ -54,7 +54,7 @@ Swap dimensions/metrics for other reports: `sessionDefaultChannelGroup` +
|
|
|
54
54
|
## Realtime
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
curl -sS -X POST "$
|
|
57
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
58
58
|
-d '{"dimensions":[{"name":"country"}],"metrics":[{"name":"activeUsers"}]}' \
|
|
59
59
|
"https://analyticsdata.googleapis.com/v1beta/properties/$PID:runRealtimeReport" | jq '.rows'
|
|
60
60
|
```
|
|
@@ -26,9 +26,9 @@ re-install. `403 PERMISSION_DENIED` on a write = read-only scope.
|
|
|
26
26
|
The doc id is the `…/document/d/<ID>/edit` segment of the URL.
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
D="https://docs.googleapis.com/v1/documents"; AUTH=
|
|
29
|
+
D="https://docs.googleapis.com/v1/documents"; AUTH="Authorization: Bearer $GOOGLE_DOCS_TOKEN"
|
|
30
30
|
# Read the document. The body is a tree of structural elements; pull plain text:
|
|
31
|
-
curl -sS "$
|
|
31
|
+
curl -sS -H "$AUTH" "$D/DOC_ID" \
|
|
32
32
|
| jq -r '.title, ([.body.content[]?.paragraph?.elements[]?.textRun?.content] | join(""))'
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -36,16 +36,16 @@ curl -sS "${AUTH[@]}" "$D/DOC_ID" \
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
# Create an empty doc
|
|
39
|
-
curl -sS -X POST "$
|
|
39
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
40
40
|
-d '{"title":"Meeting notes 2026-06-21"}' "$D" | jq '{documentId, title}'
|
|
41
41
|
|
|
42
42
|
# Insert text at the start (index 1) via batchUpdate (confirm first)
|
|
43
|
-
curl -sS -X POST "$
|
|
43
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
44
44
|
-d '{"requests":[{"insertText":{"location":{"index":1},"text":"Hello\n"}}]}' \
|
|
45
45
|
"$D/DOC_ID:batchUpdate" | jq '.documentId'
|
|
46
46
|
|
|
47
47
|
# Replace all occurrences of a placeholder
|
|
48
|
-
curl -sS -X POST "$
|
|
48
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
49
49
|
-d '{"requests":[{"replaceAllText":{"containsText":{"text":"{{name}}","matchCase":true},"replaceText":"Alex"}}]}' \
|
|
50
50
|
"$D/DOC_ID:batchUpdate" | jq '.replies'
|
|
51
51
|
```
|
|
@@ -24,9 +24,9 @@ Failures are `{"error":{"code","message","status"}}` — show verbatim. `401` =
|
|
|
24
24
|
re-install. `403` = the token's account doesn't own/verify that site.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
AUTH=
|
|
27
|
+
AUTH="Authorization: Bearer $GOOGLE_SEARCH_CONSOLE_TOKEN"
|
|
28
28
|
# Verified sites (siteUrl is the property — URL-encode it in later calls)
|
|
29
|
-
curl -sS "$
|
|
29
|
+
curl -sS -H "$AUTH" "https://searchconsole.googleapis.com/webmasters/v3/sites" \
|
|
30
30
|
| jq '.siteEntry[] | {siteUrl, permissionLevel}'
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -36,7 +36,7 @@ curl -sS "${AUTH[@]}" "https://searchconsole.googleapis.com/webmasters/v3/sites"
|
|
|
36
36
|
# Top queries by clicks for the last 28 days. siteUrl must be URL-encoded
|
|
37
37
|
# (https%3A%2F%2Fexample.com%2F or sc-domain%3Aexample.com).
|
|
38
38
|
SITE="https%3A%2F%2Fexample.com%2F"
|
|
39
|
-
curl -sS -X POST "$
|
|
39
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" -d '{
|
|
40
40
|
"startDate":"2026-05-24","endDate":"2026-06-21",
|
|
41
41
|
"dimensions":["query"],"rowLimit":10
|
|
42
42
|
}' "https://searchconsole.googleapis.com/webmasters/v3/sites/$SITE/searchAnalytics/query" \
|
|
@@ -50,11 +50,11 @@ Swap `dimensions` for `["page"]`, `["country"]`, `["date"]`, or combine
|
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
52
|
# Submitted sitemaps
|
|
53
|
-
curl -sS "$
|
|
53
|
+
curl -sS -H "$AUTH" "https://searchconsole.googleapis.com/webmasters/v3/sites/$SITE/sitemaps" \
|
|
54
54
|
| jq '.sitemap[] | {path, lastDownloaded, errors, warnings}'
|
|
55
55
|
|
|
56
56
|
# Is a URL indexed?
|
|
57
|
-
curl -sS -X POST "$
|
|
57
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
58
58
|
-d '{"inspectionUrl":"https://example.com/page","siteUrl":"https://example.com/"}' \
|
|
59
59
|
"https://searchconsole.googleapis.com/v1/urlInspection/index:inspect" \
|
|
60
60
|
| jq '.inspectionResult.indexStatusResult | {verdict, coverageState, lastCrawlTime}'
|
|
@@ -29,9 +29,9 @@ with read+write.
|
|
|
29
29
|
The spreadsheet id is the `…/spreadsheets/d/<ID>/edit` segment of the URL.
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
S="https://sheets.googleapis.com/v4/spreadsheets"; AUTH=
|
|
32
|
+
S="https://sheets.googleapis.com/v4/spreadsheets"; AUTH="Authorization: Bearer $GOOGLE_SHEETS_TOKEN"
|
|
33
33
|
# Tabs + title
|
|
34
|
-
curl -sS "$
|
|
34
|
+
curl -sS -H "$AUTH" "$S/SPREADSHEET_ID?fields=properties.title,sheets.properties(title,sheetId)" \
|
|
35
35
|
| jq '{title: .properties.title, tabs: [.sheets[].properties.title]}'
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -40,15 +40,15 @@ curl -sS "${AUTH[@]}" "$S/SPREADSHEET_ID?fields=properties.title,sheets.properti
|
|
|
40
40
|
```bash
|
|
41
41
|
ID="SPREADSHEET_ID"
|
|
42
42
|
# Read a range (A1 notation; values are rows of cells)
|
|
43
|
-
curl -sS "$
|
|
43
|
+
curl -sS -H "$AUTH" "$S/$ID/values/Sheet1!A1:D20" | jq '.values'
|
|
44
44
|
|
|
45
45
|
# Append rows (confirm first). valueInputOption=USER_ENTERED parses formulas/dates.
|
|
46
|
-
curl -sS -X POST "$
|
|
46
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
47
47
|
-d '{"values":[["2026-06-21","Acme",4200]]}' \
|
|
48
48
|
"$S/$ID/values/Sheet1!A1:append?valueInputOption=USER_ENTERED" | jq '.updates'
|
|
49
49
|
|
|
50
50
|
# Update a fixed range: PUT /values/{range}?valueInputOption=USER_ENTERED
|
|
51
|
-
curl -sS -X PUT "$
|
|
51
|
+
curl -sS -X PUT -H "$AUTH" -H "Content-Type: application/json" \
|
|
52
52
|
-d '{"values":[["done"]]}' \
|
|
53
53
|
"$S/$ID/values/Sheet1!E2?valueInputOption=USER_ENTERED" | jq '.updatedCells'
|
|
54
54
|
```
|
|
@@ -57,11 +57,11 @@ curl -sS -X PUT "${AUTH[@]}" -H "Content-Type: application/json" \
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
# New spreadsheet
|
|
60
|
-
curl -sS -X POST "$
|
|
60
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
61
61
|
-d '{"properties":{"title":"Report 2026"}}' "$S" | jq '{spreadsheetId, spreadsheetUrl}'
|
|
62
62
|
|
|
63
63
|
# Add a tab via batchUpdate (addSheet request)
|
|
64
|
-
curl -sS -X POST "$
|
|
64
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
65
65
|
-d '{"requests":[{"addSheet":{"properties":{"title":"Q3"}}}]}' \
|
|
66
66
|
"$S/$ID:batchUpdate" | jq '.replies'
|
|
67
67
|
```
|
package/skills/medium/SKILL.md
CHANGED
|
@@ -71,6 +71,8 @@ publish). `--draft-only` stops at the draft (visible only at the user's
|
|
|
71
71
|
- Markdown→Medium conversion is paragraph-level (headings, quotes, code, body);
|
|
72
72
|
complex inline formatting / images aren't converted — the user can polish in
|
|
73
73
|
the Medium editor before going public.
|
|
74
|
-
- Medium sits behind Cloudflare; an occasional 403/429 is transient
|
|
74
|
+
- Medium sits behind Cloudflare; an occasional 403/429 is transient — the CLI
|
|
75
|
+
auto-retries once after a short pause. A *persistent* 403 means the cookie is
|
|
76
|
+
genuinely expired (reconnect).
|
|
75
77
|
- **Never print `MEDIUM_COOKIES`** — it is full account access.
|
|
76
78
|
- **ToS**: acts only on the user's own account with their own captured cookie.
|
|
@@ -30,6 +30,7 @@ import json
|
|
|
30
30
|
import os
|
|
31
31
|
import re
|
|
32
32
|
import sys
|
|
33
|
+
import time
|
|
33
34
|
import urllib.error
|
|
34
35
|
import urllib.parse
|
|
35
36
|
import urllib.request
|
|
@@ -147,8 +148,15 @@ def request(method, url, jar, *, headers=None, body=None, accept="application/js
|
|
|
147
148
|
die(f"network error reaching {url}: {e.reason}")
|
|
148
149
|
|
|
149
150
|
|
|
150
|
-
def api(method, url, jar, *, body=None):
|
|
151
|
+
def api(method, url, jar, *, body=None, _retried=False):
|
|
151
152
|
status, text = request(method, url, jar, body=body)
|
|
153
|
+
# Medium sits behind Cloudflare, which intermittently 403s/429s an otherwise
|
|
154
|
+
# valid session; one retry after a short pause clears the transient block.
|
|
155
|
+
# Only retry idempotent GETs — never replay a POST (new-story/deltas/publish),
|
|
156
|
+
# which could duplicate a write if the origin already processed it.
|
|
157
|
+
if status in (403, 429) and method == "GET" and not _retried:
|
|
158
|
+
time.sleep(1.5)
|
|
159
|
+
return api(method, url, jar, body=body, _retried=True)
|
|
152
160
|
if status in (401, 403):
|
|
153
161
|
die(f"auth failed ({status}) on {url} — cookie likely expired. "
|
|
154
162
|
f"Reconnect at https://auth.acedata.cloud/user/connections.")
|
|
@@ -25,12 +25,12 @@ Failures are `{"error":{"code","message"}}` — show `message` verbatim. `401` =
|
|
|
25
25
|
token expired (re-install). `403` on a write = the user granted read-only.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
G="https://graph.microsoft.com/v1.0"; AUTH=
|
|
28
|
+
G="https://graph.microsoft.com/v1.0"; AUTH="Authorization: Bearer $MICROSOFT_EXCEL_TOKEN"
|
|
29
29
|
# Find the workbook by name (then take .id as ITEM_ID)
|
|
30
|
-
curl -sS "$
|
|
30
|
+
curl -sS -H "$AUTH" "$G/me/drive/root/search(q='budget.xlsx')" \
|
|
31
31
|
| jq '.value[] | {id, name, webUrl}'
|
|
32
32
|
# Worksheets in the workbook
|
|
33
|
-
curl -sS "$
|
|
33
|
+
curl -sS -H "$AUTH" "$G/me/drive/items/ITEM_ID/workbook/worksheets" \
|
|
34
34
|
| jq '.value[] | {id, name, position}'
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -39,16 +39,16 @@ curl -sS "${AUTH[@]}" "$G/me/drive/items/ITEM_ID/workbook/worksheets" \
|
|
|
39
39
|
```bash
|
|
40
40
|
ITEM="ITEM_ID"; WS="Sheet1"
|
|
41
41
|
# Used range (values + formulas + formats)
|
|
42
|
-
curl -sS "$
|
|
42
|
+
curl -sS -H "$AUTH" \
|
|
43
43
|
"$G/me/drive/items/$ITEM/workbook/worksheets/$WS/usedRange" \
|
|
44
44
|
| jq '{address, values: .values}'
|
|
45
45
|
|
|
46
46
|
# Read a specific range
|
|
47
|
-
curl -sS "$
|
|
47
|
+
curl -sS -H "$AUTH" \
|
|
48
48
|
"$G/me/drive/items/$ITEM/workbook/worksheets/$WS/range(address='A1:C5')" | jq '.values'
|
|
49
49
|
|
|
50
50
|
# Update a range (confirm first). values is a 2-D array matching the address shape.
|
|
51
|
-
curl -sS -X PATCH "$
|
|
51
|
+
curl -sS -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
|
|
52
52
|
-d '{"values":[["Q3",1200],["Q4",1580]]}' \
|
|
53
53
|
"$G/me/drive/items/$ITEM/workbook/worksheets/$WS/range(address='A2:B3')" | jq '.address'
|
|
54
54
|
```
|
|
@@ -56,7 +56,7 @@ curl -sS -X PATCH "${AUTH[@]}" -H "Content-Type: application/json" \
|
|
|
56
56
|
## Append a row to a table
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
curl -sS -X POST "$
|
|
59
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
60
60
|
-d '{"values":[["2026-06-21","Acme",4200]]}' \
|
|
61
61
|
"$G/me/drive/items/$ITEM/workbook/tables/Table1/rows/add" | jq '.index'
|
|
62
62
|
```
|
|
@@ -25,9 +25,9 @@ re-install. `403`/`Forbidden` on send = the user granted read-only
|
|
|
25
25
|
(`Chat.Read`) → re-connect with `Chat.ReadWrite` + `ChatMessage.Send`.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
G="https://graph.microsoft.com/v1.0"; AUTH=
|
|
28
|
+
G="https://graph.microsoft.com/v1.0"; AUTH="Authorization: Bearer $MICROSOFT_TEAMS_TOKEN"
|
|
29
29
|
# My chats (1:1 + group), most recent first; expand members for names
|
|
30
|
-
curl -sS "$
|
|
30
|
+
curl -sS -H "$AUTH" "$G/me/chats?\$top=20&\$expand=members" \
|
|
31
31
|
| jq '.value[] | {id, chatType, topic, members: [.members[].displayName]}'
|
|
32
32
|
```
|
|
33
33
|
|
|
@@ -36,11 +36,11 @@ curl -sS "${AUTH[@]}" "$G/me/chats?\$top=20&\$expand=members" \
|
|
|
36
36
|
```bash
|
|
37
37
|
CHAT="CHAT_ID"
|
|
38
38
|
# Recent messages
|
|
39
|
-
curl -sS "$
|
|
39
|
+
curl -sS -H "$AUTH" "$G/chats/$CHAT/messages?\$top=20" \
|
|
40
40
|
| jq '.value[] | {from: .from.user.displayName, created: .createdDateTime, text: .body.content}'
|
|
41
41
|
|
|
42
42
|
# Send a message (confirm content with the user first). contentType html|text.
|
|
43
|
-
curl -sS -X POST "$
|
|
43
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
44
44
|
-d '{"body":{"contentType":"html","content":"Hi from the assistant 👋"}}' \
|
|
45
45
|
"$G/chats/$CHAT/messages" | jq '{id, created: .createdDateTime}'
|
|
46
46
|
```
|
|
@@ -25,9 +25,9 @@ means the token expired (re-install). `403`/`ErrorAccessDenied` on a write means
|
|
|
25
25
|
the user only granted `Tasks.Read` → ask them to re-connect with read+write.
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
G="https://graph.microsoft.com/v1.0"; AUTH=
|
|
28
|
+
G="https://graph.microsoft.com/v1.0"; AUTH="Authorization: Bearer $MICROSOFT_TODO_TOKEN"
|
|
29
29
|
# Task lists
|
|
30
|
-
curl -sS "$
|
|
30
|
+
curl -sS -H "$AUTH" "$G/me/todo/lists" | jq '.value[] | {id, displayName, wellknownListName}'
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Tasks
|
|
@@ -35,17 +35,17 @@ curl -sS "${AUTH[@]}" "$G/me/todo/lists" | jq '.value[] | {id, displayName, well
|
|
|
35
35
|
```bash
|
|
36
36
|
LIST="LIST_ID"
|
|
37
37
|
# Open tasks in a list (filter notStarted/inProgress; $top caps page size)
|
|
38
|
-
curl -sS "$
|
|
38
|
+
curl -sS -H "$AUTH" \
|
|
39
39
|
"$G/me/todo/lists/$LIST/tasks?\$filter=status ne 'completed'&\$top=50" \
|
|
40
40
|
| jq '.value[] | {id, title, status, due: .dueDateTime.dateTime}'
|
|
41
41
|
|
|
42
42
|
# Create (confirm first). dueDateTime/reminderDateTime are optional.
|
|
43
|
-
curl -sS -X POST "$
|
|
43
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
44
44
|
-d '{"title":"Follow up with Alex","dueDateTime":{"dateTime":"2026-06-30T17:00:00","timeZone":"UTC"}}' \
|
|
45
45
|
"$G/me/todo/lists/$LIST/tasks" | jq '{id, title, status}'
|
|
46
46
|
|
|
47
47
|
# Complete: PATCH the task with {"status":"completed"}
|
|
48
|
-
curl -sS -X PATCH "$
|
|
48
|
+
curl -sS -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
|
|
49
49
|
-d '{"status":"completed"}' "$G/me/todo/lists/$LIST/tasks/TASK_ID" | jq '{title, status}'
|
|
50
50
|
```
|
|
51
51
|
|
package/skills/tiktok/SKILL.md
CHANGED
|
@@ -24,9 +24,9 @@ Responses wrap everything in `{"data":...,"error":{"code","message","log_id"}}`
|
|
|
24
24
|
`401`/`access_token_invalid` = re-connect TikTok.
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
T="https://open.tiktokapis.com/v2"; AUTH=
|
|
27
|
+
T="https://open.tiktokapis.com/v2"; AUTH="Authorization: Bearer $TIKTOK_TOKEN"
|
|
28
28
|
# Profile (account card)
|
|
29
|
-
curl -sS "$
|
|
29
|
+
curl -sS -H "$AUTH" "$T/user/info/?fields=open_id,display_name,avatar_url" \
|
|
30
30
|
| jq '.data.user'
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -37,7 +37,7 @@ on a **verified domain** (AceData's `cdn.acedata.cloud` is verified for this app
|
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
VIDEO_URL="https://cdn.acedata.cloud/...mp4" # must be on a verified domain
|
|
40
|
-
curl -sS -X POST "$
|
|
40
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" -d "$(jq -n --arg u "$VIDEO_URL" \
|
|
41
41
|
'{source_info:{source:"PULL_FROM_URL", video_url:$u}}')" \
|
|
42
42
|
"$T/post/publish/inbox/video/init/" | jq '{publish_id: .data.publish_id, error}'
|
|
43
43
|
```
|
|
@@ -46,7 +46,7 @@ This drops the video into the user's TikTok **inbox** — they open the TikTok a
|
|
|
46
46
|
to add caption / sound / privacy and post. Poll status:
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
curl -sS -X POST "$
|
|
49
|
+
curl -sS -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
50
50
|
-d '{"publish_id":"PUBLISH_ID"}' "$T/post/publish/status/fetch/" \
|
|
51
51
|
| jq '.data | {status, fail_reason}'
|
|
52
52
|
```
|
package/skills/vercel/SKILL.md
CHANGED
|
@@ -29,18 +29,18 @@ Helper for the optional team param:
|
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
TEAM=""; [ -n "$VERCEL_TEAM_ID" ] && TEAM="?teamId=$VERCEL_TEAM_ID"
|
|
32
|
-
AUTH=
|
|
32
|
+
AUTH="Authorization: Bearer $VERCEL_ACCESS_TOKEN"
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## Projects & deployments
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
# Projects
|
|
39
|
-
curl -sS "$
|
|
39
|
+
curl -sS -H "$AUTH" "https://api.vercel.com/v9/projects$TEAM" \
|
|
40
40
|
| jq '.projects[] | {name, framework, latestProduction: .latestDeployments[0].url}'
|
|
41
41
|
|
|
42
42
|
# Recent deployments (optionally filter by ?projectId=… or &state=ERROR)
|
|
43
|
-
curl -sS "$
|
|
43
|
+
curl -sS -H "$AUTH" "https://api.vercel.com/v6/deployments${TEAM:-?}&limit=20" \
|
|
44
44
|
| jq '.deployments[] | {uid, name, url, state, readyState, created}'
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -48,11 +48,11 @@ curl -sS "${AUTH[@]}" "https://api.vercel.com/v6/deployments${TEAM:-?}&limit=20"
|
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
# Deployment detail
|
|
51
|
-
curl -sS "$
|
|
51
|
+
curl -sS -H "$AUTH" "https://api.vercel.com/v13/deployments/DEPLOYMENT_ID${TEAM:+&teamId=$VERCEL_TEAM_ID}" \
|
|
52
52
|
| jq '{name, url, state: .readyState, error: .errorMessage}'
|
|
53
53
|
|
|
54
54
|
# Build / runtime events (the actual logs)
|
|
55
|
-
curl -sS "$
|
|
55
|
+
curl -sS -H "$AUTH" "https://api.vercel.com/v3/deployments/DEPLOYMENT_ID/events${TEAM:+?teamId=$VERCEL_TEAM_ID}" \
|
|
56
56
|
| jq -r '.[] | select(.type=="stdout" or .type=="stderr") | .payload.text'
|
|
57
57
|
```
|
|
58
58
|
|
|
@@ -60,7 +60,7 @@ curl -sS "${AUTH[@]}" "https://api.vercel.com/v3/deployments/DEPLOYMENT_ID/event
|
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
62
|
# Project domains
|
|
63
|
-
curl -sS "$
|
|
63
|
+
curl -sS -H "$AUTH" "https://api.vercel.com/v9/projects/PROJECT_ID/domains${TEAM:+?teamId=$VERCEL_TEAM_ID}" \
|
|
64
64
|
| jq '.domains[] | {name, verified}'
|
|
65
65
|
```
|
|
66
66
|
|