@acedatacloud/skills 2026.504.0 → 2026.504.2
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/README.md +19 -1
- package/package.json +1 -1
- package/skills/github/SKILL.md +173 -0
- package/skills/gitlab/SKILL.md +147 -0
- package/skills/google-calendar/SKILL.md +195 -0
- package/skills/google-drive/SKILL.md +201 -0
- package/skills/google-gmail/SKILL.md +214 -0
- package/skills/google-tasks/SKILL.md +185 -0
- package/skills/microsoft-onedrive/SKILL.md +256 -0
- package/skills/microsoft-outlook/SKILL.md +488 -0
- package/skills/notion/SKILL.md +122 -0
- package/skills/slack/SKILL.md +130 -0
- package/skills/wechat-official-account/SKILL.md +310 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: google-drive
|
|
3
|
+
description: Read and search Google Drive files / folders / shared content via the Drive v3 REST API. Use when the user mentions Drive files, "my drive", shared documents, Google Docs / Sheets / Slides, exporting / downloading a Drive file, or searching by name / owner / folder.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to list, search, read or download files
|
|
6
|
+
in their Google Drive — including Google-native docs (Docs / Sheets /
|
|
7
|
+
Slides) which need a special "export" call to get plain content. The
|
|
8
|
+
installed connector grants read-only scope (`drive.readonly`); writes
|
|
9
|
+
are out of scope.
|
|
10
|
+
connections: [google/drive]
|
|
11
|
+
allowed_tools: [Bash]
|
|
12
|
+
license: Apache-2.0
|
|
13
|
+
metadata:
|
|
14
|
+
author: acedatacloud
|
|
15
|
+
version: "1.0"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
Drive Google Drive via `curl + jq`. The user's OAuth bearer token is
|
|
19
|
+
in `$GOOGLE_DRIVE_TOKEN`; every call needs it as
|
|
20
|
+
`Authorization: Bearer $GOOGLE_DRIVE_TOKEN`. The token already carries
|
|
21
|
+
the `drive.readonly` scope the user agreed to at install plus the
|
|
22
|
+
identity scopes (`openid email profile`).
|
|
23
|
+
|
|
24
|
+
The Drive API returns standard JSON; failures surface as
|
|
25
|
+
`{"error": {"code": 401|403|..., "message": "..."}}` — show that
|
|
26
|
+
error verbatim to the user. `401` means the token expired and the
|
|
27
|
+
user must re-install the connector. `403 insufficientPermissions`
|
|
28
|
+
means the connector grants only `drive.readonly` and the user is
|
|
29
|
+
asking for a write — say so explicitly.
|
|
30
|
+
|
|
31
|
+
**Always start with `/about?fields=user`** to confirm the connection
|
|
32
|
+
works AND learn which Google account you're operating against.
|
|
33
|
+
|
|
34
|
+
## Recipes
|
|
35
|
+
|
|
36
|
+
### Verify auth (always run first)
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
40
|
+
"https://www.googleapis.com/drive/v3/about?fields=user(displayName,emailAddress,photoLink),storageQuota(usage,limit)" \
|
|
41
|
+
| jq '{user, quota: .storageQuota}'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### List recent files (last modified first)
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
48
|
+
"https://www.googleapis.com/drive/v3/files?orderBy=modifiedTime%20desc&pageSize=20&fields=files(id,name,mimeType,modifiedTime,owners(emailAddress),webViewLink,parents)" \
|
|
49
|
+
| jq '.files[] | {id, name, mimeType, modified: .modifiedTime, owner: .owners[0].emailAddress, webViewLink}'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`pageSize` max is 1000; default is 100. Use `pageToken` from the
|
|
53
|
+
response (`nextPageToken`) for follow-up pages.
|
|
54
|
+
|
|
55
|
+
### Search by name / fulltext
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
# Exact-name fragments — note "name contains" supports tokens, not regex
|
|
59
|
+
Q='name contains "季度复盘" and trashed = false'
|
|
60
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
61
|
+
--get "https://www.googleapis.com/drive/v3/files" \
|
|
62
|
+
--data-urlencode "q=$Q" \
|
|
63
|
+
--data-urlencode 'fields=files(id,name,mimeType,modifiedTime,webViewLink,owners(emailAddress))' \
|
|
64
|
+
--data-urlencode 'pageSize=20' \
|
|
65
|
+
| jq '.files[] | {id, name, modified: .modifiedTime, owner: .owners[0].emailAddress}'
|
|
66
|
+
|
|
67
|
+
# Full-text search (body + title)
|
|
68
|
+
Q='fullText contains "OKR 2026Q2" and trashed = false'
|
|
69
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
70
|
+
--get "https://www.googleapis.com/drive/v3/files" \
|
|
71
|
+
--data-urlencode "q=$Q" \
|
|
72
|
+
--data-urlencode 'fields=files(id,name,modifiedTime,webViewLink)' \
|
|
73
|
+
| jq '.files[]'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The `q` param uses [Drive's mini query language](https://developers.google.com/drive/api/guides/search-files):
|
|
77
|
+
`name`, `fullText`, `mimeType`, `parents`, `'<email>' in owners`,
|
|
78
|
+
`'<email>' in writers`, `modifiedTime > '2026-01-01T00:00:00'`,
|
|
79
|
+
`sharedWithMe`, `trashed`, joined by `and` / `or` / `not`.
|
|
80
|
+
|
|
81
|
+
### List files shared with me
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
85
|
+
--get "https://www.googleapis.com/drive/v3/files" \
|
|
86
|
+
--data-urlencode 'q=sharedWithMe and trashed = false' \
|
|
87
|
+
--data-urlencode 'orderBy=sharedWithMeTime desc' \
|
|
88
|
+
--data-urlencode 'fields=files(id,name,mimeType,sharedWithMeTime,owners(displayName,emailAddress))' \
|
|
89
|
+
--data-urlencode 'pageSize=30' \
|
|
90
|
+
| jq '.files[] | {name, sharedAt: .sharedWithMeTime, sharedBy: .owners[0]}'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### List children of a folder
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
FOLDER_ID='1A2B3CdEfGhIjKlMn'
|
|
97
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
98
|
+
--get "https://www.googleapis.com/drive/v3/files" \
|
|
99
|
+
--data-urlencode "q='$FOLDER_ID' in parents and trashed = false" \
|
|
100
|
+
--data-urlencode 'fields=files(id,name,mimeType,size,modifiedTime),nextPageToken' \
|
|
101
|
+
| jq '.files'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Get metadata for a single file
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
FILE_ID='1A2B3CdEfGhIjKlMn'
|
|
108
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
109
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name,mimeType,size,modifiedTime,parents,owners,webViewLink,description"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Download a binary file (PDF / image / zip / …)
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
FILE_ID='1A2B3CdEfGhIjKlMn'
|
|
116
|
+
OUT=/tmp/download.bin
|
|
117
|
+
curl -sS -L -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
118
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID?alt=media" \
|
|
119
|
+
-o "$OUT"
|
|
120
|
+
file "$OUT" && wc -c "$OUT"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Read a Google Doc as plain markdown / text
|
|
124
|
+
|
|
125
|
+
Google-native files (Docs, Sheets, Slides) **don't have raw bytes** —
|
|
126
|
+
you have to ask Drive to *export* them to a concrete MIME type:
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
DOC_ID='1A2B3CdEfGhIjKlMn'
|
|
130
|
+
|
|
131
|
+
# Markdown (best for chat-friendly summaries)
|
|
132
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
133
|
+
"https://www.googleapis.com/drive/v3/files/$DOC_ID/export?mimeType=text/markdown" \
|
|
134
|
+
> /tmp/doc.md
|
|
135
|
+
head -40 /tmp/doc.md
|
|
136
|
+
|
|
137
|
+
# Plain text fallback for older docs
|
|
138
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
139
|
+
"https://www.googleapis.com/drive/v3/files/$DOC_ID/export?mimeType=text/plain" \
|
|
140
|
+
> /tmp/doc.txt
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Common export MIME types:
|
|
144
|
+
|
|
145
|
+
| native MIME | export to |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `application/vnd.google-apps.document` | `text/markdown`, `text/plain`, `text/html`, `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
|
|
148
|
+
| `application/vnd.google-apps.spreadsheet` | `text/csv`, `application/pdf`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
|
|
149
|
+
| `application/vnd.google-apps.presentation` | `application/pdf`, `text/plain`, `application/vnd.openxmlformats-officedocument.presentationml.presentation` |
|
|
150
|
+
|
|
151
|
+
### Read a Google Sheet as CSV
|
|
152
|
+
|
|
153
|
+
```sh
|
|
154
|
+
SHEET_ID='1A2B3CdEfGhIjKlMn'
|
|
155
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
156
|
+
"https://www.googleapis.com/drive/v3/files/$SHEET_ID/export?mimeType=text/csv" \
|
|
157
|
+
> /tmp/sheet.csv
|
|
158
|
+
head /tmp/sheet.csv
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The Drive `export` endpoint returns the **first sheet only**. For
|
|
162
|
+
multi-tab access the user needs to install a separate Google Sheets
|
|
163
|
+
connector (currently out of catalog) — explain that and stop.
|
|
164
|
+
|
|
165
|
+
### Get permissions / sharing on a file
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
FILE_ID='1A2B3CdEfGhIjKlMn'
|
|
169
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
170
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID/permissions?fields=permissions(id,type,role,emailAddress,domain,deleted)" \
|
|
171
|
+
| jq '.permissions[] | {who: (.emailAddress // .domain // .type), role}'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Pagination boilerplate
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
PAGE_TOKEN=''
|
|
178
|
+
while : ; do
|
|
179
|
+
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
180
|
+
--get "https://www.googleapis.com/drive/v3/files" \
|
|
181
|
+
--data-urlencode 'q=trashed = false' \
|
|
182
|
+
--data-urlencode 'fields=files(id,name),nextPageToken' \
|
|
183
|
+
--data-urlencode 'pageSize=200' \
|
|
184
|
+
${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
|
|
185
|
+
echo "$RESP" | jq -c '.files[]'
|
|
186
|
+
PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
|
|
187
|
+
[ -z "$PAGE_TOKEN" ] && break
|
|
188
|
+
done
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Common error codes
|
|
192
|
+
|
|
193
|
+
| HTTP | meaning | what to tell the user |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Google Drive connector on the Connections page." |
|
|
196
|
+
| `403 insufficientPermissions` | scope missing | "Your installed connector only grants read access — this action needs a write scope we don't have." |
|
|
197
|
+
| `403 userRateLimitExceeded` | quota | retry once after 5–10s; if it persists, tell the user. |
|
|
198
|
+
| `404 notFound` | wrong file id OR file isn't visible to this account | double-check the id; if shared, use `sharedWithMe` query above. |
|
|
199
|
+
| `400 invalidQuery` | malformed `q` | print the `q` you sent + the error message back to the user. |
|
|
200
|
+
|
|
201
|
+
Never log or echo `$GOOGLE_DRIVE_TOKEN` — treat it as a secret.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: google-gmail
|
|
3
|
+
description: Read, search and triage Gmail mail / threads / labels / attachments via the Gmail v1 REST API. Use when the user mentions Gmail, "my inbox", unread mail, recent emails from someone, summarising a thread, downloading an attachment, or finding mail by label / query.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to read, list, search, summarise or
|
|
6
|
+
inspect Gmail mail — including triaging the inbox, surfacing unread,
|
|
7
|
+
pulling a single thread for review, or downloading an attachment.
|
|
8
|
+
The installed connector grants read-only scope (`gmail.readonly`);
|
|
9
|
+
sending / replying / archiving / labelling are out of scope.
|
|
10
|
+
connections: [google/gmail]
|
|
11
|
+
allowed_tools: [Bash]
|
|
12
|
+
license: Apache-2.0
|
|
13
|
+
metadata:
|
|
14
|
+
author: acedatacloud
|
|
15
|
+
version: "1.0"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
Drive Gmail via `curl + jq`. The user's OAuth bearer token is in
|
|
19
|
+
`$GOOGLE_GMAIL_TOKEN`; every call needs it as
|
|
20
|
+
`Authorization: Bearer $GOOGLE_GMAIL_TOKEN`. The token already
|
|
21
|
+
carries the `gmail.readonly` scope the user agreed to at install plus
|
|
22
|
+
the identity scopes (`openid email profile`).
|
|
23
|
+
|
|
24
|
+
The Gmail API returns standard JSON; failures surface as
|
|
25
|
+
`{"error": {"code": 401|403|..., "message": "..."}}` — show that
|
|
26
|
+
error verbatim. `401` means the token expired (re-install). `403`
|
|
27
|
+
or `400 insufficientPermissions` means the user is asking for a write
|
|
28
|
+
this connector cannot satisfy — say so.
|
|
29
|
+
|
|
30
|
+
**Always start with `users/me/profile`** to confirm the connection works
|
|
31
|
+
AND learn which Gmail account you're operating against. Mailbox payloads
|
|
32
|
+
can be huge — fetch metadata first, only `format=full` when the user
|
|
33
|
+
actually wants the body of a specific message.
|
|
34
|
+
|
|
35
|
+
## Recipes
|
|
36
|
+
|
|
37
|
+
### Verify auth (always run first)
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
41
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/profile" \
|
|
42
|
+
| jq '{email: .emailAddress, totalMessages, totalThreads, historyId}'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### List recent unread inbox
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
49
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
|
|
50
|
+
--data-urlencode 'q=is:unread in:inbox newer_than:7d' \
|
|
51
|
+
--data-urlencode 'maxResults=20' \
|
|
52
|
+
| jq '.messages[]'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The `messages.list` endpoint returns only `{id, threadId}` — you have
|
|
56
|
+
to fan out to `messages.get` for headers / body. Cheap pattern: list
|
|
57
|
+
ids → get with `format=metadata&metadataHeaders=From,Subject,Date` for
|
|
58
|
+
each. Use `format=full` only if the user wants the body.
|
|
59
|
+
|
|
60
|
+
### List + enrich with headers (one-shot inbox triage)
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
IDS=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
64
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
|
|
65
|
+
--data-urlencode 'q=is:unread in:inbox' \
|
|
66
|
+
--data-urlencode 'maxResults=10' \
|
|
67
|
+
| jq -r '.messages[].id')
|
|
68
|
+
|
|
69
|
+
for ID in $IDS; do
|
|
70
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
71
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$ID" \
|
|
72
|
+
--data-urlencode 'format=metadata' \
|
|
73
|
+
--data-urlencode 'metadataHeaders=From' \
|
|
74
|
+
--data-urlencode 'metadataHeaders=Subject' \
|
|
75
|
+
--data-urlencode 'metadataHeaders=Date' \
|
|
76
|
+
| jq '{id: .id, snippet: .snippet, headers: (.payload.headers | map({(.name): .value}) | add), labels: .labelIds}'
|
|
77
|
+
done | jq -s '.'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Read a single message body (plain text and html)
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
ID='18f1a2b3c4d5e6f0'
|
|
84
|
+
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
85
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$ID" \
|
|
86
|
+
--data-urlencode 'format=full')
|
|
87
|
+
|
|
88
|
+
echo "$RESP" | jq '{id, snippet, headers: (.payload.headers | map({(.name): .value}) | add)}'
|
|
89
|
+
|
|
90
|
+
# Body is base64url-encoded inside payload.parts[].body.data — Gmail
|
|
91
|
+
# splits multipart messages, so collect every text/plain or text/html
|
|
92
|
+
# leaf and base64url-decode them.
|
|
93
|
+
echo "$RESP" | jq -r '
|
|
94
|
+
def walk(p):
|
|
95
|
+
if (p.parts // null) then (p.parts | map(walk(.)) | add) else [p] end;
|
|
96
|
+
walk(.payload)
|
|
97
|
+
| map(select(.mimeType=="text/plain" and (.body.data // "") != ""))
|
|
98
|
+
| .[].body.data' \
|
|
99
|
+
| tr '_-' '/+' | base64 -d 2>/dev/null
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If the plain-text leaf is empty, fall back to the `text/html` leaf
|
|
103
|
+
(same walk, swap the mimeType filter) and tell the user it's HTML.
|
|
104
|
+
|
|
105
|
+
### Read a whole thread
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
THREAD_ID='18f1a2b3c4d5e6f0'
|
|
109
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
110
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/threads/$THREAD_ID" \
|
|
111
|
+
--data-urlencode 'format=metadata' \
|
|
112
|
+
--data-urlencode 'metadataHeaders=From' \
|
|
113
|
+
--data-urlencode 'metadataHeaders=Subject' \
|
|
114
|
+
--data-urlencode 'metadataHeaders=Date' \
|
|
115
|
+
| jq '{id, historyId, messages: [.messages[] | {id, snippet, from: (.payload.headers | from_entries.From), date: (.payload.headers | from_entries.Date)}]}'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Search by Gmail query
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
# Same query DSL the Gmail UI uses: from:, to:, subject:, has:attachment,
|
|
122
|
+
# is:unread, label:Work, after:2026/04/01, before:2026/05/01, …
|
|
123
|
+
Q='from:boss@example.com subject:OKR newer_than:30d'
|
|
124
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
125
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
|
|
126
|
+
--data-urlencode "q=$Q" \
|
|
127
|
+
--data-urlencode 'maxResults=20' \
|
|
128
|
+
| jq '.messages // []'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`q` syntax reference: <https://support.google.com/mail/answer/7190> —
|
|
132
|
+
the model-friendly bits are `from:`, `to:`, `cc:`, `subject:`, `label:`,
|
|
133
|
+
`is:unread`, `is:read`, `is:starred`, `has:attachment`, `filename:pdf`,
|
|
134
|
+
`newer_than:7d`, `older_than:30d`, `after:YYYY/MM/DD`, `before:`, `in:inbox`,
|
|
135
|
+
`in:trash`. Combine with `OR` / `()` / `-`.
|
|
136
|
+
|
|
137
|
+
### List labels (system + user-defined)
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
141
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/labels" \
|
|
142
|
+
| jq '.labels[] | {id, name, type, color: .color.backgroundColor}'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The system labels are `INBOX`, `SENT`, `DRAFT`, `IMPORTANT`, `UNREAD`,
|
|
146
|
+
`STARRED`, `SPAM`, `TRASH`, plus `CATEGORY_*` (Personal / Social /
|
|
147
|
+
Promotions / Updates / Forums).
|
|
148
|
+
|
|
149
|
+
### Filter by label
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
LABEL_ID='Label_4' # from labels.list above
|
|
153
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
154
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
|
|
155
|
+
--data-urlencode "labelIds=$LABEL_ID" \
|
|
156
|
+
--data-urlencode 'maxResults=20' \
|
|
157
|
+
| jq '.messages // []'
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Multiple `labelIds` query params behave like AND.
|
|
161
|
+
|
|
162
|
+
### Download an attachment
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
MSG_ID='18f1a2b3c4d5e6f0'
|
|
166
|
+
|
|
167
|
+
# 1. find the attachment leaf
|
|
168
|
+
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
169
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID" \
|
|
170
|
+
--data-urlencode 'format=full')
|
|
171
|
+
|
|
172
|
+
echo "$RESP" | jq '
|
|
173
|
+
def walk(p):
|
|
174
|
+
if (p.parts // null) then (p.parts | map(walk(.)) | add) else [p] end;
|
|
175
|
+
walk(.payload)
|
|
176
|
+
| map(select(.body.attachmentId? != null))
|
|
177
|
+
| .[] | {filename, mimeType, attachmentId: .body.attachmentId, size: .body.size}'
|
|
178
|
+
|
|
179
|
+
# 2. fetch the attachment by id
|
|
180
|
+
ATT_ID='ANGjdJ-abc123'
|
|
181
|
+
OUT=/tmp/attachment.bin
|
|
182
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
183
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/attachments/$ATT_ID" \
|
|
184
|
+
| jq -r .data | tr '_-' '/+' | base64 -d > "$OUT"
|
|
185
|
+
file "$OUT"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Pagination
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
PAGE_TOKEN=''
|
|
192
|
+
while : ; do
|
|
193
|
+
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
194
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages" \
|
|
195
|
+
--data-urlencode 'q=in:inbox' \
|
|
196
|
+
--data-urlencode 'maxResults=100' \
|
|
197
|
+
${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
|
|
198
|
+
echo "$RESP" | jq -c '.messages[]?'
|
|
199
|
+
PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
|
|
200
|
+
[ -z "$PAGE_TOKEN" ] && break
|
|
201
|
+
done
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Common error codes
|
|
205
|
+
|
|
206
|
+
| HTTP | meaning | what to tell the user |
|
|
207
|
+
|---|---|---|
|
|
208
|
+
| `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Gmail connector on the Connections page." |
|
|
209
|
+
| `403 insufficientPermissions` | scope missing | "This connector grants only read access — modifying mail isn't possible." |
|
|
210
|
+
| `403 userRateLimitExceeded` / `429` | quota / throttling | back off ~5s, then retry once. |
|
|
211
|
+
| `404 notFound` | wrong message / thread / attachment id | double-check the id, or fall back to `messages.list` with the right query. |
|
|
212
|
+
| `400 invalidQuery` | malformed `q` | print the `q` you sent + the error back to the user. |
|
|
213
|
+
|
|
214
|
+
Never log or echo `$GOOGLE_GMAIL_TOKEN` — treat it as a secret.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: google-tasks
|
|
3
|
+
description: Read Google Tasks task lists and individual tasks via the Tasks v1 REST API. Use when the user mentions Google Tasks, todo / pending / overdue tasks, weekly task recap, or grouping todos by list.
|
|
4
|
+
when_to_use: |
|
|
5
|
+
Trigger when the user wants to inspect their Google Tasks — list
|
|
6
|
+
task lists, surface pending items, group by due date, or pull
|
|
7
|
+
details for one task. The installed connector grants read-only
|
|
8
|
+
scope (`tasks.readonly`); creating / updating / deleting tasks is
|
|
9
|
+
out of scope.
|
|
10
|
+
connections: [google/tasks]
|
|
11
|
+
allowed_tools: [Bash]
|
|
12
|
+
license: Apache-2.0
|
|
13
|
+
metadata:
|
|
14
|
+
author: acedatacloud
|
|
15
|
+
version: "1.0"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
Drive Google Tasks via `curl + jq`. The user's OAuth bearer token is
|
|
19
|
+
in `$GOOGLE_TASKS_TOKEN`; every call needs it as
|
|
20
|
+
`Authorization: Bearer $GOOGLE_TASKS_TOKEN`. The token already
|
|
21
|
+
carries the `tasks.readonly` scope the user agreed to at install plus
|
|
22
|
+
the identity scopes (`openid email profile`).
|
|
23
|
+
|
|
24
|
+
The Tasks API returns standard JSON; failures surface as
|
|
25
|
+
`{"error": {"code": 401|403|..., "message": "..."}}` — show that
|
|
26
|
+
error verbatim. `401` means the token expired (re-install). `403
|
|
27
|
+
insufficientPermissions` means the user is asking for a write this
|
|
28
|
+
connector cannot satisfy — say so.
|
|
29
|
+
|
|
30
|
+
**Always start with `users/@me/lists`** to discover which task lists
|
|
31
|
+
the account has — the user's default plus any extras they created on
|
|
32
|
+
calendar.google.com or in the Tasks app.
|
|
33
|
+
|
|
34
|
+
## Recipes
|
|
35
|
+
|
|
36
|
+
### Verify auth + list all task lists (always run first)
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
40
|
+
"https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
|
|
41
|
+
| jq '.items[] | {id, title, updated}'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The default list is usually titled "我的任务" / "My Tasks" but the
|
|
45
|
+
**id** (a long opaque string like `MTAxMjM0NTY3OA`) is what every
|
|
46
|
+
subsequent `lists/{id}/tasks` call needs.
|
|
47
|
+
|
|
48
|
+
### List all unfinished tasks across every list
|
|
49
|
+
|
|
50
|
+
```sh
|
|
51
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
52
|
+
"https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
|
|
53
|
+
| jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
|
|
54
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
55
|
+
--get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
56
|
+
--data-urlencode 'showCompleted=false' \
|
|
57
|
+
--data-urlencode 'maxResults=100' \
|
|
58
|
+
| jq --arg list "$LIST_TITLE" '.items[]? | {list: $list, title, due, status, notes}'
|
|
59
|
+
done | jq -s '. | sort_by(.due // "9999")'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`showCompleted=false` filters out done items at the API level. The
|
|
63
|
+
default `showCompleted=true&showHidden=false` returns done tasks too.
|
|
64
|
+
|
|
65
|
+
### Pending tasks in one specific list, sorted by due date
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
69
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
70
|
+
--get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
71
|
+
--data-urlencode 'showCompleted=false' \
|
|
72
|
+
--data-urlencode 'maxResults=100' \
|
|
73
|
+
| jq '.items // [] | sort_by(.due // "9999") | .[] | {title, due, notes, status, position}'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`position` is the user's drag-to-reorder rank inside the list — useful
|
|
77
|
+
when the user says "what's at the top of my tasks". Tasks without a
|
|
78
|
+
`due` field are open-ended.
|
|
79
|
+
|
|
80
|
+
### Tasks due today
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
TODAY=$(date -u +%Y-%m-%d)
|
|
84
|
+
TOMORROW=$(date -u -d "+1 day" +%Y-%m-%d 2>/dev/null \
|
|
85
|
+
|| date -u -v+1d +%Y-%m-%d)
|
|
86
|
+
TODAY_START="${TODAY}T00:00:00.000Z"
|
|
87
|
+
TOMORROW_START="${TOMORROW}T00:00:00.000Z"
|
|
88
|
+
|
|
89
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
90
|
+
"https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
|
|
91
|
+
| jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
|
|
92
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
93
|
+
--get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
94
|
+
--data-urlencode "dueMin=$TODAY_START" \
|
|
95
|
+
--data-urlencode "dueMax=$TOMORROW_START" \
|
|
96
|
+
--data-urlencode 'showCompleted=false' \
|
|
97
|
+
| jq --arg list "$LIST_TITLE" '.items[]? | {list: $list, title, due, notes}'
|
|
98
|
+
done | jq -s
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`dueMin` / `dueMax` are RFC 3339 timestamps. The Tasks API stores
|
|
102
|
+
`due` at midnight UTC, so the local-day window is approximate around
|
|
103
|
+
the date boundary — that's fine for "due today" semantics, mention
|
|
104
|
+
the caveat only if the user pushes back.
|
|
105
|
+
|
|
106
|
+
### Overdue tasks (everything still pending with a due date in the past)
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
NOW=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
|
|
110
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
111
|
+
"https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
|
|
112
|
+
| jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
|
|
113
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
114
|
+
--get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
115
|
+
--data-urlencode "dueMax=$NOW" \
|
|
116
|
+
--data-urlencode 'showCompleted=false' \
|
|
117
|
+
| jq --arg list "$LIST_TITLE" '.items[]? | {list: $list, title, due, daysOverdue: (((now * 1000) - (.due | sub("Z"; "+00:00") | fromdateiso8601 * 1000)) / 86400000 | floor)}'
|
|
118
|
+
done | jq -s
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Recently completed tasks (this week, for a recap)
|
|
122
|
+
|
|
123
|
+
```sh
|
|
124
|
+
ONE_WEEK_AGO=$(date -u -d "-7 days" +%Y-%m-%dT%H:%M:%S.000Z 2>/dev/null \
|
|
125
|
+
|| date -u -v-7d +%Y-%m-%dT%H:%M:%S.000Z)
|
|
126
|
+
|
|
127
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
128
|
+
"https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
|
|
129
|
+
| jq -r '.items[] | "\(.id)\t\(.title)"' | while IFS=$'\t' read LIST_ID LIST_TITLE; do
|
|
130
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
131
|
+
--get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
132
|
+
--data-urlencode 'showCompleted=true' \
|
|
133
|
+
--data-urlencode 'showHidden=true' \
|
|
134
|
+
--data-urlencode "completedMin=$ONE_WEEK_AGO" \
|
|
135
|
+
| jq --arg list "$LIST_TITLE" '.items[]? | select(.status=="completed") | {list: $list, title, completed}'
|
|
136
|
+
done | jq -s '. | sort_by(.completed)'
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`completedMin` / `completedMax` mirror `dueMin`/`Max` and only apply
|
|
140
|
+
to tasks already moved to the "completed" state. You **must** pass
|
|
141
|
+
`showCompleted=true` AND `showHidden=true` to see them — Google hides
|
|
142
|
+
completed tasks from the default list.
|
|
143
|
+
|
|
144
|
+
### Get one task's details
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
148
|
+
TASK_ID='dGFza0lkRXhhbXBsZQ'
|
|
149
|
+
curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
150
|
+
"https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
|
|
151
|
+
| jq '{title, due, status, notes, completed, position, links: .links}'
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`links` exposes the user's manual hyperlinks (e.g. an attached email
|
|
155
|
+
or Drive doc) — render them as a list to the user when present.
|
|
156
|
+
|
|
157
|
+
### Pagination
|
|
158
|
+
|
|
159
|
+
`maxResults` caps at 100 per page. Use `nextPageToken`:
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
163
|
+
PAGE_TOKEN=''
|
|
164
|
+
while : ; do
|
|
165
|
+
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
166
|
+
--get "https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
167
|
+
--data-urlencode 'maxResults=100' \
|
|
168
|
+
--data-urlencode 'showCompleted=false' \
|
|
169
|
+
${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
|
|
170
|
+
echo "$RESP" | jq -c '.items[]?'
|
|
171
|
+
PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
|
|
172
|
+
[ -z "$PAGE_TOKEN" ] && break
|
|
173
|
+
done
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Common error codes
|
|
177
|
+
|
|
178
|
+
| HTTP | meaning | what to tell the user |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Google Tasks connector on the Connections page." |
|
|
181
|
+
| `403 insufficientPermissions` | scope missing | "This connector is read-only — adding or completing tasks isn't possible." |
|
|
182
|
+
| `404 notFound` | wrong list / task id | re-list with `users/@me/lists` to find the right id. |
|
|
183
|
+
| `429 quotaExceeded` | quota / throttling | back off ~5s, then retry once. |
|
|
184
|
+
|
|
185
|
+
Never log or echo `$GOOGLE_TASKS_TOKEN` — treat it as a secret.
|