xiaohongshu
xiaohongshu
google-calendar-sync
Wizard to set up Google Calendar API integration with service account authentication and domain-wide delegation
Full skill instructions
This skill guides you through configuring a Google Cloud service account to sync events with Google Calendar. It uses gcloud CLI to automate most Google Cloud steps.
This is a guided wizard with two types of steps:
gcloud CLI or codeAGENT ACTION - Run this at the start to determine current progress:
echo "=== GOOGLE CALENDAR SYNC STATE CHECK ==="
# Check 1: gcloud installed?
if command -v gcloud &> /dev/null; then
echo "gcloud: INSTALLED"
else
echo "gcloud: MISSING -> Start at Step 0.1"
fi
# Check 2: gcloud authenticated?
if gcloud auth list --filter="status:ACTIVE" --format="value(account)" 2>/dev/null | grep -q "@"; then
echo "gcloud_auth: AUTHENTICATED as $(gcloud auth list --filter='status:ACTIVE' --format='value(account)' | head -1)"
else
echo "gcloud_auth: NOT_AUTHENTICATED -> Complete Step 0.2"
fi
# Check 3: Project configured?
PROJECT=$(gcloud config get-value project 2>/dev/null)
if [ -n "$PROJECT" ] && [ "$PROJECT" != "(unset)" ]; then
echo "project: CONFIGURED as $PROJECT"
else
echo "project: NOT_SET -> Start at Phase 2"
fi
# Check 4: Calendar API enabled?
if [ -n "$PROJECT" ] && [ "$PROJECT" != "(unset)" ]; then
if gcloud services list --enabled --filter="name:calendar" --format="value(name)" 2>/dev/null | grep -q "calendar"; then
echo "calendar_api: ENABLED"
else
echo "calendar_api: NOT_ENABLED -> Complete Step 2.2"
fi
fi
# Check 5: Service account exists?
if [ -n "$PROJECT" ] && [ "$PROJECT" != "(unset)" ]; then
SA_EMAIL="calendar-sync@${PROJECT}.iam.gserviceaccount.com"
if gcloud iam service-accounts describe "$SA_EMAIL" &>/dev/null; then
echo "service_account: EXISTS ($SA_EMAIL)"
else
echo "service_account: NOT_FOUND -> Complete Step 2.3"
fi
fi
# Check 6: Credentials file exists?
if [ -f "google-credentials.json" ]; then
echo "credentials_file: EXISTS (needs base64 encoding)"
elif [ -f "google-credentials.b64" ]; then
echo "credentials_b64: EXISTS"
else
echo "credentials: NOT_FOUND -> Complete Step 2.5"
fi
# Check 7: Environment variables?
echo "=== ENV VAR CHECK ==="
if [ -f ".env.local" ]; then
grep -q "GOOG_CREDENTIALS_JSON" .env.local 2>/dev/null && echo "GOOG_CREDENTIALS_JSON: SET in .env.local" || echo "GOOG_CREDENTIALS_JSON: MISSING"
grep -q "GOOG_CALENDAR_IMPERSONATE_USER" .env.local 2>/dev/null && echo "GOOG_CALENDAR_IMPERSONATE_USER: SET in .env.local" || echo "GOOG_CALENDAR_IMPERSONATE_USER: MISSING"
grep -q "GOOG_CALENDAR_ID" .env.local 2>/dev/null && echo "GOOG_CALENDAR_ID: SET in .env.local" || echo "GOOG_CALENDAR_ID: MISSING"
elif [ -f ".env" ]; then
grep -q "GOOG_CREDENTIALS_JSON" .env 2>/dev/null && echo "GOOG_CREDENTIALS_JSON: SET in .env" || echo "GOOG_CREDENTIALS_JSON: MISSING"
grep -q "GOOG_CALENDAR_IMPERSONATE_USER" .env 2>/dev/null && echo "GOOG_CALENDAR_IMPERSONATE_USER: SET in .env" || echo "GOOG_CALENDAR_IMPERSONATE_USER: MISSING"
grep -q "GOOG_CALENDAR_ID" .env 2>/dev/null && echo "GOOG_CALENDAR_ID: SET in .env" || echo "GOOG_CALENDAR_ID: MISSING"
else
echo "env_file: NOT_FOUND -> Complete Phase 5"
fi
# Check 8: googleapis package?
if [ -f "package.json" ]; then
grep -q "googleapis" package.json && echo "googleapis: INSTALLED" || echo "googleapis: NOT_INSTALLED -> Complete Step 5.4"
fi
echo "=== END STATE CHECK ==="
Based on the state check output, skip to the appropriate phase or step.
The agent will check for required tools and guide you through installing anything that's missing.
AGENT ACTION - Run this check:
if command -v gcloud &> /dev/null; then
echo "INSTALLED: gcloud $(gcloud --version 2>/dev/null | head -1)"
else
echo "NOT_INSTALLED: gcloud"
fi
If NOT_INSTALLED:
HUMAN ACTION REQUIRED
Install the Google Cloud CLI:
macOS (Homebrew):
brew install google-cloud-sdkmacOS (Manual):
curl https://sdk.cloud.google.com | bash exec -l $SHELLLinux (Debian/Ubuntu):
sudo apt-get install apt-transport-https ca-certificates gnupg curl curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list sudo apt-get update && sudo apt-get install google-cloud-cliLinux (Other):
curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-linux-x86_64.tar.gz tar -xf google-cloud-cli-linux-x86_64.tar.gz ./google-cloud-sdk/install.shWindows: Download installer from: https://cloud.google.com/sdk/docs/install
Reply "gcloud installed" when complete.
AGENT ACTION - Run this check:
if gcloud auth list --filter="status:ACTIVE" --format="value(account)" 2>/dev/null | head -1 | grep -q "@"; then
echo "AUTHENTICATED: $(gcloud auth list --filter='status:ACTIVE' --format='value(account)' | head -1)"
else
echo "NOT_AUTHENTICATED"
fi
If NOT_AUTHENTICATED:
HUMAN ACTION REQUIRED
Authenticate with your Google account:
gcloud auth loginThis will open a browser window. Sign in with the Google account that has access to your Google Cloud projects.
Reply "authenticated" when complete.
AGENT ACTION - Run this check:
if command -v node &> /dev/null; then
echo "INSTALLED: node $(node --version)"
else
echo "NOT_INSTALLED: node"
fi
If NOT_INSTALLED:
HUMAN ACTION REQUIRED
Install Node.js (required for the googleapis package and testing):
macOS:
brew install nodeLinux:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejsOr use nvm: https://github.com/nvm-sh/nvm
Reply "node installed" when complete.
AGENT ACTION - Run this check:
if command -v pnpm &> /dev/null; then
echo "INSTALLED: pnpm $(pnpm --version)"
elif command -v npm &> /dev/null; then
echo "FALLBACK: npm $(npm --version)"
else
echo "NOT_INSTALLED: package manager"
fi
The skill will use pnpm if available, otherwise npm.
# Check .env files for existing Google config
grep -r "GOOG_" .env* 2>/dev/null || echo "No existing Google config found"
grep -r "GOOGLE_" .env* 2>/dev/null || echo "No existing Google config found"
gcloud projects list --format="table(projectId,name,createTime)"
Ask user: Use an existing project or create a new one?
AGENT ACTION - Create new project:
# Generate a unique project ID
PROJECT_ID="calendar-sync-$(date +%s | tail -c 7)"
PROJECT_NAME="Calendar Sync"
gcloud projects create "$PROJECT_ID" --name="$PROJECT_NAME"
gcloud config set project "$PROJECT_ID"
Or select existing:
gcloud config set project YOUR_PROJECT_ID
AGENT ACTION
gcloud services enable calendar-json.googleapis.com
Verify it's enabled:
gcloud services list --enabled --filter="name:calendar"
AGENT ACTION
SERVICE_ACCOUNT_NAME="calendar-sync"
PROJECT_ID=$(gcloud config get-value project)
gcloud iam service-accounts create "$SERVICE_ACCOUNT_NAME" \
--display-name="Calendar Sync Service" \
--description="Manages calendar events for the application"
Get the service account email:
SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
echo "Service Account: $SERVICE_ACCOUNT_EMAIL"
AGENT ACTION
PROJECT_ID=$(gcloud config get-value project)
SERVICE_ACCOUNT_EMAIL="calendar-sync@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role="roles/owner"
AGENT ACTION (may require org policy override)
PROJECT_ID=$(gcloud config get-value project)
SERVICE_ACCOUNT_EMAIL="calendar-sync@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts keys create ./google-credentials.json \
--iam-account="$SERVICE_ACCOUNT_EMAIL"
If this fails with "Key creation is not allowed":
HUMAN ACTION REQUIRED
Disable the org policy constraint:
# Requires org admin permissions
gcloud org-policies reset iam.disableServiceAccountKeyCreation --project="$PROJECT_ID"
Or do it via Console: Organization Policies > search iam.disableServiceAccountKeyCreation > Override > Not enforced.
Then retry the key creation command.
AGENT ACTION
# Encode and display (copy this for GOOG_CREDENTIALS_JSON)
cat google-credentials.json | base64 -w 0
echo ""
# Or save to a file
cat google-credentials.json | base64 -w 0 > google-credentials.b64
AGENT ACTION
PROJECT_ID=$(gcloud config get-value project)
SERVICE_ACCOUNT_EMAIL="calendar-sync@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts describe "$SERVICE_ACCOUNT_EMAIL" \
--format="value(uniqueId)"
Save this Client ID for the next phase.
Note: Only required for Google Workspace. Skip if using personal Gmail with calendar sharing.
HUMAN ACTION REQUIRED - This cannot be automated via CLI.
https://www.googleapis.com/auth/calendar.events
Confirm: Reply "Delegation configured" when complete.
HUMAN ACTION REQUIRED
Provide the email of a Google Workspace user who has access to the target calendar.
This will be GOOG_CALENDAR_IMPERSONATE_USER.
Option A - Primary calendar: The calendar ID is the user's email address.
Option B - Shared/secondary calendar:
HUMAN ACTION REQUIRED
[email protected])# Read the base64 credentials
CREDS_B64=$(cat google-credentials.json | base64 -w 0)
# Append to .env.local (or .env)
cat >> .env.local << EOF
# Google Calendar Integration
GOOG_CREDENTIALS_JSON="$CREDS_B64"
GOOG_CALENDAR_IMPERSONATE_USER="[email protected]"
GOOG_CALENDAR_ID="[email protected]"
EOF
cat >> .env.example << 'EOF'
# Google Calendar Integration
# See: .opencode/skill/google-calendar-sync/SKILL.md
GOOG_CREDENTIALS_JSON=
GOOG_CALENDAR_IMPERSONATE_USER=
GOOG_CALENDAR_ID=
EOF
echo "google-credentials.json" >> .gitignore
echo "google-credentials.b64" >> .gitignore
AGENT ACTION - Use detected package manager:
# Detect and use the right package manager
if command -v pnpm &> /dev/null; then
pnpm add googleapis
elif command -v yarn &> /dev/null; then
yarn add googleapis
else
npm install googleapis
fi
Create lib/google-calendar.ts:
import { google } from 'googleapis'
function getCalendarClient() {
const credentials = JSON.parse(
Buffer.from(process.env.GOOG_CREDENTIALS_JSON!, 'base64').toString()
)
const auth = new google.auth.GoogleAuth({
credentials,
scopes: ['https://www.googleapis.com/auth/calendar.events'],
clientOptions: {
subject: process.env.GOOG_CALENDAR_IMPERSONATE_USER,
},
})
return google.calendar({ version: 'v3', auth })
}
export async function createCalendarEvent(event: {
summary: string
description?: string
start: Date
end: Date
location?: string
}) {
const calendar = getCalendarClient()
const calendarId = process.env.GOOG_CALENDAR_ID!
return calendar.events.insert({
calendarId,
requestBody: {
summary: event.summary,
description: event.description,
location: event.location,
start: {
dateTime: event.start.toISOString(),
timeZone: 'UTC',
},
end: {
dateTime: event.end.toISOString(),
timeZone: 'UTC',
},
},
})
}
export async function updateCalendarEvent(
eventId: string,
event: {
summary?: string
description?: string
start?: Date
end?: Date
location?: string
}
) {
const calendar = getCalendarClient()
const calendarId = process.env.GOOG_CALENDAR_ID!
return calendar.events.patch({
calendarId,
eventId,
requestBody: {
...(event.summary && { summary: event.summary }),
...(event.description && { description: event.description }),
...(event.location && { location: event.location }),
...(event.start && {
start: { dateTime: event.start.toISOString(), timeZone: 'UTC' },
}),
...(event.end && {
end: { dateTime: event.end.toISOString(), timeZone: 'UTC' },
}),
},
})
}
export async function deleteCalendarEvent(eventId: string) {
const calendar = getCalendarClient()
const calendarId = process.env.GOOG_CALENDAR_ID!
return calendar.events.delete({
calendarId,
eventId,
})
}
export async function listCalendarEvents(options?: {
timeMin?: Date
timeMax?: Date
maxResults?: number
}) {
const calendar = getCalendarClient()
const calendarId = process.env.GOOG_CALENDAR_ID!
return calendar.events.list({
calendarId,
timeMin: options?.timeMin?.toISOString(),
timeMax: options?.timeMax?.toISOString(),
maxResults: options?.maxResults ?? 10,
singleEvents: true,
orderBy: 'startTime',
})
}
AGENT ACTION - Check required env vars:
echo "=== VERIFYING ENV VARS ==="
# Source the env file to check
if [ -f ".env.local" ]; then
source .env.local 2>/dev/null
elif [ -f ".env" ]; then
source .env 2>/dev/null
fi
MISSING=""
[ -z "$GOOG_CREDENTIALS_JSON" ] && MISSING="$MISSING GOOG_CREDENTIALS_JSON"
[ -z "$GOOG_CALENDAR_IMPERSONATE_USER" ] && MISSING="$MISSING GOOG_CALENDAR_IMPERSONATE_USER"
[ -z "$GOOG_CALENDAR_ID" ] && MISSING="$MISSING GOOG_CALENDAR_ID"
if [ -n "$MISSING" ]; then
echo "MISSING ENV VARS:$MISSING"
echo "Please complete Phase 5 before testing."
exit 1
else
echo "All required env vars are set!"
fi
AGENT ACTION - Run connection test:
# Load env vars first
if [ -f ".env.local" ]; then
export $(grep -v '^#' .env.local | xargs)
elif [ -f ".env" ]; then
export $(grep -v '^#' .env | xargs)
fi
# Quick Node.js test script
node -e "
const { google } = require('googleapis');
if (!process.env.GOOG_CREDENTIALS_JSON) {
console.error('ERROR: GOOG_CREDENTIALS_JSON not set');
process.exit(1);
}
const creds = JSON.parse(Buffer.from(process.env.GOOG_CREDENTIALS_JSON, 'base64').toString());
const auth = new google.auth.GoogleAuth({
credentials: creds,
scopes: ['https://www.googleapis.com/auth/calendar.events'],
clientOptions: { subject: process.env.GOOG_CALENDAR_IMPERSONATE_USER }
});
const calendar = google.calendar({ version: 'v3', auth });
console.log('Testing connection to calendar:', process.env.GOOG_CALENDAR_ID);
console.log('Impersonating user:', process.env.GOOG_CALENDAR_IMPERSONATE_USER);
calendar.events.list({
calendarId: process.env.GOOG_CALENDAR_ID,
maxResults: 5,
timeMin: new Date().toISOString(),
singleEvents: true,
orderBy: 'startTime'
}).then(r => {
console.log('SUCCESS! Connection working.');
console.log('Found', r.data.items?.length || 0, 'upcoming events');
if (r.data.items?.length > 0) {
console.log('Next event:', r.data.items[0].summary);
}
}).catch(e => {
console.error('ERROR:', e.message);
if (e.message.includes('insufficient')) {
console.error('-> Check domain-wide delegation is configured correctly');
}
if (e.message.includes('not found')) {
console.error('-> Check GOOG_CALENDAR_ID is correct');
}
process.exit(1);
});
"
If the test fails, check the Troubleshooting section below.
# Delete the JSON key file (you have it base64-encoded in .env now)
rm -f google-credentials.json google-credentials.b64
https://www.googleapis.com/auth/calendar.eventsgcloud org-policies reset iam.disableServiceAccountKeyCreation --project="$(gcloud config get-value project)"
# List all calendars the impersonation user can access
node -e "
const { google } = require('googleapis');
const creds = JSON.parse(Buffer.from(process.env.GOOG_CREDENTIALS_JSON, 'base64').toString());
const auth = new google.auth.GoogleAuth({
credentials: creds,
scopes: ['https://www.googleapis.com/auth/calendar.readonly'],
clientOptions: { subject: process.env.GOOG_CALENDAR_IMPERSONATE_USER }
});
const calendar = google.calendar({ version: 'v3', auth });
calendar.calendarList.list().then(r => {
r.data.items?.forEach(c => console.log(c.id, '-', c.summary));
}).catch(e => console.error('Error:', e.message));
"
# Auth
gcloud auth login
gcloud auth list
# Projects
gcloud projects list
gcloud projects create PROJECT_ID --name="Name"
gcloud config set project PROJECT_ID
# APIs
gcloud services enable calendar-json.googleapis.com
gcloud services list --enabled
# Service Accounts
gcloud iam service-accounts list
gcloud iam service-accounts create NAME --display-name="Display Name"
gcloud iam service-accounts keys create output.json --iam-account=EMAIL
gcloud iam service-accounts describe EMAIL --format="value(uniqueId)"
gcloud CLI installed and authenticated.envgoogleapis package installedxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience