@acedatacloud/skills 2026.504.4 → 2026.504.5
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/google-calendar/SKILL.md +160 -14
- package/skills/google-drive/SKILL.md +160 -13
- package/skills/google-gmail/SKILL.md +184 -14
- package/skills/google-tasks/SKILL.md +123 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@acedatacloud/skills",
|
|
3
|
-
"version": "2026.504.
|
|
3
|
+
"version": "2026.504.5",
|
|
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",
|
|
@@ -1,38 +1,48 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: google-calendar
|
|
3
|
-
description: Read Google Calendar events / agenda / free-busy / invitations via the Calendar v3 REST API. Use when the user mentions Google Calendar events, today's agenda, this week's meetings, finding conflicts, listing invitations,
|
|
3
|
+
description: Read and manage Google Calendar events / agenda / free-busy / invitations via the Calendar v3 REST API. Use when the user mentions Google Calendar events, today's agenda, this week's meetings, finding conflicts, listing invitations, checking free time, or scheduling / rescheduling / cancelling a meeting.
|
|
4
4
|
when_to_use: |
|
|
5
|
-
Trigger when the user wants to read events
|
|
6
|
-
Calendar — list / search / inspect events, build today's
|
|
7
|
-
week's agenda, check free / busy windows,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Trigger when the user wants to read or manage events on their
|
|
6
|
+
Google Calendar — list / search / inspect events, build today's
|
|
7
|
+
or this week's agenda, check free / busy windows, pull invite
|
|
8
|
+
details, or have the AI create / update / cancel events on their
|
|
9
|
+
behalf and email invites to attendees. The installed connector
|
|
10
|
+
always grants `calendar.readonly`; the user opts in to the
|
|
11
|
+
broader `calendar` scope (full read + write) at install — confirm
|
|
12
|
+
before destructive writes.
|
|
11
13
|
connections: [google/calendar]
|
|
12
14
|
allowed_tools: [Bash]
|
|
13
15
|
license: Apache-2.0
|
|
14
16
|
metadata:
|
|
15
17
|
author: acedatacloud
|
|
16
|
-
version: "1.
|
|
18
|
+
version: "1.1"
|
|
17
19
|
---
|
|
18
20
|
|
|
19
21
|
Drive Google Calendar via `curl + jq`. The user's OAuth bearer token
|
|
20
22
|
is in `$GOOGLE_CALENDAR_TOKEN`; every call needs it as
|
|
21
|
-
`Authorization: Bearer $GOOGLE_CALENDAR_TOKEN`.
|
|
22
|
-
carries
|
|
23
|
-
|
|
23
|
+
`Authorization: Bearer $GOOGLE_CALENDAR_TOKEN`. At minimum the token
|
|
24
|
+
carries `calendar.readonly` plus the identity scopes
|
|
25
|
+
(`openid email profile`); if the user opted in to write at install
|
|
26
|
+
time it also carries the broader `calendar` scope (read + write).
|
|
24
27
|
|
|
25
28
|
The Calendar API returns standard JSON; failures surface as
|
|
26
29
|
`{"error": {"code": 401|403|..., "message": "..."}}` — show that
|
|
27
30
|
error verbatim. `401` means the token expired (re-install). `403
|
|
28
|
-
insufficientPermissions` means the user
|
|
29
|
-
|
|
31
|
+
insufficientPermissions` on a write means the user only granted
|
|
32
|
+
`calendar.readonly` — ask them to re-install the connector with the
|
|
33
|
+
read+write box checked.
|
|
30
34
|
|
|
31
35
|
**Always start with `users/me/calendarList`** to learn which calendars
|
|
32
36
|
the account can see (the user's primary plus any subscribed / shared
|
|
33
37
|
ones), AND with `users/me/settings/timezone` so you render times in
|
|
34
38
|
the user's local zone instead of UTC.
|
|
35
39
|
|
|
40
|
+
**Before any destructive write** (creating, moving, or cancelling an
|
|
41
|
+
event that has attendees) show the exact event details and ask the
|
|
42
|
+
user to confirm. When attendees are involved, also confirm whether
|
|
43
|
+
they want Google to email the attendees — that's controlled by the
|
|
44
|
+
`sendUpdates` query parameter.
|
|
45
|
+
|
|
36
46
|
## Recipes
|
|
37
47
|
|
|
38
48
|
### Verify auth + discover calendars (always run first)
|
|
@@ -182,14 +192,150 @@ while : ; do
|
|
|
182
192
|
done
|
|
183
193
|
```
|
|
184
194
|
|
|
195
|
+
## Write recipes
|
|
196
|
+
|
|
197
|
+
These all need the broader `calendar` scope. If the user only granted
|
|
198
|
+
`calendar.readonly` you'll get `403 insufficientPermissions` —
|
|
199
|
+
surface that and ask them to re-install with the read+write box
|
|
200
|
+
checked. **Always echo the event summary, time and attendee list
|
|
201
|
+
back to the user before creating or cancelling anything.**
|
|
202
|
+
|
|
203
|
+
### Create a single event (with optional attendees + Google Meet link)
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
207
|
+
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
|
|
208
|
+
|
|
209
|
+
cat > /tmp/_cal_event.json <<JSON
|
|
210
|
+
{
|
|
211
|
+
"summary": "Sync — Q2 OKR review",
|
|
212
|
+
"location": "Online",
|
|
213
|
+
"description": "Drafted by AceDataCloud.",
|
|
214
|
+
"start": {"dateTime": "2026-05-12T10:00:00", "timeZone": "$TZ"},
|
|
215
|
+
"end": {"dateTime": "2026-05-12T10:30:00", "timeZone": "$TZ"},
|
|
216
|
+
"attendees": [
|
|
217
|
+
{"email": "alice@example.com"},
|
|
218
|
+
{"email": "bob@example.com"}
|
|
219
|
+
],
|
|
220
|
+
"reminders": {"useDefault": true},
|
|
221
|
+
"conferenceData": {
|
|
222
|
+
"createRequest": {
|
|
223
|
+
"requestId": "meet-$(date +%s)",
|
|
224
|
+
"conferenceSolutionKey": {"type": "hangoutsMeet"}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
JSON
|
|
229
|
+
|
|
230
|
+
# sendUpdates: 'all' = email all attendees; 'externalOnly' = only non-org; 'none' = silent
|
|
231
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
232
|
+
-H 'Content-Type: application/json' \
|
|
233
|
+
--data @/tmp/_cal_event.json \
|
|
234
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all" \
|
|
235
|
+
| jq '{id, htmlLink, hangoutLink, summary, start, end, attendees}'
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Drop the `conferenceData` block if the user didn't ask for a Meet
|
|
239
|
+
link — it'll fall back to a plain event.
|
|
240
|
+
|
|
241
|
+
### Create a recurring event
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
245
|
+
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
|
|
246
|
+
cat > /tmp/_cal_recur.json <<JSON
|
|
247
|
+
{
|
|
248
|
+
"summary": "Weekly 1:1",
|
|
249
|
+
"start": {"dateTime": "2026-05-12T15:00:00", "timeZone": "$TZ"},
|
|
250
|
+
"end": {"dateTime": "2026-05-12T15:30:00", "timeZone": "$TZ"},
|
|
251
|
+
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"]
|
|
252
|
+
}
|
|
253
|
+
JSON
|
|
254
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
255
|
+
-H 'Content-Type: application/json' \
|
|
256
|
+
--data @/tmp/_cal_recur.json \
|
|
257
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events" \
|
|
258
|
+
| jq '{id, recurrence, summary}'
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
RRULE follows RFC 5545. Common patterns: `FREQ=DAILY`, `FREQ=WEEKLY;BYDAY=MO,WE,FR`,
|
|
262
|
+
`FREQ=MONTHLY;BYMONTHDAY=15`. Add `UNTIL=20261231T235959Z` or `COUNT=12`
|
|
263
|
+
for a hard stop.
|
|
264
|
+
|
|
265
|
+
### Update an existing event (PATCH — partial update)
|
|
266
|
+
|
|
267
|
+
```sh
|
|
268
|
+
EVENT_ID='abc123def4567890ghijklmnop'
|
|
269
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
270
|
+
-H 'Content-Type: application/json' \
|
|
271
|
+
--data '{"location":"Conference Room 4","description":"Now in-person."}' \
|
|
272
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
|
|
273
|
+
| jq '{id, summary, location, description}'
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`PATCH` only changes the fields you send; `PUT` replaces the entire
|
|
277
|
+
event payload. Prefer `PATCH`.
|
|
278
|
+
|
|
279
|
+
### Reschedule an event
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
EVENT_ID='abc123def4567890ghijklmnop'
|
|
283
|
+
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
284
|
+
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
|
|
285
|
+
cat > /tmp/_cal_resched.json <<JSON
|
|
286
|
+
{
|
|
287
|
+
"start": {"dateTime": "2026-05-12T14:00:00", "timeZone": "$TZ"},
|
|
288
|
+
"end": {"dateTime": "2026-05-12T14:30:00", "timeZone": "$TZ"}
|
|
289
|
+
}
|
|
290
|
+
JSON
|
|
291
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
292
|
+
-H 'Content-Type: application/json' \
|
|
293
|
+
--data @/tmp/_cal_resched.json \
|
|
294
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
|
|
295
|
+
| jq '{id, summary, start, end}'
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Add or change attendees
|
|
299
|
+
|
|
300
|
+
Google requires you to send the **complete** attendee list when
|
|
301
|
+
patching attendees — fetch the current list, mutate, send back:
|
|
302
|
+
|
|
303
|
+
```sh
|
|
304
|
+
EVENT_ID='abc123def4567890ghijklmnop'
|
|
305
|
+
CURRENT=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
306
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?fields=attendees" \
|
|
307
|
+
| jq '.attendees // []')
|
|
308
|
+
NEW=$(echo "$CURRENT" | jq '. + [{"email":"carol@example.com"}]')
|
|
309
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
310
|
+
-H 'Content-Type: application/json' \
|
|
311
|
+
--data "{\"attendees\": $NEW}" \
|
|
312
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
|
|
313
|
+
| jq '{id, attendees}'
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Cancel / delete an event
|
|
317
|
+
|
|
318
|
+
```sh
|
|
319
|
+
EVENT_ID='abc123def4567890ghijklmnop'
|
|
320
|
+
curl -sS -X DELETE -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
|
|
321
|
+
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
|
|
322
|
+
-o /dev/null -w 'HTTP %{http_code}\n'
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
`204` = success. To cancel one occurrence of a recurring event, fetch
|
|
326
|
+
the instance with `events.instances` first, then `DELETE` the
|
|
327
|
+
specific instance id (it has a longer `EVENT_ID_YYYYMMDDTHHMMSSZ`
|
|
328
|
+
shape).
|
|
329
|
+
|
|
185
330
|
## Common error codes
|
|
186
331
|
|
|
187
332
|
| HTTP | meaning | what to tell the user |
|
|
188
333
|
|---|---|---|
|
|
189
334
|
| `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Google Calendar connector on the Connections page." |
|
|
190
|
-
| `403 insufficientPermissions` | scope missing | "This
|
|
335
|
+
| `403 insufficientPermissions` | write scope missing | "This action needs the Calendar read+write scope, but only `calendar.readonly` was granted. Re-install the connector with the read+write box checked." |
|
|
191
336
|
| `403 forbidden` | calendar id not visible to this account | check `calendarList` first; if it's a shared calendar, the owner needs to share it. |
|
|
192
337
|
| `404 notFound` | wrong event / calendar id | double-check the id and try `calendarList` to confirm the calendar exists. |
|
|
338
|
+
| `409 conflict` | recurring event id collision | append a UUID to your `requestId` and retry. |
|
|
193
339
|
| `429 quotaExceeded` | quota / throttling | back off ~5s, then retry once. |
|
|
194
340
|
|
|
195
341
|
Never log or echo `$GOOGLE_CALENDAR_TOKEN` — treat it as a secret.
|
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: google-drive
|
|
3
|
-
description: Read and
|
|
3
|
+
description: Read, search, upload, rename, move and delete 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, searching by name / owner / folder, uploading a new file, renaming or moving files, or organising folders.
|
|
4
4
|
when_to_use: |
|
|
5
|
-
Trigger when the user wants to list, search, read
|
|
6
|
-
in their Google Drive — including Google-native docs
|
|
7
|
-
Slides) which need a special "export" call to get
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Trigger when the user wants to list, search, read, download or
|
|
6
|
+
modify files in their Google Drive — including Google-native docs
|
|
7
|
+
(Docs / Sheets / Slides) which need a special "export" call to get
|
|
8
|
+
plain content, as well as uploads, renames, folder moves, and
|
|
9
|
+
trashing files. The installed connector always grants `drive.readonly`;
|
|
10
|
+
the user opts in to the broader `drive` scope (full read + write)
|
|
11
|
+
at install time — confirm before performing destructive writes.
|
|
10
12
|
connections: [google/drive]
|
|
11
13
|
allowed_tools: [Bash]
|
|
12
14
|
license: Apache-2.0
|
|
13
15
|
metadata:
|
|
14
16
|
author: acedatacloud
|
|
15
|
-
version: "1.
|
|
17
|
+
version: "1.1"
|
|
16
18
|
---
|
|
17
19
|
|
|
18
20
|
Drive Google Drive via `curl + jq`. The user's OAuth bearer token is
|
|
19
21
|
in `$GOOGLE_DRIVE_TOKEN`; every call needs it as
|
|
20
|
-
`Authorization: Bearer $GOOGLE_DRIVE_TOKEN`.
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
`Authorization: Bearer $GOOGLE_DRIVE_TOKEN`. At minimum the token
|
|
23
|
+
carries `drive.readonly` plus the identity scopes
|
|
24
|
+
(`openid email profile`); if the user opted in to write at install
|
|
25
|
+
time it also carries the broader `drive` scope (full read + write).
|
|
23
26
|
|
|
24
27
|
The Drive API returns standard JSON; failures surface as
|
|
25
28
|
`{"error": {"code": 401|403|..., "message": "..."}}` — show that
|
|
26
29
|
error verbatim to the user. `401` means the token expired and the
|
|
27
30
|
user must re-install the connector. `403 insufficientPermissions`
|
|
28
|
-
means the
|
|
29
|
-
|
|
31
|
+
on a write means the user did not grant the `drive` scope at install
|
|
32
|
+
— ask them to re-install with the read+write box checked.
|
|
33
|
+
|
|
34
|
+
**Before any destructive write** (renaming, moving, trashing, or
|
|
35
|
+
bulk-mutating files) show the exact target list and ask the user to
|
|
36
|
+
confirm. Never trash by guessing an id — always echo back the file
|
|
37
|
+
name + path you're about to touch.
|
|
30
38
|
|
|
31
39
|
**Always start with `/about?fields=user`** to confirm the connection
|
|
32
40
|
works AND learn which Google account you're operating against.
|
|
@@ -188,14 +196,153 @@ while : ; do
|
|
|
188
196
|
done
|
|
189
197
|
```
|
|
190
198
|
|
|
199
|
+
## Write recipes
|
|
200
|
+
|
|
201
|
+
These all need the broader `drive` scope. If the user only granted
|
|
202
|
+
`drive.readonly` you'll get `403 insufficientPermissions` — surface
|
|
203
|
+
that and suggest re-installing with the read+write box checked.
|
|
204
|
+
**Always echo the target name + path back to the user before
|
|
205
|
+
trashing or bulk-moving anything.**
|
|
206
|
+
|
|
207
|
+
### Rename a file
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
FILE_ID='1A2B3CdEfGhIjKlMn'
|
|
211
|
+
NEW_NAME='2026 Q2 OKR (final).gdoc'
|
|
212
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
213
|
+
-H 'Content-Type: application/json' \
|
|
214
|
+
--data "{\"name\":$(jq -nr --arg n "$NEW_NAME" '$n')}" \
|
|
215
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name"
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Move a file to a different folder
|
|
219
|
+
|
|
220
|
+
Drive's folder model is parent-id based. Move = remove old parent,
|
|
221
|
+
add new parent:
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
FILE_ID='1A2B3CdEfGhIjKlMn'
|
|
225
|
+
NEW_PARENT='1XYZnewFolderId'
|
|
226
|
+
|
|
227
|
+
# Read existing parents (so we can pass them in removeParents)
|
|
228
|
+
OLD_PARENTS=$(curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
229
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=parents" \
|
|
230
|
+
| jq -r '.parents | join(",")')
|
|
231
|
+
|
|
232
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
233
|
+
--data '' \
|
|
234
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID?addParents=$NEW_PARENT&removeParents=$OLD_PARENTS&fields=id,name,parents"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Create a new folder
|
|
238
|
+
|
|
239
|
+
```sh
|
|
240
|
+
PARENT_ID='1XYZparentFolderId' # or 'root' for My Drive root
|
|
241
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
242
|
+
-H 'Content-Type: application/json' \
|
|
243
|
+
--data "{\"name\":\"Reports / 2026Q2\",\"mimeType\":\"application/vnd.google-apps.folder\",\"parents\":[\"$PARENT_ID\"]}" \
|
|
244
|
+
"https://www.googleapis.com/drive/v3/files?fields=id,name,webViewLink" \
|
|
245
|
+
| jq
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Upload a file (multipart so metadata + bytes go in one request)
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
LOCAL=/tmp/report.pdf
|
|
252
|
+
NAME='Q2 report.pdf'
|
|
253
|
+
PARENT_ID='1XYZparentFolderId'
|
|
254
|
+
MIME='application/pdf'
|
|
255
|
+
|
|
256
|
+
BOUNDARY='aceDataBoundary'
|
|
257
|
+
META=$(jq -nc --arg n "$NAME" --arg p "$PARENT_ID" '{name:$n, parents:[$p]}')
|
|
258
|
+
{
|
|
259
|
+
printf -- '--%s\r\n' "$BOUNDARY"
|
|
260
|
+
printf 'Content-Type: application/json; charset=UTF-8\r\n\r\n'
|
|
261
|
+
printf '%s\r\n' "$META"
|
|
262
|
+
printf -- '--%s\r\n' "$BOUNDARY"
|
|
263
|
+
printf 'Content-Type: %s\r\n\r\n' "$MIME"
|
|
264
|
+
cat "$LOCAL"
|
|
265
|
+
printf '\r\n--%s--\r\n' "$BOUNDARY"
|
|
266
|
+
} > /tmp/_drive_upload.bin
|
|
267
|
+
|
|
268
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
269
|
+
-H "Content-Type: multipart/related; boundary=$BOUNDARY" \
|
|
270
|
+
--data-binary @/tmp/_drive_upload.bin \
|
|
271
|
+
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id,name,webViewLink" \
|
|
272
|
+
| jq
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
For a **media-only** upload (no metadata) use `uploadType=media`; for
|
|
276
|
+
files >5 MB use `uploadType=resumable` (covered in [Drive's docs]
|
|
277
|
+
(https://developers.google.com/drive/api/guides/manage-uploads#resumable)).
|
|
278
|
+
|
|
279
|
+
### Replace the contents of an existing file
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
FILE_ID='1A2B3CdEfGhIjKlMn'
|
|
283
|
+
LOCAL=/tmp/report-v2.pdf
|
|
284
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
285
|
+
-H 'Content-Type: application/pdf' \
|
|
286
|
+
--data-binary @"$LOCAL" \
|
|
287
|
+
"https://www.googleapis.com/upload/drive/v3/files/$FILE_ID?uploadType=media&fields=id,name,modifiedTime"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Metadata stays the same (id / parents / name) — only the bytes are
|
|
291
|
+
replaced and Drive bumps `modifiedTime`.
|
|
292
|
+
|
|
293
|
+
### Trash a file (or restore one)
|
|
294
|
+
|
|
295
|
+
```sh
|
|
296
|
+
FILE_ID='1A2B3CdEfGhIjKlMn'
|
|
297
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
298
|
+
-H 'Content-Type: application/json' \
|
|
299
|
+
--data '{"trashed":true}' \
|
|
300
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name,trashed"
|
|
301
|
+
|
|
302
|
+
# Restore:
|
|
303
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
304
|
+
-H 'Content-Type: application/json' \
|
|
305
|
+
--data '{"trashed":false}' \
|
|
306
|
+
"https://www.googleapis.com/drive/v3/files/$FILE_ID?fields=id,name,trashed"
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Prefer `trashed:true` over `DELETE` — `DELETE` is permanent and the
|
|
310
|
+
user can't undo it. Only use `DELETE` when they explicitly say
|
|
311
|
+
"permanently delete".
|
|
312
|
+
|
|
313
|
+
### Bulk "move every PDF in the root to /Documents/PDF" (confirmation pattern)
|
|
314
|
+
|
|
315
|
+
```sh
|
|
316
|
+
# 1. List candidates and show the user before doing anything
|
|
317
|
+
DST_FOLDER_ID='1XYZdocsPdfFolder'
|
|
318
|
+
ROOT_ID='root'
|
|
319
|
+
|
|
320
|
+
CANDS=$(curl -sS -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
321
|
+
--get "https://www.googleapis.com/drive/v3/files" \
|
|
322
|
+
--data-urlencode "q='$ROOT_ID' in parents and mimeType='application/pdf' and trashed=false" \
|
|
323
|
+
--data-urlencode 'fields=files(id,name,webViewLink)' \
|
|
324
|
+
| jq '.files')
|
|
325
|
+
echo "$CANDS" | jq -r '.[] | "- \(.name)"'
|
|
326
|
+
|
|
327
|
+
# 2. (after user confirms) actually move
|
|
328
|
+
echo "$CANDS" | jq -r '.[] | .id' | while read FID; do
|
|
329
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_DRIVE_TOKEN" \
|
|
330
|
+
--data '' \
|
|
331
|
+
"https://www.googleapis.com/drive/v3/files/$FID?addParents=$DST_FOLDER_ID&removeParents=$ROOT_ID&fields=id,name,parents" \
|
|
332
|
+
| jq -c '{id, name, parents}'
|
|
333
|
+
done
|
|
334
|
+
```
|
|
335
|
+
|
|
191
336
|
## Common error codes
|
|
192
337
|
|
|
193
338
|
| HTTP | meaning | what to tell the user |
|
|
194
339
|
|---|---|---|
|
|
195
340
|
| `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Google Drive connector on the Connections page." |
|
|
196
|
-
| `403 insufficientPermissions` | scope missing | "
|
|
341
|
+
| `403 insufficientPermissions` | write scope missing | "This action needs the Drive read+write scope, but only `drive.readonly` was granted at install. Re-install the connector and check the read+write box." |
|
|
197
342
|
| `403 userRateLimitExceeded` | quota | retry once after 5–10s; if it persists, tell the user. |
|
|
198
343
|
| `404 notFound` | wrong file id OR file isn't visible to this account | double-check the id; if shared, use `sharedWithMe` query above. |
|
|
199
344
|
| `400 invalidQuery` | malformed `q` | print the `q` you sent + the error message back to the user. |
|
|
200
345
|
|
|
201
346
|
Never log or echo `$GOOGLE_DRIVE_TOKEN` — treat it as a secret.
|
|
347
|
+
|
|
348
|
+
Never log or echo `$GOOGLE_DRIVE_TOKEN` — treat it as a secret.
|
|
@@ -1,31 +1,44 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: google-gmail
|
|
3
|
-
description: Read, search and
|
|
3
|
+
description: Read, search, triage, label, archive and send 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, finding mail by label / query, archiving or labelling a thread, or drafting and sending a reply / new message.
|
|
4
4
|
when_to_use: |
|
|
5
|
-
Trigger when the user wants to read, list, search, summarise
|
|
6
|
-
inspect Gmail mail — including triaging the inbox,
|
|
7
|
-
pulling a single thread
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Trigger when the user wants to read, list, search, summarise,
|
|
6
|
+
inspect, modify or send Gmail mail — including triaging the inbox,
|
|
7
|
+
surfacing unread, pulling a single thread, downloading an
|
|
8
|
+
attachment, archiving / labelling / trashing messages, or having
|
|
9
|
+
the AI draft and send a reply or new message on their behalf.
|
|
10
|
+
The installed connector always grants `gmail.readonly`; the user
|
|
11
|
+
also opts in to `gmail.modify` (label / archive / trash) and
|
|
12
|
+
`gmail.send` (compose + send) at install time — confirm the action
|
|
13
|
+
is in scope before issuing it.
|
|
10
14
|
connections: [google/gmail]
|
|
11
15
|
allowed_tools: [Bash]
|
|
12
16
|
license: Apache-2.0
|
|
13
17
|
metadata:
|
|
14
18
|
author: acedatacloud
|
|
15
|
-
version: "1.
|
|
19
|
+
version: "1.1"
|
|
16
20
|
---
|
|
17
21
|
|
|
18
22
|
Drive Gmail via `curl + jq`. The user's OAuth bearer token is in
|
|
19
23
|
`$GOOGLE_GMAIL_TOKEN`; every call needs it as
|
|
20
|
-
`Authorization: Bearer $GOOGLE_GMAIL_TOKEN`.
|
|
21
|
-
carries
|
|
22
|
-
|
|
24
|
+
`Authorization: Bearer $GOOGLE_GMAIL_TOKEN`. At minimum the token
|
|
25
|
+
carries `gmail.readonly` plus the identity scopes
|
|
26
|
+
(`openid email profile`); if the user opted in to write at install
|
|
27
|
+
time it also carries `gmail.modify` (label / archive / trash) and/or
|
|
28
|
+
`gmail.send` (compose + send). Always assume the narrowest scope
|
|
29
|
+
until a write actually fails — don't ask Google for new scopes from
|
|
30
|
+
here.
|
|
23
31
|
|
|
24
32
|
The Gmail API returns standard JSON; failures surface as
|
|
25
33
|
`{"error": {"code": 401|403|..., "message": "..."}}` — show that
|
|
26
|
-
error verbatim. `401` means the token expired (re-install). `403
|
|
27
|
-
|
|
28
|
-
this
|
|
34
|
+
error verbatim. `401` means the token expired (re-install). `403
|
|
35
|
+
insufficientPermissions` means the user didn't grant the write scope
|
|
36
|
+
this call needs — explain which scope is missing and suggest
|
|
37
|
+
re-installing the connector with the matching write box checked.
|
|
38
|
+
|
|
39
|
+
**Before any destructive write** (trashing a thread, sending an email)
|
|
40
|
+
show the user the exact target / draft and ask them to confirm. Don't
|
|
41
|
+
fan out across many messages without an explicit go-ahead.
|
|
29
42
|
|
|
30
43
|
**Always start with `users/me/profile`** to confirm the connection works
|
|
31
44
|
AND learn which Gmail account you're operating against. Mailbox payloads
|
|
@@ -201,12 +214,169 @@ while : ; do
|
|
|
201
214
|
done
|
|
202
215
|
```
|
|
203
216
|
|
|
217
|
+
## Write recipes
|
|
218
|
+
|
|
219
|
+
These all need `gmail.modify` (label / archive / trash) or
|
|
220
|
+
`gmail.send` (compose + send). If the user only granted
|
|
221
|
+
`gmail.readonly` at install you'll get `403 insufficientPermissions`
|
|
222
|
+
— surface that and ask them to re-install with the write boxes
|
|
223
|
+
checked.
|
|
224
|
+
|
|
225
|
+
### Mark a message read / unread, star it, archive it (gmail.modify)
|
|
226
|
+
|
|
227
|
+
```sh
|
|
228
|
+
MSG_ID='18f1a2b3c4d5e6f0'
|
|
229
|
+
|
|
230
|
+
# Mark as read = remove the UNREAD label
|
|
231
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
232
|
+
-H 'Content-Type: application/json' \
|
|
233
|
+
--data '{"removeLabelIds":["UNREAD"]}' \
|
|
234
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
|
|
235
|
+
|
|
236
|
+
# Star it = add the STARRED label
|
|
237
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
238
|
+
-H 'Content-Type: application/json' \
|
|
239
|
+
--data '{"addLabelIds":["STARRED"]}' \
|
|
240
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
|
|
241
|
+
|
|
242
|
+
# Archive = remove from INBOX (keeps in All Mail)
|
|
243
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
244
|
+
-H 'Content-Type: application/json' \
|
|
245
|
+
--data '{"removeLabelIds":["INBOX"]}' \
|
|
246
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The `modify` endpoint takes `addLabelIds` and `removeLabelIds`
|
|
250
|
+
together — useful for atomic "archive + label" moves. Use the same
|
|
251
|
+
shape on `/threads/$THREAD_ID/modify` to apply across a whole thread.
|
|
252
|
+
|
|
253
|
+
### Apply a custom label
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
# 1. find or remember the label id from labels.list
|
|
257
|
+
LABEL_ID='Label_4'
|
|
258
|
+
MSG_ID='18f1a2b3c4d5e6f0'
|
|
259
|
+
|
|
260
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
261
|
+
-H 'Content-Type: application/json' \
|
|
262
|
+
--data "{\"addLabelIds\":[\"$LABEL_ID\"]}" \
|
|
263
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/modify"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Creating a brand-new label needs the same scope:
|
|
267
|
+
|
|
268
|
+
```sh
|
|
269
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
270
|
+
-H 'Content-Type: application/json' \
|
|
271
|
+
--data '{"name":"Follow up","messageListVisibility":"show","labelListVisibility":"labelShow"}' \
|
|
272
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/labels" \
|
|
273
|
+
| jq '{id, name}'
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Trash a message or thread
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
MSG_ID='18f1a2b3c4d5e6f0'
|
|
280
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
281
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/$MSG_ID/trash"
|
|
282
|
+
|
|
283
|
+
# Whole thread:
|
|
284
|
+
THREAD_ID='18f1a2b3c4d5e6f0'
|
|
285
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
286
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/threads/$THREAD_ID/trash"
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Use `/untrash` (same shape) to restore. **Never** use
|
|
290
|
+
`messages.delete` — it permanently deletes and needs a higher scope
|
|
291
|
+
that we don't request.
|
|
292
|
+
|
|
293
|
+
### Send a brand-new email (gmail.send)
|
|
294
|
+
|
|
295
|
+
Gmail wants the message as a base64url-encoded RFC 2822 string.
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
# Compose the message
|
|
299
|
+
TO='alice@example.com'
|
|
300
|
+
SUBJECT='Quick hello'
|
|
301
|
+
BODY='Hi Alice,
|
|
302
|
+
|
|
303
|
+
Just a quick test note from the AceDataCloud Gmail connector.
|
|
304
|
+
|
|
305
|
+
Best,
|
|
306
|
+
Qingcai'
|
|
307
|
+
|
|
308
|
+
# Multi-line subject lines need MIME encoded-word for non-ASCII; ASCII is fine raw.
|
|
309
|
+
RAW=$(printf 'To: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\nMIME-Version: 1.0\r\n\r\n%s' \
|
|
310
|
+
"$TO" "$SUBJECT" "$BODY" \
|
|
311
|
+
| base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
|
|
312
|
+
|
|
313
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
314
|
+
-H 'Content-Type: application/json' \
|
|
315
|
+
--data "{\"raw\":\"$RAW\"}" \
|
|
316
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/send" \
|
|
317
|
+
| jq '{id, threadId, labelIds}'
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
For non-ASCII subjects (Chinese / emoji), use MIME encoded-word:
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
SUBJECT_RAW='你好,季度复盘草稿'
|
|
324
|
+
SUBJECT_ENCODED="=?UTF-8?B?$(printf %s "$SUBJECT_RAW" | base64)?="
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Reply in-thread (keeps the thread together)
|
|
328
|
+
|
|
329
|
+
Reply by setting the `In-Reply-To` and `References` headers to the
|
|
330
|
+
Message-Id of the message you're replying to, **and** pass the
|
|
331
|
+
Gmail thread id in the API body:
|
|
332
|
+
|
|
333
|
+
```sh
|
|
334
|
+
ORIG_MSG_ID='18f1a2b3c4d5e6f0'
|
|
335
|
+
ORIG=$(curl -sS -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
336
|
+
--get "https://gmail.googleapis.com/gmail/v1/users/me/messages/$ORIG_MSG_ID" \
|
|
337
|
+
--data-urlencode 'format=metadata' \
|
|
338
|
+
--data-urlencode 'metadataHeaders=Message-ID' \
|
|
339
|
+
--data-urlencode 'metadataHeaders=Subject' \
|
|
340
|
+
--data-urlencode 'metadataHeaders=From')
|
|
341
|
+
MID=$(echo "$ORIG" | jq -r '.payload.headers | from_entries | .["Message-ID"] // .["Message-Id"]')
|
|
342
|
+
FROM=$(echo "$ORIG" | jq -r '.payload.headers | from_entries | .From')
|
|
343
|
+
SUBJ=$(echo "$ORIG" | jq -r '.payload.headers | from_entries | .Subject')
|
|
344
|
+
TID=$(echo "$ORIG" | jq -r .threadId)
|
|
345
|
+
|
|
346
|
+
RAW=$(printf 'To: %s\r\nSubject: Re: %s\r\nIn-Reply-To: %s\r\nReferences: %s\r\nContent-Type: text/plain; charset=UTF-8\r\nMIME-Version: 1.0\r\n\r\n%s' \
|
|
347
|
+
"$FROM" "$SUBJ" "$MID" "$MID" \
|
|
348
|
+
'Replying inline — will follow up later today.' \
|
|
349
|
+
| base64 | tr -d '\n' | tr '+/' '-_' | tr -d '=')
|
|
350
|
+
|
|
351
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
352
|
+
-H 'Content-Type: application/json' \
|
|
353
|
+
--data "{\"raw\":\"$RAW\",\"threadId\":\"$TID\"}" \
|
|
354
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/messages/send" \
|
|
355
|
+
| jq '{id, threadId}'
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Without the `threadId` in the body Gmail starts a brand-new thread
|
|
359
|
+
even with the right `In-Reply-To` headers.
|
|
360
|
+
|
|
361
|
+
### Save a draft instead of sending
|
|
362
|
+
|
|
363
|
+
Same `raw` payload, different endpoint — still costs `gmail.send`
|
|
364
|
+
(`drafts` shares the send scope under the hood for write):
|
|
365
|
+
|
|
366
|
+
```sh
|
|
367
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_GMAIL_TOKEN" \
|
|
368
|
+
-H 'Content-Type: application/json' \
|
|
369
|
+
--data "{\"message\":{\"raw\":\"$RAW\"}}" \
|
|
370
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/drafts" \
|
|
371
|
+
| jq '{id, message: {id: .message.id, threadId: .message.threadId}}'
|
|
372
|
+
```
|
|
373
|
+
|
|
204
374
|
## Common error codes
|
|
205
375
|
|
|
206
376
|
| HTTP | meaning | what to tell the user |
|
|
207
377
|
|---|---|---|
|
|
208
378
|
| `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Gmail connector on the Connections page." |
|
|
209
|
-
| `403 insufficientPermissions` | scope missing |
|
|
379
|
+
| `403 insufficientPermissions` | scope missing | identify which scope (`gmail.modify` for label/archive/trash, `gmail.send` for sending) and suggest re-installing the connector with that box checked. |
|
|
210
380
|
| `403 userRateLimitExceeded` / `429` | quota / throttling | back off ~5s, then retry once. |
|
|
211
381
|
| `404 notFound` | wrong message / thread / attachment id | double-check the id, or fall back to `messages.list` with the right query. |
|
|
212
382
|
| `400 invalidQuery` | malformed `q` | print the `q` you sent + the error back to the user. |
|
|
@@ -1,36 +1,43 @@
|
|
|
1
1
|
---
|
|
2
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,
|
|
3
|
+
description: Read and manage 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, grouping todos by list, adding or completing a task, or moving / deleting tasks.
|
|
4
4
|
when_to_use: |
|
|
5
|
-
Trigger when the user wants to inspect their Google
|
|
6
|
-
task lists, surface pending items, group by due date,
|
|
7
|
-
details for one task
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Trigger when the user wants to inspect or manage their Google
|
|
6
|
+
Tasks — list task lists, surface pending items, group by due date,
|
|
7
|
+
pull details for one task, add new todos, mark items complete,
|
|
8
|
+
re-order or delete tasks. The installed connector always grants
|
|
9
|
+
`tasks.readonly`; the user opts in to the broader `tasks` scope
|
|
10
|
+
(full read + write) at install — confirm before destructive writes.
|
|
10
11
|
connections: [google/tasks]
|
|
11
12
|
allowed_tools: [Bash]
|
|
12
13
|
license: Apache-2.0
|
|
13
14
|
metadata:
|
|
14
15
|
author: acedatacloud
|
|
15
|
-
version: "1.
|
|
16
|
+
version: "1.1"
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
Drive Google Tasks via `curl + jq`. The user's OAuth bearer token is
|
|
19
20
|
in `$GOOGLE_TASKS_TOKEN`; every call needs it as
|
|
20
|
-
`Authorization: Bearer $GOOGLE_TASKS_TOKEN`.
|
|
21
|
-
carries
|
|
22
|
-
|
|
21
|
+
`Authorization: Bearer $GOOGLE_TASKS_TOKEN`. At minimum the token
|
|
22
|
+
carries `tasks.readonly` plus the identity scopes
|
|
23
|
+
(`openid email profile`); if the user opted in to write at install
|
|
24
|
+
time it also carries the broader `tasks` scope (read + write).
|
|
23
25
|
|
|
24
26
|
The Tasks API returns standard JSON; failures surface as
|
|
25
27
|
`{"error": {"code": 401|403|..., "message": "..."}}` — show that
|
|
26
28
|
error verbatim. `401` means the token expired (re-install). `403
|
|
27
|
-
insufficientPermissions` means the user
|
|
28
|
-
|
|
29
|
+
insufficientPermissions` on a write means the user only granted
|
|
30
|
+
`tasks.readonly` — ask them to re-install with the read+write box
|
|
31
|
+
checked.
|
|
29
32
|
|
|
30
33
|
**Always start with `users/@me/lists`** to discover which task lists
|
|
31
34
|
the account has — the user's default plus any extras they created on
|
|
32
35
|
calendar.google.com or in the Tasks app.
|
|
33
36
|
|
|
37
|
+
**Before bulk creates / completions / deletes** echo the exact
|
|
38
|
+
titles back to the user and ask them to confirm. Don't trash a
|
|
39
|
+
task by guessing an id.
|
|
40
|
+
|
|
34
41
|
## Recipes
|
|
35
42
|
|
|
36
43
|
### Verify auth + list all task lists (always run first)
|
|
@@ -173,12 +180,115 @@ while : ; do
|
|
|
173
180
|
done
|
|
174
181
|
```
|
|
175
182
|
|
|
183
|
+
## Write recipes
|
|
184
|
+
|
|
185
|
+
These all need the broader `tasks` scope. If the user only granted
|
|
186
|
+
`tasks.readonly` you'll get `403 insufficientPermissions` — surface
|
|
187
|
+
that and ask them to re-install with the read+write box checked.
|
|
188
|
+
|
|
189
|
+
### Add a new task
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
193
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
194
|
+
-H 'Content-Type: application/json' \
|
|
195
|
+
--data '{"title":"Draft Q2 plan","notes":"Outline + risks + asks.","due":"2026-05-15T00:00:00.000Z"}' \
|
|
196
|
+
"https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
197
|
+
| jq '{id, title, due, status}'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Google stores `due` as midnight UTC of the chosen day — the time of
|
|
201
|
+
day is ignored in the UI. To insert at the very top of the list,
|
|
202
|
+
add `?previous=` (no value) to the URL.
|
|
203
|
+
|
|
204
|
+
### Bulk add three tasks under user confirmation
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
208
|
+
DUE='2026-05-12T00:00:00.000Z'
|
|
209
|
+
for T in 'Reply to Alice' 'Review PR #404' 'Send meeting recap'; do
|
|
210
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
211
|
+
-H 'Content-Type: application/json' \
|
|
212
|
+
--data "{\"title\":$(jq -nr --arg t "$T" '$t'),\"due\":\"$DUE\"}" \
|
|
213
|
+
"https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks" \
|
|
214
|
+
| jq -c '{id, title, due}'
|
|
215
|
+
done
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Always list the titles you're about to create and ask for the user's
|
|
219
|
+
go-ahead before running this loop — there is no atomic batch endpoint.
|
|
220
|
+
|
|
221
|
+
### Mark a task complete
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
225
|
+
TASK_ID='dGFza0lkRXhhbXBsZQ'
|
|
226
|
+
NOW=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
|
|
227
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
228
|
+
-H 'Content-Type: application/json' \
|
|
229
|
+
--data "{\"status\":\"completed\",\"completed\":\"$NOW\"}" \
|
|
230
|
+
"https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
|
|
231
|
+
| jq '{id, title, status, completed}'
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Reverse with `{"status":"needsAction","completed":null}`.
|
|
235
|
+
|
|
236
|
+
### Edit a task's title / notes / due date
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
240
|
+
TASK_ID='dGFza0lkRXhhbXBsZQ'
|
|
241
|
+
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
242
|
+
-H 'Content-Type: application/json' \
|
|
243
|
+
--data '{"title":"Draft Q2 plan (rev2)","notes":"Cover risks + asks + budget.","due":"2026-05-20T00:00:00.000Z"}' \
|
|
244
|
+
"https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
|
|
245
|
+
| jq '{id, title, due, notes}'
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Delete a task
|
|
249
|
+
|
|
250
|
+
```sh
|
|
251
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
252
|
+
TASK_ID='dGFza0lkRXhhbXBsZQ'
|
|
253
|
+
curl -sS -X DELETE -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
254
|
+
"https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID" \
|
|
255
|
+
-o /dev/null -w 'HTTP %{http_code}\n'
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`204` = success. There is no soft-delete — once gone the task is
|
|
259
|
+
gone. Echo the title back before deleting.
|
|
260
|
+
|
|
261
|
+
### Re-order: move a task to a position
|
|
262
|
+
|
|
263
|
+
```sh
|
|
264
|
+
LIST_ID='MTAxMjM0NTY3OA'
|
|
265
|
+
TASK_ID='dGFza0lkRXhhbXBsZQ'
|
|
266
|
+
PREV='dGFza0lkUHJldg' # task id this one should appear AFTER; omit to move to top
|
|
267
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
268
|
+
--data '' \
|
|
269
|
+
"https://tasks.googleapis.com/tasks/v1/lists/$LIST_ID/tasks/$TASK_ID/move?previous=$PREV" \
|
|
270
|
+
| jq '{id, title, parent, position}'
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Use `?parent=...` instead of `?previous=...` to nest a task under
|
|
274
|
+
another task as a sub-task.
|
|
275
|
+
|
|
276
|
+
### Create a brand-new task list
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_TASKS_TOKEN" \
|
|
280
|
+
-H 'Content-Type: application/json' \
|
|
281
|
+
--data '{"title":"Q2 follow-ups"}' \
|
|
282
|
+
"https://tasks.googleapis.com/tasks/v1/users/@me/lists" \
|
|
283
|
+
| jq '{id, title}'
|
|
284
|
+
```
|
|
285
|
+
|
|
176
286
|
## Common error codes
|
|
177
287
|
|
|
178
288
|
| HTTP | meaning | what to tell the user |
|
|
179
289
|
|---|---|---|
|
|
180
290
|
| `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Google Tasks connector on the Connections page." |
|
|
181
|
-
| `403 insufficientPermissions` | scope missing | "This
|
|
291
|
+
| `403 insufficientPermissions` | write scope missing | "This action needs the Tasks read+write scope, but only `tasks.readonly` was granted. Re-install the connector with the read+write box checked." |
|
|
182
292
|
| `404 notFound` | wrong list / task id | re-list with `users/@me/lists` to find the right id. |
|
|
183
293
|
| `429 quotaExceeded` | quota / throttling | back off ~5s, then retry once. |
|
|
184
294
|
|