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 ベアラートークンは
$GOOGLE_CALENDAR_TOKEN に格納されています。すべての呼び出しで
Authorization: Bearer $GOOGLE_CALENDAR_TOKEN として指定する必要があります。トークンには最低限、
calendar.readonly と ID スコープ
(openid email profile)が含まれます。インストール時にユーザーが書き込みを許可した場合は、
より広範な calendar スコープ(読み取り+書き込み)も含まれます。
Calendar API は標準の JSON を返します。エラーは
{"error": {"code": 401|403|..., "message": "..."}} の形式で返されるため、
そのエラーをそのまま表示してください。401 はトークンの期限切れを意味します(再インストールしてください)。
書き込み時の 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 は次の 2 つのケースで使用してください。
+agendaはSettings.timezoneからユーザーのアカウントのタイムゾーンを取得し (24 時間キャッシュ)、そのタイムゾーンで今日の予定を表示します。そのため、時刻を整形する前にタイムゾーンを自分で取得する必要はありません。+insertは予定作成用の JSON(参加者、sendUpdates、リマインダー)を整形するため、1 行のコマンドで正しい形式のリクエストを作成できます。
それ以外のすべて(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 は OAuth ベアラートークンを環境変数
GOOGLE_WORKSPACE_CLI_TOKEN から読み込みます。このスキルで使用する Calendar トークンは
$GOOGLE_CALENDAR_TOKEN にあるため、gws を呼び出す各シェルブロックの先頭で、一度再エクスポートしてください。
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 がリクエストを拒否すると、構造化された JSON エラーを stderr に出力してゼロ以外の終了コードで終了します。そのエラーをそのまま表示してください。参加者がいる予定に対する +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、または [email protected] のようなメールアドレス形式の ID)が、後続の 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 を指定すると、繰り返しの予定が個別の予定に展開されます。議題を表示する場合、ほとんどの場合これが適しています。指定しない場合は、繰り返しルールが 1 回だけ返されるため、クライアント側で展開する必要があります。
今週の会議(月〜日)
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 は、概要、説明、場所、参加者のメールアドレス、作成者/主催者を対象に検索します。
予定 1 件の詳細をすべて取得(参加者、場所、リンクを含む)
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 でその回を取得し、その後、特定のインスタンス ID(より長い
EVENT_ID_YYYYMMDDTHHMMSSZ の形式)を DELETE します。
よくあるエラーコード
| HTTP | 意味 | ユーザーへの説明 |
|---|---|---|
401 UNAUTHENTICATED | トークンの有効期限切れ/取り消し | 「Connections ページで Google Calendar コネクターを再接続してください。」 |
403 insufficientPermissions | 書き込みスコープがない | 「この操作には Calendar の読み取り+書き込みスコープが必要ですが、calendar.readonly しか許可されていません。読み取り+書き込みのチェックボックスをオンにして、コネクターを再インストールしてください。」 |
403 forbidden | このアカウントからカレンダー ID が見えない | まず calendarList を確認してください。共有カレンダーの場合は、所有者に共有してもらう必要があります。 |
404 notFound | イベント ID またはカレンダー ID が間違っている | ID を再確認し、calendarList でカレンダーが存在することを確認してください。 |
409 conflict | 繰り返しイベント ID の衝突 | requestId に UUID を追加して、再試行してください。 |
429 quotaExceeded | クォータ超過/スロットリング | 約 5 秒待ってから、一度だけ再試行してください。 |
$GOOGLE_CALENDAR_TOKEN をログに記録したり、ユーザーに伝えたりしないでください。秘密情報として扱ってください。