Skip to main content
Rowboat syncs your Google Calendar events and attached Google Docs to keep your schedule and meeting notes accessible to your AI assistant.

Overview

The Google Calendar integration:
  • Syncs events from 14 days in the past to 14 days in the future
  • Downloads Google Docs attached to calendar events
  • Tracks event changes (new, updated, deleted)
  • Converts meeting notes to markdown
  • Runs automatically every 5 minutes

Prerequisites

Calendar integration requires Google OAuth setup with both Calendar and Drive API access.

Google OAuth Setup

Follow these steps to connect Google Calendar to Rowboat.
1

Open Google Cloud Console

Go to Google Cloud Console and make sure you’re logged into the Google account you want to use.
2

Create a New Project

Go to Create Project
  • Click Create Project
  • Give it a name (e.g. Rowboat Integration)
  • Click Create
Once created, select the new project in the top project dropdown.
3

Enable Required APIs

Enable the following APIs for your project:Google Calendar API: https://console.cloud.google.com/apis/api/calendar-json.googleapis.comGoogle Drive API: https://console.cloud.google.com/apis/api/drive.googleapis.comClick Enable for each API.
4

Configure OAuth Consent Screen

Go to OAuth Consent ScreenApp Information:
  • App name: Rowboat
  • User support email: Your email
Audience:
  • Choose External
Contact Information:
  • Add your email address
Click Save and Continue through the remaining steps.
You do NOT need to publish the app — keeping it in Testing mode is fine.
5

Add Test Users

If your app is in Testing mode, you must add users manually.Go to Test UsersUnder Test Users:
  • Click Add Users
  • Add the email address you plan to connect with Rowboat
Save changes.
6

Create OAuth Client ID

Go to CredentialsClick Create Credentials → OAuth Client IDApplication Type:
  • Select: Universal Windows Platform (UWP)
  • Name: Rowboat Desktop
  • Store ID: test
  • Click Create
7

Copy the Client ID

After creation, Google will show:
  • Client ID
  • Client Secret
Copy the Client ID and paste it into Rowboat where prompted.

How It Works

Sync Configuration

The Calendar sync runs with these settings:
const SYNC_DIR = path.join(WorkDir, 'calendar_sync');
const SYNC_INTERVAL_MS = 5 * 60 * 1000; // Every 5 minutes
const LOOKBACK_DAYS = 14; // 14 days back, 14 days forward
const REQUIRED_SCOPES = [
  'https://www.googleapis.com/auth/calendar.events.readonly',
  'https://www.googleapis.com/auth/drive.readonly'
];

Event Storage Format

Each calendar event is saved as a JSON file:
{
  "id": "event123",
  "summary": "Team Standup",
  "start": { "dateTime": "2024-01-01T10:00:00Z" },
  "end": { "dateTime": "2024-01-01T10:30:00Z" },
  "organizer": { "email": "[email protected]" },
  "attendees": [
    { "email": "[email protected]" },
    { "email": "[email protected]" }
  ],
  "attachments": [
    {
      "fileId": "doc123",
      "title": "Meeting Notes",
      "mimeType": "application/vnd.google-apps.document"
    }
  ]
}

Meeting Notes Sync

Google Docs attached to events are automatically synced:
if (attachment.mimeType === 'application/vnd.google-apps.document') {
  const fileId = attachment.fileId;
  const html = await drive.files.export({
    fileId: fileId,
    mimeType: 'text/html'
  });
  
  const markdown = nodeHtmlMarkdown.translate(html);
  
  const frontmatter = [
    `# ${attachment.title}`,
    `**Event:** ${eventTitle}`,
    `**Date:** ${eventDate}`,
    `**Organizer:** ${organizer}`,
    `**Link:** ${attachment.fileUrl}`,
    `---`,
  ].join('\n');
  
  fs.writeFileSync(filePath, frontmatter + markdown);
}

Sync Window

Rowboat syncs events in a rolling window:
const now = new Date();
const lookbackMs = 14 * 24 * 60 * 60 * 1000;
const forwardMs = 14 * 24 * 60 * 60 * 1000;

const timeMin = new Date(now.getTime() - lookbackMs).toISOString();
const timeMax = new Date(now.getTime() + forwardMs).toISOString();

const events = await calendar.events.list({
  calendarId: 'primary',
  timeMin: timeMin,
  timeMax: timeMax,
  singleEvents: true,
  orderBy: 'startTime'
});
Events outside this window are automatically deleted from local storage.

Change Detection

Rowboat tracks three types of changes:

New Events

if (!fs.existsSync(filePath)) {
  fs.writeFileSync(filePath, content);
  newCount++;
}

Updated Events

const existing = fs.readFileSync(filePath, 'utf-8');
if (existing !== content) {
  fs.writeFileSync(filePath, content);
  updatedCount++;
}

Deleted Events

function cleanUpOldFiles(currentEventIds: Set<string>, syncDir: string) {
  const files = fs.readdirSync(syncDir);
  for (const filename of files) {
    const eventId = filename.replace('.json', '');
    if (!currentEventIds.has(eventId)) {
      fs.unlinkSync(path.join(syncDir, filename));
      deletedCount++;
    }
  }
}

Activity Logging

Rowboat logs all sync activity:
await serviceLogger.log({
  type: 'changes_identified',
  service: 'calendar',
  runId,
  level: 'info',
  message: `Calendar updates: ${totalChanges} changes`,
  counts: {
    newEvents: newCount,
    updatedEvents: updatedCount,
    deletedFiles: deletedCount,
    attachments: attachmentCount,
  },
});

Trigger Manual Sync

You can trigger an immediate sync:
import { triggerSync } from './sync_calendar';

triggerSync(); // Wakes up sync immediately

Troubleshooting

401 Unauthorized Error

If authentication fails, Rowboat clears the OAuth cache and you’ll need to reconnect your Google account.
if (error.response?.status === 401) {
  console.log("401 Unauthorized, clearing cache");
  GoogleClientFactory.clearCache();
}

Missing Meeting Notes

Rowboat only syncs Google Docs attachments. Other file types are ignored:
if (attachment.mimeType === 'application/vnd.google-apps.document') {
  // Only process Google Docs
}
PDFs, images, and other file types attached to calendar events are not synced.

Rate Limiting

To avoid API rate limits:
  • Sync runs every 5 minutes
  • Only events within the sync window are processed
  • Meeting notes are cached (skipped if file exists)

Files Synced

LocationDescription
~/rowboat/calendar_sync/{eventId}.jsonEvent metadata
~/rowboat/calendar_sync/{eventId}_doc_{title}.mdMeeting notes
~/rowboat/calendar_sync/sync_state.jsonSync state tracking

Example Use Cases

Meeting Preparation

“What meetings do I have tomorrow?” Rowboat can search your synced calendar events and provide context from attached meeting notes.

Follow-up Tasks

“What action items came out of yesterday’s standup?” Rowboat can read the Google Doc notes attached to the event and extract action items.

Schedule Overview

“When am I meeting with the design team this week?” Rowboat searches event metadata including attendees and titles.

Privacy & Security

  • Read-Only Access: Uses .readonly scopes only
  • Local Storage: All data stored locally on your machine
  • No Cloud Sync: Calendar data never sent to external servers
  • OAuth Tokens: Securely managed by Google’s OAuth flow

Build docs developers (and LLMs) love