关于 google-calendar
这项技能通过 curl 和 jq 操作 Google Calendar,也可选用 Google's gws 命令行查看日程和创建事件。它涵盖日历发现、本地时区显示、事件搜索、空闲状态查询及谨慎修改事件。
主要功能
- 01发现可访问的日历和账户时区
- 02列出并搜索事件
- 03查询多个日历的忙闲状态
- 04创建、更新、移动和取消事件
- 05处理参会者通知和权限错误
适用场景
- 01查看今天或本周的日程
- 02搜索符合查询条件的事件
- 03检查多个日历的空闲时间
- 04经确认后改期或取消事件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ acedatacloud/ skills --skill google-calendar安装后在 Claude Code 中输入/google-calendar确认已加载。
详细介绍
通过 curl + jq 操作 Google Calendar。用户的 OAuth bearer token
位于 $GOOGLE_CALENDAR_TOKEN;每次调用都需要使用
Authorization: Bearer $GOOGLE_CALENDAR_TOKEN。该 token 至少包含 calendar.readonly 和身份范围
(openid email profile);如果用户在安装时选择启用写入权限,还会包含范围更广的 calendar 范围(读写)。
Calendar API 返回标准 JSON;失败时会返回
{"error": {"code": 401|403|..., "message": "..."}},请原样显示该错误。401 表示 token 已过期(重新安装)。写入操作出现 403 insufficientPermissions 表示用户只授予了 calendar.readonly;请让他们重新安装连接器,并勾选读写权限。
始终先调用 users/me/calendarList,了解该账号可查看哪些日历(用户的主日历以及任何已订阅/共享的日历);还要调用 users/me/settings/timezone,以便按用户的本地时区而非 UTC 显示时间。
执行任何破坏性写入之前(创建、移动或取消包含与会者的事件),先显示准确的事件详情并请用户确认。如果涉及与会者,还要确认用户是否希望 Google 向与会者发送邮件——这由 sendUpdates 查询参数控制。
可选:使用 Google Workspace CLI(gws)查询日程和创建事件
gws 是 Google 官方 CLI(非官方支持,由 googleworkspace 组织维护)。它会根据 Google 的 Discovery Document 动态构建命令,并在 API 错误时以非零状态码退出;此外还提供手工编写的辅助命令(以 + 为前缀),用于处理与时间相关的工作流。
以下两种情况请使用 gws:
+agenda会从Settings.timezone读取用户账号的时区(缓存 24 小时),并按该时区显示今天的事件,因此格式化时间前无需自行获取时区。+insert会为你构建创建事件所需的 JSON(与会者、sendUpdates、提醒),让你通过一行命令生成格式正确的请求。
其他操作(events.list / patch / move / delete、freebusy、 calendarList)使用下面的 curl 示例即可实现相同效果,而且更简短——请继续使用这些示例。
安装
npm install -g @googleworkspace/cli # or: brew install googleworkspace-cli
# Pre-built binaries also at https://github.com/googleworkspace/cli/releases
gws --version
身份验证
gws 从 GOOGLE_WORKSPACE_CLI_TOKEN
环境变量读取其 OAuth bearer token。此技能使用的 Calendar token 位于
$GOOGLE_CALENDAR_TOKEN,因此每个调用 gws 的 shell
代码块开头都要重新导出一次:
export GOOGLE_WORKSPACE_CLI_TOKEN="$GOOGLE_CALENDAR_TOKEN"
查询日程 + 创建事件
# Today on the primary calendar, in the account's own timezone
gws calendar +agenda
# Today / week, with explicit overrides
gws calendar +agenda --today --tz America/New_York
gws calendar +agenda --range week
# Create an event (auto-shapes attendees + sendUpdates JSON)
gws calendar +insert --calendar primary \
--json '{
"summary":"Standup",
"start":{"dateTime":"2026-05-06T10:00:00-04:00"},
"end": {"dateTime":"2026-05-06T10:30:00-04:00"},
"attendees":[{"email":"[email protected]"}]
}' \
--params '{"sendUpdates":"all"}'
如果 Google 拒绝请求,这两个辅助命令都会在 stderr 上输出结构化 JSON 错误,并以非零状态码退出——请原样显示错误。对包含与会者的事件使用 +insert 需要范围更广的 calendar 范围;如果出现 403 insufficientPermissions,请让用户重新安装并勾选读写权限。
示例
验证身份验证并发现日历(始终首先运行)
# Account confirmation + calendars the user can read
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/calendarList" \
| jq '.items[] | {id, summary, primary, accessRole, timeZone}'
# User's preferred display zone (use this when formatting times)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" \
| jq -r .value
每个日历的 id(primary,或类似电子邮件地址的 ID,如
[email protected])都可用于后续的
calendars/{id}/events 调用。
主日历上的今日安排
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
TODAY=$(TZ=$TZ date +%Y-%m-%d)
START="${TODAY}T00:00:00Z"
END="${TODAY}T23:59:59Z"
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode "timeMin=$START" \
--data-urlencode "timeMax=$END" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
--data-urlencode "timeZone=$TZ" \
| jq '.items[] | {summary, start: (.start.dateTime // .start.date), end: (.end.dateTime // .end.date), location, attendees: [.attendees[]?.email], hangout: .hangoutLink, status, htmlLink}'
singleEvents=true 会将重复会议展开为单个实例——这几乎总是查询日程时想要的结果。如果不使用该参数,你只会获取一次重复规则,并且必须在客户端展开。
本周会议(周一至周日)
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
# Bash date math: Monday-of-this-week
MON=$(TZ=$TZ date -d "$(TZ=$TZ date +%Y-%m-%d) -$(($(TZ=$TZ date +%u) - 1)) days" +%Y-%m-%d 2>/dev/null \
|| TZ=$TZ date -v-mondayw +%Y-%m-%d) # macOS fallback
SUN=$(TZ=$TZ date -d "$MON +6 days" +%Y-%m-%d 2>/dev/null \
|| TZ=$TZ date -v+6d -j -f %Y-%m-%d "$MON" +%Y-%m-%d)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode "timeMin=${MON}T00:00:00Z" \
--data-urlencode "timeMax=${SUN}T23:59:59Z" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
| jq -r '.items[] | "\(.start.dateTime // .start.date)\t\(.summary)\t\((.attendees // []) | length) attendees"'
按查询词搜索事件
Q='quarterly review'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode "q=$Q" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'maxResults=20' \
| jq '.items[] | {start: .start.dateTime, summary, htmlLink}'
q 会匹配摘要、说明、地点、与会者电子邮件地址以及创建者/组织者。
获取单个事件的完整详情(包括与会者、地点和链接)
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID" \
| jq '{summary, start, end, location, description, attendees, organizer, hangoutLink, conferenceData}'
查询多个日历的空闲/忙碌时间(未来 7 天)
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
NOW=$(TZ=$TZ date -u +%Y-%m-%dT%H:%M:%SZ)
NEXT_WEEK=$(TZ=$TZ date -u -d "+7 days" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| TZ=$TZ date -u -v+7d +%Y-%m-%dT%H:%M:%SZ)
cat > /tmp/freebusy.json <<JSON
{
"timeMin": "$NOW",
"timeMax": "$NEXT_WEEK",
"timeZone": "$TZ",
"items": [
{"id": "primary"},
{"id": "[email protected]"}
]
}
JSON
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/freebusy.json \
"https://www.googleapis.com/calendar/v3/freeBusy" \
| jq '.calendars'
每个日历的响应格式为 {"busy": [{"start": "...", "end": "..."}]}
——时间段之间的空档即为空闲时间。
列出非主日历上的事件
CAL_ID='[email protected]'
# URL-encode the @ in the path
CAL_ENCODED=$(printf %s "$CAL_ID" | jq -sRr @uri)
curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/$CAL_ENCODED/events" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
--data-urlencode 'maxResults=20' \
| jq '.items[] | {start: .start.dateTime, summary}'
分页
PAGE_TOKEN=''
while : ; do
RESP=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
--get "https://www.googleapis.com/calendar/v3/calendars/primary/events" \
--data-urlencode 'singleEvents=true' \
--data-urlencode 'orderBy=startTime' \
--data-urlencode 'maxResults=250' \
${PAGE_TOKEN:+--data-urlencode "pageToken=$PAGE_TOKEN"})
echo "$RESP" | jq -c '.items[]?'
PAGE_TOKEN=$(echo "$RESP" | jq -r '.nextPageToken // empty')
[ -z "$PAGE_TOKEN" ] && break
done
编写操作指南
这些操作都需要更广泛的 calendar 作用域。如果用户只授予了
calendar.readonly,你会收到 403 insufficientPermissions —
请向用户说明这一点,并要求他们重新安装连接器,同时勾选读写选项。创建或取消任何内容之前,务必先向用户确认活动摘要、时间和参与者名单。
创建单个活动(可选添加参与者和 Google Meet 链接)
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_event.json <<JSON
{
"summary": "Sync — Q2 OKR review",
"location": "Online",
"description": "Drafted by AceDataCloud.",
"start": {"dateTime": "2026-05-12T10:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T10:30:00", "timeZone": "$TZ"},
"attendees": [
{"email": "[email protected]"},
{"email": "[email protected]"}
],
"reminders": {"useDefault": true},
"conferenceData": {
"createRequest": {
"requestId": "meet-$(date +%s)",
"conferenceSolutionKey": {"type": "hangoutsMeet"}
}
}
}
JSON
# sendUpdates: 'all' = email all attendees; 'externalOnly' = only non-org; 'none' = silent
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/_cal_event.json \
"https://www.googleapis.com/calendar/v3/calendars/primary/events?conferenceDataVersion=1&sendUpdates=all" \
| jq '{id, htmlLink, hangoutLink, summary, start, end, attendees}'
如果用户没有要求添加 Meet 链接,请移除 conferenceData 块 —
系统会改为创建普通活动。
创建重复活动
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_recur.json <<JSON
{
"summary": "Weekly 1:1",
"start": {"dateTime": "2026-05-12T15:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T15:30:00", "timeZone": "$TZ"},
"recurrence": ["RRULE:FREQ=WEEKLY;BYDAY=TU;COUNT=12"]
}
JSON
curl -sS -X POST -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/_cal_recur.json \
"https://www.googleapis.com/calendar/v3/calendars/primary/events" \
| jq '{id, recurrence, summary}'
RRULE 遵循 RFC 5545。常见模式:FREQ=DAILY、FREQ=WEEKLY;BYDAY=MO,WE,FR、
FREQ=MONTHLY;BYMONTHDAY=15。添加 UNTIL=20261231T235959Z 或 COUNT=12
即可设置硬性结束条件。
更新现有活动(PATCH — 部分更新)
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data '{"location":"Conference Room 4","description":"Now in-person."}' \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, summary, location, description}'
PATCH 只会更改你发送的字段;PUT 会替换整个
活动载荷。优先使用 PATCH。
改期
EVENT_ID='abc123def4567890ghijklmnop'
TZ=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/users/me/settings/timezone" | jq -r .value)
cat > /tmp/_cal_resched.json <<JSON
{
"start": {"dateTime": "2026-05-12T14:00:00", "timeZone": "$TZ"},
"end": {"dateTime": "2026-05-12T14:30:00", "timeZone": "$TZ"}
}
JSON
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data @/tmp/_cal_resched.json \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, summary, start, end}'
添加或更改参与者
Google 要求你在更新参与者时发送完整的参与者名单 — 先获取当前名单,修改后再发回:
EVENT_ID='abc123def4567890ghijklmnop'
CURRENT=$(curl -sS -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?fields=attendees" \
| jq '.attendees // []')
NEW=$(echo "$CURRENT" | jq '. + [{"email":"[email protected]"}]')
curl -sS -X PATCH -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
-H 'Content-Type: application/json' \
--data "{\"attendees\": $NEW}" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
| jq '{id, attendees}'
取消 / 删除活动
EVENT_ID='abc123def4567890ghijklmnop'
curl -sS -X DELETE -H "Authorization: Bearer $GOOGLE_CALENDAR_TOKEN" \
"https://www.googleapis.com/calendar/v3/calendars/primary/events/$EVENT_ID?sendUpdates=all" \
-o /dev/null -w 'HTTP %{http_code}\n'
204 = 成功。要取消重复活动中的某一次,请先用
events.instances 获取该实例,然后 DELETE 特定的实例 ID(其格式更长,为
EVENT_ID_YYYYMMDDTHHMMSSZ)。
常见错误代码
| HTTP | 含义 | 告诉用户的内容 |
|---|---|---|
401 UNAUTHENTICATED | 令牌已过期 / 被撤销 | “请在 Connections 页面重新连接 Google Calendar 连接器。” |
403 insufficientPermissions | 缺少写入作用域 | “此操作需要 Calendar 读写作用域,但你只授予了 calendar.readonly。请重新安装连接器,并勾选读写选项。” |
403 forbidden | 此账号无法查看该日历 ID | 先检查 calendarList;如果这是共享日历,请所有者共享该日历。 |
404 notFound | 活动 / 日历 ID 错误 | 仔细检查 ID,并尝试使用 calendarList 确认该日历是否存在。 |
409 conflict | 重复活动 ID 冲突 | 在 requestId 后追加一个 UUID,然后重试。 |
429 quotaExceeded | 配额 / 请求限流 | 等待约 5s,然后重试一次。 |
切勿记录或回显 $GOOGLE_CALENDAR_TOKEN — 请将其视为机密。