A Go CLI tool that automates meeting note organization, calendar attachment syncing, AI-powered task assignment, and decision extraction using Google Workspace APIs and Gemini AI.
GCal Organizer runs a 4-step workflow that keeps your Google Drive and Calendar in sync:
| Step | Command | Description |
|---|---|---|
| 1 | organize |
Finds meeting docs in Drive and organizes them into topic-based folders |
| 2 | sync-calendar |
Links calendar attachments to meeting folders, shares with attendees |
| 3 | assign-tasks |
Locates checkbox action items in Google Docs Notes by Gemini and assigns them if Gemini can determine the assignee |
| 4 | (automatic) | Extracts decisions from meeting transcripts and creates a "Decisions" tab with categorized sections |
Run all four with gcal-organizer run, or steps 1-3 individually.
Documents are matched by the naming pattern Topic Name - YYYY-MM-DD (configurable via regex). The topic name becomes the folder:
Google Drive
βββ Meeting Notes/ β Master folder (configurable)
β βββ Weekly Standup/
β β βββ Notes - Weekly Standup - 2026-02-03 β moved here
β β βββ Notes - Weekly Standup - 2026-02-10 β moved here
β βββ Project Alpha Review/
β β βββ Notes - Project Alpha Review - 2026-02-05
β β βββ π Design Spec (shortcut) β calendar attachment
β βββ 1-1 with Jordan/
β βββ Notes - 1-1 with Jordan - 2026-02-07
Before: Docs scattered across "My Drive" and "Shared with me" After: Auto-organized into topic folders with calendar attachments linked as shortcuts
- Take meeting notes in a Google Doc named
Notes - Sprint Planning - 2026-02-08 - Have Gemini take notes. It will add checkboxes like
β @jordan review the API spec by Friday - Attach the doc to the calendar event
- Run
gcal-organizer run - Result:
- Doc moves to
Meeting Notes / Sprint Planning /folder - Calendar attachment linked as a shortcut in the meeting folder
- Folder and attachment shared with all attendees (edit access)
- Jordan gets the task assigned in Google Docs
- Doc moves to
| Requirement | Purpose | Install |
|---|---|---|
| Go 1.24+ | Build the CLI | go.dev/dl |
| Node.js 18+ | Browser automation for task assignment | nodejs.org |
| Google Chrome | Task assignment via Playwright | google.com/chrome |
| Ollama | Local AI for sensitivity gate and task assignment | ollama.com |
| GCP Project | OAuth2 credentials + Gemini API key | Setup Guide |
# Tap and install the pre-built signed binary
brew tap jflowers/gcal-organizer
brew install --cask gcal-organizer
# Authenticate with Google
gcal-organizer auth login
# Set up browser automation and install hourly service
gcal-organizer setup-browser
gcal-organizer install
# Test with dry-run first
gcal-organizer run --dry-run --verbosemacOS note: Release binaries are code-signed and notarized by Apple. Gatekeeper will not block execution or display "unidentified developer" warnings.
# Clone and build
git clone https://github.com/jflowers/gcal-organizer.git
cd gcal-organizer
make install
# Configure and authenticate
gcal-organizer init
gcal-organizer auth login
# Set up browser automation
gcal-organizer setup-browser
# Test
gcal-organizer run --dry-run --verbose
# Install hourly service
gcal-organizer install# Install the hourly service (works for both Homebrew and source installs):
gcal-organizer install
# Check status
gcal-organizer doctor
# Remove the service
gcal-organizer uninstall
# Or via Makefile (source installs only):
make install-service # install
make service-status # check status
make service-logs # view logs
make service-trigger # trigger immediate run
make uninstall-service # removeThe service runs with GCAL_DAYS_TO_LOOK_BACK=1 to process only the last day of events.
man gcal-organizer| Command | Description |
|---|---|
gcal-organizer run |
Run the full 4-step workflow |
gcal-organizer organize |
Step 1 only: organize documents into folders |
gcal-organizer sync-calendar |
Step 2 only: sync calendar attachments |
gcal-organizer assign-tasks --doc <ID> |
Step 3 only: assign tasks from a specific doc |
gcal-organizer auth login |
Authenticate with Google |
gcal-organizer auth status |
Check authentication status |
gcal-organizer config show |
Show current configuration |
gcal-organizer init |
Set up configuration (interactive wizard) |
gcal-organizer doctor |
Check system health and report fixes |
gcal-organizer install |
Install as hourly background service |
gcal-organizer uninstall |
Remove the background service |
| Flag | Description |
|---|---|
--dry-run |
Show what would be done without making changes |
--verbose / -v |
Detailed output |
--owned-only |
Only mutate files you own; skip non-owned files |
--days N |
Days to look back for calendar events (default: 8) |
The --owned-only flag prevents gcal-organizer from modifying files you don't own. When active:
- Owned files: Moved, shared, and processed normally
- Non-owned files: Only shortcuts are created (no moves, shares, or task assignments)
- Use
--verboseto see which files are skipped and why - Combine with
--dry-runto preview ownership filtering
# Run with ownership protection
gcal-organizer run --owned-only --verbose
# Preview what would be skipped
gcal-organizer run --owned-only --dry-run --verboseLimitation: Shared Drive files are treated as non-owned (the organization owns them). Do not use --owned-only if your workflow depends on Shared Drive mutations.
Environment variables (or ~/.gcal-organizer/.env):
# Required
GEMINI_API_KEY=your-gcp-api-key
# Optional
GCAL_MASTER_FOLDER_NAME="Meeting Notes" # Default: "Meeting Notes"
GCAL_DAYS_TO_LOOK_BACK=1 # Default: 1
GCAL_OWNED_ONLY=true # Default: false (only mutate owned files)
GCAL_FILENAME_KEYWORDS="Notes,Meeting" # Comma-separated
GCAL_FILENAME_PATTERN="(.+)\s*-\s*(\d{4}-\d{2}-\d{2})"
GEMINI_MODEL="gemini-2.0-flash" # Default: gemini-2.0-flash
GCAL_DECISIONS_EXPORT_DIR="~/.gcal-organizer/decisions" # DefaultSensitivity gate: When enabled (default), every transcript is screened locally by Granite Guardian before any cloud processing. Sensitive transcripts are skipped entirely.
What goes to Gemini AI: Only decision extraction uses Gemini (unless local-only mode is enabled). Task assignment runs locally via Ollama. In local-only mode, no data is sent to cloud AI services.
What stays local: OAuth tokens, credentials, configuration, and all local AI processing are stored securely and never transmitted to cloud AI services.
By default, OAuth tokens, the Gemini API key, and client credentials are stored in your OS credential store (macOS Keychain or Linux Secret Service). This prevents sensitive data from being stored as plaintext files on disk.
| Data | Default Storage | Fallback |
|---|---|---|
| OAuth token | OS keychain | ~/.gcal-organizer/token.json |
| Gemini API key | OS keychain | ~/.gcal-organizer/.env |
| Client credentials | OS keychain | ~/.gcal-organizer/credentials.json |
To disable keychain storage (e.g., on headless servers without a credential store):
gcal-organizer run --no-keyring
# or
export GCAL_NO_KEYRING=trueThe Google Docs API provides read access to document content (including checkboxes), but cannot interact with the native "Assign as a task" canvas widget in Google Docs. This is a UI-only feature with no API equivalent. The tool uses a Playwright/Node.js sidecar to:
- Read checkbox text and extract assignees via Gemini
- Open each Google Doc in a headed Chrome browser
- Hover over checkboxes to reveal the "Assign" tooltip
- Click to assign the task to the identified person
This requires Chrome with your Google profile for authentication. See Browser Automation Setup for details.
Step 4 automatically processes meeting transcript documents to extract and categorize decisions:
- During calendar sync (Step 2), the tool identifies documents titled "Notes by Gemini" (exact match) or ending with "- Transcript" (suffix match)
- For each eligible document, it reads the transcript content and sends it to Gemini AI
- Gemini extracts decisions into three categories:
- Decisions Made -- commitments the team agreed on
- Decisions Deferred -- items explicitly tabled for later
- Open Items -- unresolved topics needing further discussion
- A new "Decisions" tab is created in the document with clickable timestamp links back to the transcript
- A local markdown copy is written to
~/.gcal-organizer/decisions/with YAML frontmatter (topic,date,attendees) for indexing by semantic search tools
Markdown Export: Each exported file uses the naming convention <topic-slug>-<YYYY-MM-DD>.md and contains categorized decision sections. Empty categories are omitted. Files are overwritten on reprocessing (idempotent). Export failures are logged as warnings but never block the pipeline.
Configure export directory: Set GCAL_DECISIONS_EXPORT_DIR or add decisions_export_dir to your config file to change the output location. The ~ shorthand is expanded automatically.
Idempotent: Documents with an existing "Decisions" tab are automatically skipped. Safe to run repeatedly.
Error handling: If Gemini fails on a document, it's skipped with a warning. The next run will retry since no tab was created.
gcal-organizer integrates with Ollama for local AI processing using IBM Granite models:
Every meeting transcript is screened locally by Granite Guardian before any cloud processing. If sensitive content is detected (HR discussions, legal matters, compensation, health information, termination planning), the transcript is skipped entirely β no cloud AI calls, no document modifications, no local exports.
# View sensitivity classifications in logs
gcal-organizer run --verbose
# Preview classifications without skipping
gcal-organizer run --dry-runTask assignment (identifying who is responsible for each action item) runs locally using Granite 3.2:8b instead of Gemini. Action item content never leaves your machine.
For maximum privacy, enable local-only mode to run all AI processing locally:
# In ~/.gcal-organizer/config.yaml
ollama:
local_only: trueIn this mode, decision extraction also uses the local model instead of Gemini. No cloud AI calls are made.
# ~/.gcal-organizer/config.yaml
ollama:
enabled: true # Enable local AI features
endpoint: "http://localhost:11434" # Ollama API endpoint
timeout: 120 # Request timeout (seconds)
sensitivity:
enabled: true # Enable sensitivity gate
model: "granite-guardian" # Sensitivity model
threshold: 0.7 # Sensitivity threshold (0.0-1.0)
assignments:
model: "granite3.2:8b" # Task assignment model
local_only: false # All AI processing local| Issue | Fix |
|---|---|
| "Ollama is configured but not available" | Run ollama serve to start the service |
| "Model not available" | Run ollama pull <model> to download the model |
| "Ollama binary not found" | Install with brew install ollama (macOS) |
gcal-organizer/
βββ cmd/gcal-organizer/ # CLI entry point (Cobra)
βββ internal/
β βββ auth/ # OAuth2 + Gemini API auth
β βββ calendar/ # Calendar event operations
β βββ config/ # Configuration management
β βββ docs/ # Docs checkbox extraction + decision tab creation
β βββ drive/ # Drive folder/file operations
β βββ export/ # Decision markdown export (local files)
β βββ gemini/ # Gemini AI assignee + decision extraction
β βββ ollama/ # Local AI via Ollama (sensitivity, assignments, decisions)
β βββ organizer/ # Workflow orchestration
βββ browser/ # Playwright task assignment script (TypeScript)
βββ .specify/ # Spec-kit artifacts
βββ .github/workflows/ # CI/CD (build/test + release)
βββ man/ # Man page (roff)
βββ Makefile # Build, test, service management
βββ docs/SETUP.md # Full setup guide
make build # Build the binary
make test # Run tests
make check # Format, vet, and test
make lint # Run golangci-lint
make help # Show all targetsThis project uses Spec-Kit for specification-driven development. Specs are in specs/:
| Spec | Description |
|---|---|
001-gcal-organizer-cli |
Core CLI β organize, sync, share |
002-browser-task-assignment |
Playwright-based task assignment |
003-cicd-github-actions |
CI/CD workflows |
004-service-deployment |
Hourly service on macOS/Fedora |
See LICENSE for details.