@acedatacloud/skills 2026.504.1 → 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 CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  Compatible with **30+ AI coding agents** via the [agentskills.io](https://agentskills.io/) open standard: Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, Cursor, Roo Code, Goose, and more.
14
14
 
15
- ## Available Skills (19)
15
+ ## Available Skills (30)
16
16
 
17
17
  ### AI Music & Audio
18
18
 
@@ -52,6 +52,24 @@ Compatible with **30+ AI coding agents** via the [agentskills.io](https://agents
52
52
  | [short-url](skills/short-url/) | Create and manage short URLs |
53
53
  | [acedatacloud-api](skills/acedatacloud-api/) | API usage guide — authentication, SDKs, error handling |
54
54
 
55
+ ### Connectors
56
+
57
+ These skills drive third-party connectors users wire up at [auth.acedata.cloud/user/connections](https://auth.acedata.cloud/user/connections). Each declares the OAuth / BYOC connection it needs in `connections:` frontmatter; the [aichat2](https://chat.acedata.cloud) runtime injects the matching access token into the sandbox before the skill's `Bash` calls run.
58
+
59
+ | Skill | Description | Connection |
60
+ |-------|-------------|------------|
61
+ | [github](skills/github/) | GitHub issues, pull requests, repos, code search, and Actions via `gh` CLI | `github` |
62
+ | [gitlab](skills/gitlab/) | GitLab issues, MRs, projects, pipelines via the `glab` CLI | `gitlab` |
63
+ | [google-drive](skills/google-drive/) | List, search, and read Google Drive files via the Drive v3 REST API | `google/drive` |
64
+ | [google-gmail](skills/google-gmail/) | Read, search, and triage Gmail messages via the Gmail v1 REST API | `google/gmail` |
65
+ | [google-calendar](skills/google-calendar/) | Read calendar events, agenda, and free-busy windows via Calendar v3 | `google/calendar` |
66
+ | [google-tasks](skills/google-tasks/) | List and inspect Google Tasks via the Tasks v1 REST API | `google/tasks` |
67
+ | [microsoft-onedrive](skills/microsoft-onedrive/) | List and read OneDrive files via the Microsoft Graph API | `microsoft/onedrive` |
68
+ | [microsoft-outlook](skills/microsoft-outlook/) | Read Outlook mail, calendar events, and contacts via Microsoft Graph | `microsoft/outlook` |
69
+ | [notion](skills/notion/) | Read and search Notion pages, databases, and blocks | `notion` |
70
+ | [slack](skills/slack/) | Read Slack channels, messages, and user info via Web API | `slack` |
71
+ | [wechat-official-account](skills/wechat-official-account/) | Manage WeChat MP — drafts, publishing, materials, user tags | `wechat` (BYOC) |
72
+
55
73
  ## Prerequisites
56
74
 
57
75
  Get your API token at [platform.acedata.cloud](https://platform.acedata.cloud):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acedatacloud/skills",
3
- "version": "2026.504.1",
3
+ "version": "2026.504.2",
4
4
  "description": "Agent Skills for AceDataCloud AI services — music, image, video generation, LLM chat, web search. Compatible with Claude Code, GitHub Copilot, Gemini CLI, OpenAI Codex, and 30+ AI coding agents.",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -0,0 +1,173 @@
1
+ ---
2
+ name: github
3
+ description: GitHub issues, pull requests, repos, code search, and Actions via the gh CLI. Use when the user mentions GitHub, an issue/PR number, a repo, a commit, or code review.
4
+ when_to_use: |
5
+ Trigger when the user wants to read or write something on GitHub —
6
+ list / view / create / comment on issues or PRs, search code, view
7
+ a repo, view CI runs, etc.
8
+ connections: [github]
9
+ allowed_tools: [Bash]
10
+ license: Apache-2.0
11
+ metadata:
12
+ author: acedatacloud
13
+ version: "1.0"
14
+ ---
15
+
16
+ Use the `gh` CLI for everything. The user's OAuth access token is exported
17
+ as `$GH_TOKEN`; `gh` reads it automatically — `gh auth status` will say
18
+ "not logged in" because gh keeps no config file in the sandbox, but every
19
+ authenticated subcommand works regardless.
20
+
21
+ `gh --help` and `gh <subcommand> --help` are always current. When unsure,
22
+ read the help first instead of guessing flags.
23
+
24
+ ## Two ways to call gh — prefer subcommands
25
+
26
+ ### Style A: First-class subcommands — START HERE
27
+
28
+ `gh issue`, `gh pr`, `gh repo`, `gh search`, `gh release`, `gh workflow`,
29
+ `gh run`, `gh status`, `gh project`, `gh label`, `gh secret`,
30
+ `gh variable`, `gh gist`. Use these whenever they cover the task; they
31
+ output formatted text by default and structured JSON via
32
+ `--json <fields> [--jq <expr>]`.
33
+
34
+ ### Style B: Raw REST / GraphQL via `gh api`
35
+
36
+ `gh api <endpoint>` for REST, `gh api graphql -f query='…'` for GraphQL.
37
+ Useful when no first-class subcommand exists. Notable flags:
38
+
39
+ - `-X POST|PATCH|PUT|DELETE` — override method (default `GET`, becomes
40
+ `POST` automatically when `-f`/`-F` is set).
41
+ - `-f key=value` — string field; `-F key=value` — JSON-typed field
42
+ (`true`/`123`/`@file.json`); both URL-encode for `GET` and JSON-encode
43
+ for body methods.
44
+ - `-q '<jq>'` — same as `--jq`. With a primitive top-level value (string
45
+ / number) it prints the raw value (no quotes).
46
+ - `-H 'Accept: application/vnd.github.raw'` — fetch a file's raw bytes
47
+ instead of the JSON wrapper.
48
+ - `--paginate` — auto-walk `Link: rel="next"`.
49
+
50
+ ## Recipes
51
+
52
+ ### Triage what's on my plate (issues + PRs + reviews + mentions)
53
+
54
+ ```sh
55
+ gh status
56
+ ```
57
+
58
+ ### List recent issues in a repo
59
+
60
+ ```sh
61
+ gh issue list --repo OWNER/REPO --limit 20
62
+ gh issue list --repo OWNER/REPO --state all --limit 20 \
63
+ --json number,title,state,author,updatedAt,labels --jq '.[]'
64
+ ```
65
+
66
+ ### View an issue with comments
67
+
68
+ ```sh
69
+ gh issue view 123 --repo OWNER/REPO --comments
70
+ gh issue view 123 --repo OWNER/REPO --json title,body,comments \
71
+ --jq '{title, body, comments: [.comments[] | {author: .author.login, body, createdAt}]}'
72
+ ```
73
+
74
+ ### Create / comment / close an issue
75
+
76
+ ```sh
77
+ gh issue create --repo OWNER/REPO --title "Bug: foo" --body "Repro steps…" --label bug
78
+ gh issue comment 123 --repo OWNER/REPO --body "LGTM"
79
+ gh issue close 123 --repo OWNER/REPO --comment "Fixed in #456"
80
+ ```
81
+
82
+ ### List PRs assigned to / authored by me
83
+
84
+ ```sh
85
+ gh search prs --assignee=@me --state=open --json number,title,repository,updatedAt
86
+ gh search prs --author=@me --state=open
87
+ ```
88
+
89
+ ### View a PR with diff and CI checks
90
+
91
+ ```sh
92
+ gh pr view 456 --repo OWNER/REPO
93
+ gh pr diff 456 --repo OWNER/REPO
94
+ gh pr checks 456 --repo OWNER/REPO
95
+ ```
96
+
97
+ ### Comment / review / merge a PR
98
+
99
+ ```sh
100
+ gh pr comment 456 --repo OWNER/REPO --body "Please rebase on main."
101
+ gh pr review 456 --repo OWNER/REPO --approve --body "LGTM"
102
+ gh pr review 456 --repo OWNER/REPO --request-changes --body "See nits"
103
+ gh pr merge 456 --repo OWNER/REPO --squash --delete-branch
104
+ ```
105
+
106
+ ### Search code across GitHub
107
+
108
+ ```sh
109
+ gh search code 'someFunction language:typescript' --limit 20 \
110
+ --json repository,path,url --jq '.[] | "\(.repository.nameWithOwner) \(.path)"'
111
+ ```
112
+
113
+ ### Read a file from a repo (raw bytes, no base64 dance)
114
+
115
+ ```sh
116
+ gh api "repos/OWNER/REPO/contents/path/to/file.ts" \
117
+ -H 'Accept: application/vnd.github.raw'
118
+ ```
119
+
120
+ ### List recent commits on the default branch
121
+
122
+ ```sh
123
+ gh api "repos/OWNER/REPO/commits?per_page=20" \
124
+ --jq '.[] | "\(.sha[0:7]) \(.commit.author.date) \(.commit.message | split("\n")[0])"'
125
+ ```
126
+
127
+ ### Trigger / inspect Actions workflows
128
+
129
+ ```sh
130
+ gh workflow list --repo OWNER/REPO
131
+ gh workflow run ci.yaml --repo OWNER/REPO --ref main -f key=value
132
+ gh run list --repo OWNER/REPO --workflow ci.yaml --limit 5
133
+ gh run view <RUN_ID> --repo OWNER/REPO --log-failed
134
+ ```
135
+
136
+ ### View a repo's metadata
137
+
138
+ ```sh
139
+ gh repo view OWNER/REPO
140
+ gh repo view OWNER/REPO --json description,url,stargazerCount,defaultBranchRef
141
+ ```
142
+
143
+ ### GraphQL for things REST can't do (e.g. project board items)
144
+
145
+ ```sh
146
+ gh api graphql -f query='
147
+ query($owner: String!, $repo: String!, $num: Int!) {
148
+ repository(owner: $owner, name: $repo) {
149
+ issue(number: $num) {
150
+ title
151
+ timelineItems(first: 50) {
152
+ nodes { __typename ... on CrossReferencedEvent { source { ... on PullRequest { number title state } } } }
153
+ }
154
+ }
155
+ }
156
+ }' -f owner=OWNER -f repo=REPO -F num=123
157
+ ```
158
+
159
+ ## Notes
160
+
161
+ - For private repos the user MUST have granted `repo` scope when they
162
+ authorized the connection at `auth.acedata.cloud/user/connections`.
163
+ A 404 on a repo you know exists usually means missing scope, not a
164
+ wrong URL.
165
+ - When `--json` rejects a field name, gh prints the full list of valid
166
+ fields — re-read the error and pick from there.
167
+ - `gh issue list --search` and `gh search issues` use the GitHub search
168
+ syntax (`is:open`, `assignee:@me`, `repo:owner/name`, etc.). Use
169
+ `gh search issues` / `gh search prs` for cross-repo queries; use
170
+ `gh issue list` for one repo.
171
+ - `gh api --paginate` only works on endpoints that emit a `Link` header;
172
+ for cursor-paginated endpoints you have to follow `pagination.next`
173
+ yourself.
@@ -0,0 +1,147 @@
1
+ ---
2
+ name: gitlab
3
+ description: GitLab issues, merge requests, repositories, CI pipelines, and code search via the glab CLI. Use when the user mentions GitLab, an MR/issue number, a project on gitlab.com, or a self-hosted GitLab instance.
4
+ when_to_use: |
5
+ Trigger when the user wants to read or write something on GitLab —
6
+ list / view / create / comment on issues or MRs, browse a project,
7
+ view CI pipelines, etc.
8
+ connections: [gitlab]
9
+ allowed_tools: [Bash]
10
+ license: Apache-2.0
11
+ metadata:
12
+ author: acedatacloud
13
+ version: "1.0"
14
+ ---
15
+
16
+ Use the `glab` CLI for everything. The user's OAuth access token is
17
+ exported as `$GITLAB_TOKEN`; `glab` reads it automatically (the token
18
+ is also accepted via `GITLAB_ACCESS_TOKEN` and `OAUTH_TOKEN` for tooling
19
+ compatibility). Default host is `gitlab.com` — for self-hosted set
20
+ `GITLAB_HOST` or pass `--hostname <host>` per command.
21
+
22
+ `glab --help` and `glab <subcommand> --help` are always current.
23
+
24
+ ## Two ways to call glab — prefer subcommands
25
+
26
+ ### Style A: First-class subcommands — START HERE
27
+
28
+ `glab issue`, `glab mr`, `glab repo`, `glab ci`, `glab job`, `glab pipeline`,
29
+ `glab release`, `glab snippet`, `glab variable`, `glab label`,
30
+ `glab milestone`, `glab schedule`. These print formatted text by default
31
+ and JSON via `--output json`.
32
+
33
+ ### Style B: Raw REST / GraphQL via `glab api`
34
+
35
+ `glab api <path>` for REST, `glab api graphql -f query='…'` for GraphQL.
36
+ Notable flags:
37
+
38
+ - `-X POST|PATCH|PUT|DELETE` — override method (default `GET`, becomes
39
+ `POST` when `--field` / `--raw-field` is set).
40
+ - `-f key=value` — magic-typed (literals `true` / `false` / `null` /
41
+ integers become JSON types, leading `@filename` reads from a file).
42
+ - `-F key=value` — same as `-f` but always treats the value as a string.
43
+ - `--paginate` — auto-walk `Link: rel="next"`.
44
+ - `--hostname <host>` — target a different GitLab host than default.
45
+ - Path placeholders: when run inside a git checkout, `:fullpath` /
46
+ `:branch` / `:user` are auto-populated from the repo. From a generic
47
+ shell, encode the path manually (see recipes).
48
+
49
+ ## Recipes
50
+
51
+ ### List open issues on a project
52
+
53
+ ```sh
54
+ glab issue list --repo OWNER/PROJECT --opened --output json
55
+ glab issue list --repo OWNER/PROJECT --assignee=@me --output json
56
+ ```
57
+
58
+ ### View an issue with comments
59
+
60
+ ```sh
61
+ glab issue view 42 --repo OWNER/PROJECT --comments
62
+ glab issue view 42 --repo OWNER/PROJECT --output json
63
+ ```
64
+
65
+ ### Create / comment / close an issue
66
+
67
+ ```sh
68
+ glab issue create --repo OWNER/PROJECT --title "Bug: foo" --description "Repro steps…" --label bug
69
+ glab issue note 42 --repo OWNER/PROJECT --message "Acknowledged."
70
+ glab issue close 42 --repo OWNER/PROJECT
71
+ ```
72
+
73
+ ### List MRs assigned to / authored by me
74
+
75
+ ```sh
76
+ glab mr list --repo OWNER/PROJECT --assignee=@me --opened --output json
77
+ glab mr list --repo OWNER/PROJECT --author=@me --opened
78
+ ```
79
+
80
+ ### View an MR with diff and CI pipeline
81
+
82
+ ```sh
83
+ glab mr view 99 --repo OWNER/PROJECT
84
+ glab mr diff 99 --repo OWNER/PROJECT
85
+ glab ci view --repo OWNER/PROJECT --branch <BRANCH>
86
+ ```
87
+
88
+ ### Approve / merge / leave a note on an MR
89
+
90
+ ```sh
91
+ glab mr approve 99 --repo OWNER/PROJECT
92
+ glab mr note 99 --repo OWNER/PROJECT --message "Looks good — ready when CI is green."
93
+ glab mr merge 99 --repo OWNER/PROJECT --squash --remove-source-branch
94
+ ```
95
+
96
+ ### Read a file from the default branch (raw bytes)
97
+
98
+ ```sh
99
+ # URL-encode the project path AND the file path because both contain '/'.
100
+ PROJECT=$(printf '%s' 'OWNER/PROJECT' | jq -sRr @uri)
101
+ FILE=$(printf '%s' 'src/main.go' | jq -sRr @uri)
102
+ glab api "projects/${PROJECT}/repository/files/${FILE}/raw?ref=main"
103
+ ```
104
+
105
+ ### List the latest pipelines on a branch
106
+
107
+ ```sh
108
+ glab ci list --repo OWNER/PROJECT --status running,success,failed
109
+ glab ci view --repo OWNER/PROJECT --branch main
110
+ glab ci trace --repo OWNER/PROJECT <JOB_ID> # stream a job log
111
+ ```
112
+
113
+ ### Search across a group's issues
114
+
115
+ ```sh
116
+ glab api "groups/GROUP_PATH_OR_ID/issues?state=opened&search=keyword" \
117
+ --paginate \
118
+ | jq '.[] | {iid, title, project: .references.full, web_url}'
119
+ ```
120
+
121
+ ### GraphQL example: project metadata + open MR count
122
+
123
+ ```sh
124
+ glab api graphql -f query='
125
+ query($path: ID!) {
126
+ project(fullPath: $path) {
127
+ name webUrl
128
+ mergeRequests(state: opened) { count }
129
+ }
130
+ }' -f path=OWNER/PROJECT
131
+ ```
132
+
133
+ ## Notes
134
+
135
+ - `--repo OWNER/PROJECT` accepts `OWNER/PROJECT`, `GROUP/SUBGROUP/PROJECT`,
136
+ full HTTPS URL, or git URL. The project path goes verbatim (no URL
137
+ encoding) for `--repo`, but does need `jq @uri` encoding when used
138
+ inside a `glab api` path.
139
+ - For self-hosted GitLab the user must have authorized the connection
140
+ with the right `host`. A 404 on a project you know exists usually
141
+ means the connection is pointing at gitlab.com when the project lives
142
+ elsewhere — surface that hint to the user.
143
+ - Many `glab` commands have an `--output` flag that takes `text` (default)
144
+ or `json`. `glab issue list` and `glab mr list` additionally have
145
+ `--output-format` (`details` / `ids` / `urls`) which is a separate,
146
+ list-only formatter. Pass the long flag `--output json` to avoid the
147
+ short-flag confusion (`-O` vs `-F`).
@@ -0,0 +1,195 @@
1
+ ---
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, or checking free time on a specific calendar.
4
+ when_to_use: |
5
+ Trigger when the user wants to read events from their Google
6
+ Calendar — list / search / inspect events, build today's or this
7
+ week's agenda, check free / busy windows, or pull invite details
8
+ for a specific meeting. The installed connector grants read-only
9
+ scope (`calendar.readonly`); creating / updating / cancelling
10
+ events is out of scope.
11
+ connections: [google/calendar]
12
+ allowed_tools: [Bash]
13
+ license: Apache-2.0
14
+ metadata:
15
+ author: acedatacloud
16
+ version: "1.0"
17
+ ---
18
+
19
+ Drive Google Calendar via `curl + jq`. The user's OAuth bearer token
20
+ is in `$GOOGLE_CALENDAR_TOKEN`; every call needs it as
21
+ `Authorization: Bearer $GOOGLE_CALENDAR_TOKEN`. The token already
22
+ carries the `calendar.readonly` scope the user agreed to at install
23
+ plus the identity scopes (`openid email profile`).
24
+
25
+ The Calendar API returns standard JSON; failures surface as
26
+ `{"error": {"code": 401|403|..., "message": "..."}}` — show that
27
+ error verbatim. `401` means the token expired (re-install). `403
28
+ insufficientPermissions` means the user is asking for a write this
29
+ connector cannot satisfy — say so.
30
+
31
+ **Always start with `users/me/calendarList`** to learn which calendars
32
+ the account can see (the user's primary plus any subscribed / shared
33
+ ones), AND with `users/me/settings/timezone` so you render times in
34
+ the user's local zone instead of UTC.
35
+
36
+ ## Recipes
37
+
38
+ ### Verify auth + discover calendars (always run first)
39
+
40
+ ```sh
41
+ # Account confirmation + calendars the user can read
42
+ curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
43
+ "https://www.googleapis.com/calendar/v3/users/me/calendarList" \
44
+ | jq '.items[] | {id, summary, primary, accessRole, timeZone}'
45
+
46
+ # User's preferred display zone (use this when formatting times)
47
+ curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
48
+ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" \
49
+ | jq -r .value
50
+ ```
51
+
52
+ The `id` of each calendar (`primary`, or an email-shaped id like
53
+ `team-monday@group.calendar.google.com`) is what subsequent
54
+ `calendars/{id}/events` calls take.
55
+
56
+ ### Today's agenda on the primary calendar
57
+
58
+ ```sh
59
+ TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
60
+ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
61
+ TODAY=$(TZ=$TZ date +%Y-%m-%d)
62
+ START="${TODAY}T00:00:00Z"
63
+ END="${TODAY}T23:59:59Z"
64
+
65
+ curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
66
+ --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
67
+ --data-urlencode "timeMin=$START" \
68
+ --data-urlencode "timeMax=$END" \
69
+ --data-urlencode 'singleEvents=true' \
70
+ --data-urlencode 'orderBy=startTime' \
71
+ --data-urlencode "timeZone=$TZ" \
72
+ | jq '.items[] | {summary, start: (.start.dateTime // .start.date), end: (.end.dateTime // .end.date), location, attendees: [.attendees[]?.email], hangout: .hangoutLink, status, htmlLink}'
73
+ ```
74
+
75
+ `singleEvents=true` flattens recurring meetings into individual
76
+ instances — almost always what you want for an agenda. Without it,
77
+ you'd get the recurrence rule once and have to expand it client-side.
78
+
79
+ ### This week's meetings (Mon–Sun)
80
+
81
+ ```sh
82
+ TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
83
+ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
84
+ # Bash date math: Monday-of-this-week
85
+ MON=$(TZ=$TZ date -d "$(TZ=$TZ date +%Y-%m-%d) -$(($(TZ=$TZ date +%u) - 1)) days" +%Y-%m-%d 2>/dev/null \
86
+ || TZ=$TZ date -v-mondayw +%Y-%m-%d) # macOS fallback
87
+ SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null \
88
+ || TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d)
89
+
90
+ curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
91
+ --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
92
+ --data-urlencode "timeMin=${MON}T00:00:00Z" \
93
+ --data-urlencode "timeMax=${SUN}T23:59:59Z" \
94
+ --data-urlencode 'singleEvents=true' \
95
+ --data-urlencode 'orderBy=startTime' \
96
+ | jq -r '.items[] | "\(.start.dateTime // .start.date)\t\(.summary)\t\((.attendees // []) | length) attendees"'
97
+ ```
98
+
99
+ ### Search events by query
100
+
101
+ ```sh
102
+ Q='quarterly review'
103
+ curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
104
+ --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
105
+ --data-urlencode "q=$Q" \
106
+ --data-urlencode 'singleEvents=true' \
107
+ --data-urlencode 'maxResults=20' \
108
+ | jq '.items[] | {start: .start.dateTime, summary, htmlLink}'
109
+ ```
110
+
111
+ `q` matches against summary, description, location, attendee emails,
112
+ and creator/organizer.
113
+
114
+ ### Get one event's full details (incl. attendees, location, link)
115
+
116
+ ```sh
117
+ EVENT_ID='abc123def4567890ghijklmnop'
118
+ curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
119
+ "https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID" \
120
+ | jq '{summary, start, end, location, description, attendees, organizer, hangoutLink, conferenceData}'
121
+ ```
122
+
123
+ ### Free / busy across multiple calendars (next 7 days)
124
+
125
+ ```sh
126
+ TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
127
+ "https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
128
+ NOW=$(TZ=$TZ date -u +%Y-%m-%dT%H:%M:%SZ)
129
+ NEXT_WEEK=$(TZ=$TZ date -u -d "+7 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
130
+ || TZ=$TZ date -u -v+7d +%Y-%m-%dT%H:%M:%SZ)
131
+
132
+ cat > /tmp/freebusy.json <<JSON
133
+ {
134
+ "timeMin": "$NOW",
135
+ "timeMax": "$NEXT_WEEK",
136
+ "timeZone": "$TZ",
137
+ "items": [
138
+ {"id": "primary"},
139
+ {"id": "team-monday@group.calendar.google.com"}
140
+ ]
141
+ }
142
+ JSON
143
+
144
+ curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
145
+ -H 'Content-Type: application/json' \
146
+ --data @/tmp/freebusy.json \
147
+ "https://www.googleapis.com/calendar/v3/freeBusy" \
148
+ | jq '.calendars'
149
+ ```
150
+
151
+ Each calendar's response is `{"busy": [{"start": "...", "end": "..."}]}`
152
+ — gaps between are free.
153
+
154
+ ### List events on a non-primary calendar
155
+
156
+ ```sh
157
+ CAL_ID='team-monday@group.calendar.google.com'
158
+ # URL-encode the @ in the path
159
+ CAL_ENCODED=$(printf %s "$CAL_ID" | jq -sRr @uri)
160
+ curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
161
+ --get "https://www.googleapis.com/calendar/v3/calendars/$CAL_ENCODED/events" \
162
+ --data-urlencode 'singleEvents=true' \
163
+ --data-urlencode 'orderBy=startTime' \
164
+ --data-urlencode 'maxResults=20' \
165
+ | jq '.items[] | {start: .start.dateTime, summary}'
166
+ ```
167
+
168
+ ### Pagination
169
+
170
+ ```sh
171
+ PAGE_TOKEN=''
172
+ while : ; do
173
+ RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
174
+ --get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
175
+ --data-urlencode 'singleEvents=true' \
176
+ --data-urlencode 'orderBy=startTime' \
177
+ --data-urlencode 'maxResults=250' \
178
+ ${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
179
+ echo "$RESP" | jq -c '.items[]?'
180
+ PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
181
+ [ -z "$PAGE_TOKEN" ] && break
182
+ done
183
+ ```
184
+
185
+ ## Common error codes
186
+
187
+ | HTTP | meaning | what to tell the user |
188
+ |---|---|---|
189
+ | `401 UNAUTHENTICATED` | token expired / revoked | "Reconnect the Google Calendar connector on the Connections page." |
190
+ | `403 insufficientPermissions` | scope missing | "This connector is read-only — creating or modifying events isn't possible." |
191
+ | `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
+ | `404 notFound` | wrong event / calendar id | double-check the id and try `calendarList` to confirm the calendar exists. |
193
+ | `429 quotaExceeded` | quota / throttling | back off ~5s, then retry once. |
194
+
195
+ Never log or echo `$GOOGLE_CALENDAR_TOKEN` — treat it as a secret.