Granola Sync
SkillFiles & storageSyncs Granola meeting notes to Obsidian vault. Reads from Granola's local cache to extract meeting metadata and notes, then creates properly formatted markdown files with frontmatter. Also handles manual transcript formatting when cache doesn't have content.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Granola Sync skill
What this skill tells your AI
The instructions your AI receives, as published by elliotjlt/claude-skill-potions in skills/granola-sync/SKILL.md and read by ahel’s review.
When To Activate
Manual formatting:
- User pastes a meeting transcript
- User shares Granola export content
- User says "format this meeting" with pasted content
Important Limitation
When local cache has no notes, ask user to copy from Granola app.
Instructions
Granola Cache Location
~/Library/Application Support/Granola/cache-v3.json
Cache Structure
{
"cache": "{\"state\":{\"documents\":{...}}}" // nested JSON string
}
Each document contains:
id- unique meeting IDtitle- meeting titlenotes_markdown- notes content (often empty)notes_plain- plain text versiongoogle_calendar_event.start.dateTime- meeting timegoogle_calendar_event.attendees- list withemailfields
Reading the Cache
<read_cache>
cat ~/Library/Application\ Support/Granola/cache-v3.json | python3 -c "
import json,sys
d=json.load(sys.stdin)
cache=json.loads(d['cache'])
docs=cache['state']['documents']
# List meetings with notes
for doc in docs.values():
md = doc.get('notes_markdown', '')
if md and len(md) > 50:
title = doc.get('title', 'Untitled')
cal = doc.get('google_calendar_event') or {}
start = cal.get('start') or {}
dt = start.get('dateTime', '')[:10]
print(f'{dt} | {title[:40]} | {len(md)} chars')
"
</read_cache>
Output File Format
<file_format>
Filename: YYYY-MM-DD-kebab-case-title.md
Location: Project meetings folder (e.g., Projects/project-name/meetings/)
---
type: meeting
project: <project-name>
status: active
date: YYYY-MM-DD
attendees:
- name1
- name2
source: granola
granola-id: <meeting-id>
tags:
- meeting
---
# Meeting Title
**Date**: YYYY-MM-DD
**Attendees**: Name1, Name2
---
## Notes
<notes_markdown content>
</file_format>
Manual Transcript Formatting
<manual_format> When user pastes a transcript without cache data:
-
Ask for metadata if not obvious:
- Meeting date
- Attendees
- Topic/title
-
Structure the content:
---
type: meeting
project: <project>
status: active
date: YYYY-MM-DD
attendees:
- name1
- name2
source: granola-manual
tags:
- meeting
---
# Topic Name
**Date**: YYYY-MM-DD
**Attendees**: Name1, Name2
---
## Summary
<2-3 sentence summary>
## Key Decisions
- Decision 1
- Decision 2
## Discussion Notes
<cleaned transcript, organized by topic>
## Action Items
- [ ] Action 1 (owner)
- [ ] Action 2 (owner)
</manual_format>
Output Format
When syncing from cache:
📥 **Granola Sync**
Found X meetings with notes:
- YYYY-MM-DD: Meeting Title (XXX chars)
Syncing to `Projects/project/meetings/`...
✓ Created: 2025-01-28-meeting-title.md
When no notes in cache:
📥 **Granola Sync**
Found X meetings but none have local notes cached.
Paste transcript content and I'll format it.
NEVER
- Sync meetings without notes content
- Overwrite existing meeting files without asking
- Include sensitive attendee details beyond names
- Create duplicate files for same meeting
ALWAYS
- Use kebab-case lowercase filenames
- Add frontmatter with proper metadata
- Check for existing files before creating
- Ask which project folder to use if unclear
- Include granola-id to prevent duplicates
Signals
- GitHub stars
- 62
- Forks
- 2
- Last commit
- Feb 2026
Advanced
- Catalog kind
- skill
- Gateway key
granola-sync- Source
- github.com/elliotjlt/claude-skill-potions