@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
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 (
|
|
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.
|
|
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.
|