google-calendar-sync について
google-calendar-syncは、予約システムでGoogle Calendar同期を実装・監査するためのスキルです。空き時間の取り込みと予約イベントの書き込みを分離し、OAuth、ストレージ、通知、チャネル、テストを案内します。
主な機能
- 01期間全体の再同期で予定あり時間を取り込み
- 02決定的なIDで予約イベントを書き込み
- 03OAuth接続と暗号化トークンを処理
- 04監視チャネルを更新し再同期をスケジュール
- 05FirestoreとSupabaseのストアを提供
- 06テストと運用のガイダンス
活用シーン
- 01選択したカレンダーでホストの空き時間をブロック
- 02確定した予約をホストのカレンダーに追加
- 03重複・古い・停止した同期の監査
- 04FirestoreとSupabase間で同期ストレージを移行
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ timerise-ai/ google-calendar-sync --skill google-calendar-syncインストール後、Claude Code で/google-calendar-syncを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
CHANGELOG.mdREADME.mdCLAUDE.mdLICENSE.gitattributes.github/workflows/agent-eval.ymlevals/prompts.mdreferences/booking-events.mdadaptation.mdbusy-sync.mdchannels-and-settings.mddata-model.mdhttp-routes.mdoperations.mdtest-suite.mdgoogle-api.mdoauth-and-tokens.mdfirestore-backend.mdsupabase-backend.mdprovenance.mdtesting.md詳細
Google Calendar Sync
2 つの方向に、それぞれ 1 つのルールがあります。受信:ホストのほかの予定については Google が正として扱われるため、予定あり時間帯は時間範囲を反映した破棄可能なミラーです。完全な再同期で再構築し、手動では編集しません。 送信:予約については予約ストアが正として扱われるため、Google に書き込むイベントには予約から導出した ID を割り当て、予定あり時間として読み戻すことはありません。この 2 つの方向を分離することを、テンプレートとテストで徹底しています。
使用する場面
- ホストが Google Calendar に接続し、空き状況をブロックするカレンダーを選択する場合。
- 予約をホストの Google Calendar に表示し、必要に応じて Meet リンクを付け、キャンセル時に削除する場合。
- 既存の Google 同期を監査する場合:重複、ゴースト、停止した同期、再接続ループ。
- 同期を Firestore と Supabase/Postgres の間で移行する場合。
使用しない場面
- 予約可能な時間枠の生成、予定あり時間の差し引き:
time-slot-generator。このスキルは、消費する予定あり時間を生成します。 - Outlook または Microsoft 365:
microsoft-calendar-sync。Microsoft Graph 上で同じアーキテクチャとレコード形式を使用します。 - カレンダー UI の表示や招待状の送信:ホスト自身のメールを使用してください。この方法では Google の招待メールを送信せずにイベントを書き込みます。
- ユーザーごとの OAuth を使わない読み取り専用の空き状況確認:サービスアカウントとドメイン全体の委任を使う Google の空き時間照会は、別の設計です。
アーキテクチャ
connect (PKCE, single-use state) -> callback -> encrypted tokens -> calendar rows
|
inbound Google -- X-Goog-* -> /notifications -> syncBusy (lease) -> list all pages -> toBusyBlock -> diffBusy -> busy blocks -> availability
^ ^
+-- watch channel <-- cron: renew new-before-old, resync every busy calendar
outbound booking confirmed -> pushBookingEvent -> insert id=bk<sha256> (409: update) -> Meet link back
booking cancelled -> removeBookingEvent -> delete by id (404/410 = done)
適用の契約
adaptation.md に契約全体、ホスト側の前提条件、作業順序が記載されています。名前の変更表は data-model.md にあります。
| 接合部 | このスキルが提供するもの | ホストが用意するもの |
|---|---|---|
| 認証 | authenticate(request): userId or null | セッションの照合処理 |
| データ | Firestore と Supabase 向けの CalendarStore | プロジェクト |
| 予約 | BookingSource、pushBookingEvent、removeBookingEvent | 予約ステータスのフローからの呼び出し |
| 空き状況 | ユーザーごとの予定あり時間、listHostBusy | スロットエンジン(time-slot-generator) |
| HTTP | Web の Request と Response ハンドラー | Next.js のルートハンドラー、または Express アダプター |
| バックグラウンド処理 | 12 時間ごとの renewChannels、任意の defer | スケジューラー、after() |
重要な事実
- 予定あり時間の同期は、イベントストリームではなく、時間範囲全体の再同期です。 通知が知らせるのは「何かが変更された」ということだけです。
[now, now + horizon)のすべてのページを取得して差分を確認します。取得が一部でも欠けている場合は中断し、削除してはいけません。 - ID はすべて決定的に生成します。 カレンダー行、予定あり時間、予約イベントの ID は、それぞれが表す対象のハッシュです。これにより、同時に届く通知、再試行、二重トリガーが重複を生まずに収束します。
- 自分のイベントも戻ってきます。 予定あり時間の同期対象でもあるカレンダーに書き込んだ予約は、非公開の
bookingIdマーカーを付けてスキップしない限り、再インポートされます。座席数ベースのサービスでは、最初の予約後にグループセッションの残りの枠がブロックされます。 - Google が変更を通知するのは、チャネルが有効な間だけです。 チャネルには有効期限があり、通知は確実に再試行されるわけではなく、時間範囲も移動します。Cron でチャネルを更新し、予定あり時間を同期するすべてのカレンダーを再同期します。これを行わないと予定あり時間の更新が止まり、そのことも通知されません。
invalid_grantはエラーではなく状態です。 アクセスの取り消し、6 か月間の未使用、または「Testing」モードでの 7 日間が原因です。接続をreauth_requiredに設定してユーザーに知らせ、同じアカウントで再接続した際にはすべての設定を維持してください。- 終日イベントは、カレンダーのタイムゾーンにおける現地時間の午前 0 時から、
start.dateから排他的なend.dateまでを占めます。 時刻指定イベントにはオフセットが含まれます。送信時はZ付きの時刻を渡してください。これを取り除くとイベントの時刻がずれます。 - 空き時間イベントと辞退済みイベントはブロックしません。 Google 自身の空き時間照会でも無視されます。例外は祝日カレンダーです。ブロックする目的でのみ有効にします。
厳守事項
1. OAuth コールバック URL からユーザー ID を取得しないでください。 state は、サーバー側に保存される一度限りの10分間有効なレコードへのキーです。そうしないと、誰でも他人のアカウントに自分のカレンダーを紐付けられてしまいます。
2. 通知が届いたチャネル ID と照合してチャネル トークンを検証するまでは、通知を信頼しないでください。 エンドポイントは公開されているため、トークンだけが認証情報です。
3. 新しいチャネルが作成される前に、古いチャネルを停止しないでください。 新しい watch が失敗すると、カレンダーにチャネルがなくなり、更新するものもなくなるため、UI では「オン」と表示されていても、予定の空き時間が更新されなくなります。
4. トークンを平文で保存しないでください。 リフレッシュトークンは、カレンダーへの長期間有効な読み取り・書き込みアクセスを許可します。
5. Google のエラーによって予約が失敗しないようにしてください。 外部への書き込みではログを記録して戻り、予約は確定したままにします。
クイックスタート
- ホストを確認し、接続部分の仕様を埋めます:adaptation.md。
- Google Cloud の OAuth クライアントと同意画面を作成します: oauth-and-tokens.md。
- 型をコピーし、名前を変更するかどうか決めます:data-model.md。
- Google クライアントをコピーします:google-api.md。
- 受信同期 busy-sync.md、送信処理 booking-events.md、ライフサイクル channels-and-settings.md をコピーします。
- ストアを選択します:firestore-backend.md または supabase-backend.md。
- ルートと cron をマウントします:http-routes.md。
- テストスイートをコピーし、2つの
TZ値で実行します:testing.md、 test-suite.md。 - 運用担当者にヘルスビューを提供します:operations.md。
- 既存の連携を修正する場合は、provenance.md に記載された順序に従ってください。
リファレンスディレクトリ
| シナリオ | トリガーキーワード | リファレンス |
|---|---|---|
| 境界と作業順序 | adapt, host probe, seam contract, non-negotiables | adaptation.md |
| エンティティ、ストアの契約、名前変更 | LinkedCalendar, BusyBlock, WatchChannel, CalendarStore, mutateCalendar, version, compare-and-swap | data-model.md |
| アカウントの接続 | OAuth, PKCE, state, consent screen, scopes, refresh_token, invalid_grant, reauth, revoke, AES-256-GCM, Testing mode | oauth-and-tokens.md |
| Google との通信 | fetch, calendarList, events.list, pageToken, events.watch, channels.stop, 409 duplicate, 410, rate limit | google-api.md |
| 忙しい時間帯の取り込み | toBusyBlock, transparency, declined, all-day, end.date, DST, diffBusy, lease, resyncRequested, horizon | busy-sync.md |
| 予約の送信 | event id, base32hex, extendedProperties private, Meet, conferenceData, hangoutLink, sendUpdates, backfill | booking-events.md |
| チャネルと切り替え設定 | startChannel, renewChannels, make-before-break, orphan channel, holiday calendar, syncBusy, syncBookings, planTransitions | channels-and-settings.md |
| ルートと Webhook | X-Goog-Channel-ID, X-Goog-Resource-State, sync, exists, 204, callback redirect, cron secret, Next.js, Express | http-routes.md |
| Firestore | firebase-admin, runTransaction, batch 500, calendarBusyBlocks, composite index, security rules | firestore-backend.md |
| Supabase / Postgres | schema.sql, RLS, service role, optimistic concurrency, 23505, PostgREST 1000 rows | supabase-backend.md |
| 動作の検証 | node:test, memory store, fake Google, TZ=, mutation check | testing.md, test-suite.md |
| 実行方法 | health, stale, channel-expired, env vars, quotas, dashboards, runbook, ngrok, localhost | operations.md |
| この設計を採用した理由 | audit, defect, kept deliberately, designed here, fix order | provenance.md |
兄弟スキルを一覧にした Timerise Skills インデックスの一部です。