@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acedatacloud/skills",
3
- "version": "2026.621.8",
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",
@@ -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)
@@ -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=(-H "Authorization: Bearer $FIGMA_TOKEN")
30
+ F="https://api.figma.com/v1"; AUTH="Authorization: Bearer $FIGMA_TOKEN"
31
31
  # Who am I (account card)
32
- curl -sS "${AUTH[@]}" "$F/me" | jq '{handle, email}'
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 "${AUTH[@]}" "$F/files/FILE_KEY?depth=2" \
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 "${AUTH[@]}" "$F/files/$KEY/nodes?ids=1:23,1:45" \
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 "${AUTH[@]}" "$F/images/$KEY?ids=1:23&format=png&scale=2" \
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 "${AUTH[@]}" "$F/files/FILE_KEY/comments" \
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 "${AUTH[@]}" "$F/teams/TEAM_ID/projects" | jq '.projects'
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=(-H "Authorization: Bearer $GOOGLE_ADS_TOKEN" -H "developer-token: $GOOGLE_ADS_DEVELOPER_TOKEN")
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 "${AUTH[@]}" "$BASE/customers:listAccessibleCustomers" | jq '.resourceNames'
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 "${AUTH[@]}" -H "login-customer-id: ${GOOGLE_ADS_LOGIN_CUSTOMER_ID:-$CID}" \
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=(-H "Authorization: Bearer $GOOGLE_ANALYTICS_TOKEN")
27
+ AUTH="Authorization: Bearer $GOOGLE_ANALYTICS_TOKEN"
28
28
  # List the GA4 properties the user can access (via their account summaries)
29
- curl -sS "${AUTH[@]}" "https://analyticsadmin.googleapis.com/v1beta/accountSummaries" \
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 "${AUTH[@]}" -H "Content-Type: application/json" -d '{
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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=(-H "Authorization: Bearer $GOOGLE_DOCS_TOKEN")
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 "${AUTH[@]}" "$D/DOC_ID" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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=(-H "Authorization: Bearer $GOOGLE_SEARCH_CONSOLE_TOKEN")
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 "${AUTH[@]}" "https://searchconsole.googleapis.com/webmasters/v3/sites" \
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 "${AUTH[@]}" -H "Content-Type: application/json" -d '{
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 "${AUTH[@]}" "https://searchconsole.googleapis.com/webmasters/v3/sites/$SITE/sitemaps" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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=(-H "Authorization: Bearer $GOOGLE_SHEETS_TOKEN")
32
+ S="https://sheets.googleapis.com/v4/spreadsheets"; AUTH="Authorization: Bearer $GOOGLE_SHEETS_TOKEN"
33
33
  # Tabs + title
34
- curl -sS "${AUTH[@]}" "$S/SPREADSHEET_ID?fields=properties.title,sheets.properties(title,sheetId)" \
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 "${AUTH[@]}" "$S/$ID/values/Sheet1!A1:D20" | jq '.values'
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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
  ```
@@ -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=(-H "Authorization: Bearer $MICROSOFT_EXCEL_TOKEN")
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 "${AUTH[@]}" "$G/me/drive/root/search(q='budget.xlsx')" \
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 "${AUTH[@]}" "$G/me/drive/items/ITEM_ID/workbook/worksheets" \
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 "${AUTH[@]}" \
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 "${AUTH[@]}" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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=(-H "Authorization: Bearer $MICROSOFT_TEAMS_TOKEN")
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 "${AUTH[@]}" "$G/me/chats?\$top=20&\$expand=members" \
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 "${AUTH[@]}" "$G/chats/$CHAT/messages?\$top=20" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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=(-H "Authorization: Bearer $MICROSOFT_TODO_TOKEN")
28
+ G="https://graph.microsoft.com/v1.0"; AUTH="Authorization: Bearer $MICROSOFT_TODO_TOKEN"
29
29
  # Task lists
30
- curl -sS "${AUTH[@]}" "$G/me/todo/lists" | jq '.value[] | {id, displayName, wellknownListName}'
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 "${AUTH[@]}" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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
 
@@ -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=(-H "Authorization: Bearer $TIKTOK_TOKEN")
27
+ T="https://open.tiktokapis.com/v2"; AUTH="Authorization: Bearer $TIKTOK_TOKEN"
28
28
  # Profile (account card)
29
- curl -sS "${AUTH[@]}" "$T/user/info/?fields=open_id,display_name,avatar_url" \
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 "${AUTH[@]}" -H "Content-Type: application/json" -d "$(jq -n --arg u "$VIDEO_URL" \
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 "${AUTH[@]}" -H "Content-Type: application/json" \
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
  ```
@@ -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=(-H "Authorization: Bearer $VERCEL_ACCESS_TOKEN")
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 "${AUTH[@]}" "https://api.vercel.com/v9/projects$TEAM" \
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 "${AUTH[@]}" "https://api.vercel.com/v6/deployments${TEAM:-?}&limit=20" \
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 "${AUTH[@]}" "https://api.vercel.com/v13/deployments/DEPLOYMENT_ID${TEAM:+&teamId=$VERCEL_TEAM_ID}" \
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 "${AUTH[@]}" "https://api.vercel.com/v3/deployments/DEPLOYMENT_ID/events${TEAM:+?teamId=$VERCEL_TEAM_ID}" \
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 "${AUTH[@]}" "https://api.vercel.com/v9/projects/PROJECT_ID/domains${TEAM:+?teamId=$VERCEL_TEAM_ID}" \
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