BACK

Building granola-cli: AI Meeting Notes in Your Terminal

7 min read

Disclaimer: This project is an independent open-source tool and is not affiliated with, endorsed by, or connected to Granola.ai.

What if you could query your meeting history like a database? granola-cli brings your Granola.ai meeting notes to the command line—grep transcripts, export to JSON, and pipe action items directly into Claude Code or your task manager.

What is Granola?

Granola is an AI meeting assistant that sits in your menu bar. It records system audio, transcribes locally, and uses LLMs to produce structured summaries—key decisions, action items, discussion points. No bots joining your calls, no awkward "Granola is recording" notifications to your teammates. At Buffer, it has become a daily driver for our team.

The notes it generates are excellent. But I hit a ceiling.

The Problem I Needed to Solve

I wanted raw data in my terminal. I wanted to feed last week's engineering syncs into Claude Code to plan my next sprint. I wanted to grep through transcripts or pipe action items directly into my task manager.

Copy-pasting from a web UI fell short. I found Joseph Thacker's research on reverse engineering the Granola API, plus the getprobo/reverse-engineering-granola-api repo. The groundwork existed. A proper CLI for daily use did not.

So I built one.

How I Mapped Granola's API

The Granola desktop app stores authentication tokens in a local JSON file. The CLI reads these credentials, stores them securely in your system keychain via cross-keychain (which I wrote about previously), and uses them to call Granola's internal APIs.

Key endpoints I mapped:

  • POST /v2/get-documents — list meetings with cursor pagination
  • POST /v1/get-document-metadata — notes and participant data
  • POST /v1/get-document-transcript — transcript segments with speaker detection
  • POST /v2/get-document-lists — folders and workspace organization

Query, Filter, and Export Your Meetings

List and Filter

# Recent meetings
granola meeting list --limit 10
# Filter by date (natural language supported)
granola meeting list --date yesterday
granola meeting list --since "last week"
granola meeting list --since 2025-12-01 --until 2025-12-15
# Filter by attendee or search
granola meeting list --attendee "[email protected]"
granola meeting list --search "quarterly planning"

View Content

# Full meeting details with participants
granola meeting view <id>
# Your handwritten notes (converted from ProseMirror to Markdown)
granola meeting notes <id>
# AI-generated summary with key decisions and action items
granola meeting enhanced <id>
# Full transcript with speaker detection
granola meeting transcript <id>
granola meeting transcript <id> --timestamps

Export for Pipelines

# Export everything about a meeting
granola meeting export <id> --format json
granola meeting export <id> --format toon
# Pipe to LLMs
granola meeting enhanced <id> --output toon | llm "What were the action items?"

40% Fewer Tokens with TOON

The CLI supports TOON (Token-Oriented Object Notation), a format designed for LLM consumption. TOON delivers the same structured data as JSON using 40% fewer tokens.

When you pipe meeting data to Claude or another model, every token saved means more context for your question. TOON determines whether one meeting or three fits your context window.

$ granola meeting export abc123 --format json | wc -c
15234
$ granola meeting export abc123 --format toon | wc -c
9140

Same data. 40% smaller. That's the difference between asking a follow-up question or hitting your context limit.

Turning Meeting History into AI Context

This is why I built it: to empower my AI agents.

I use Claude Code heavily. With granola-cli installed, I can ask Claude to analyze my meetings directly:

You: Check my engineering syncs from last week and list any blockers mentioned.
Claude: I'll query your recent meetings using granola-cli.
[Runs: granola meeting list --since "last week" --search "sync"]
Found 3 engineering syncs. Analyzing transcripts...
Blockers mentioned:
1. CI pipeline flakiness blocking the release (Dec 18 sync)
2. Waiting on design review for the dashboard redesign (Dec 19 sync)
3. API rate limiting issues with the third-party integration (Dec 20 sync)

No copy-pasting. No switching windows. Claude reads the data directly and gives me answers.

I've also built Agent Skills that check meeting history, summarize decisions, and surface blockers from previous syncs. Your meeting history becomes a queryable database for your AI workflow.

Under the Hood

Secure Credential Storage

I refuse to store API tokens in plaintext config files. Too many CLI tools dump secrets into ~/.config/toolname/credentials.json. One accidental git add . or misconfigured backup exposes your tokens.

The CLI stores credentials via cross-keychain in your OS's native credential manager—macOS Keychain, Windows Credential Manager, or Linux Secret Service. These systems encrypt secrets at rest, integrate with your login session, and follow platform security best practices. Your Granola tokens never touch the filesystem in readable form.

Token Refresh with File Locking

Granola uses single-use refresh tokens—each token works once before invalidation. This improves security but creates a race condition: if two CLI processes refresh simultaneously, one gets a valid token while the other wastes the refresh token and fails.

The CLI solves this with file-based locking. Before refreshing, the process acquires an exclusive lock on a temp file. If another process is already refreshing, the second waits (30-second timeout) rather than racing. The lock releases immediately after refresh completes, so parallel CLI invocations work smoothly—they take turns when needed.

ProseMirror to Markdown Conversion

Granola stores notes in ProseMirror format—the same rich-text framework Notion, The New York Times, and Atlassian use. It represents content as a JSON tree of nodes with marks (formatting) attached.

The CLI walks this tree, converting it to Markdown. Headings become # lines, lists become - items, text marks become their Markdown equivalents: bold wraps in **, italic in *, code in backticks. The conversion preserves nested structures, so a bulleted list inside a blockquote renders correctly. The result: readable Markdown you can pipe to other tools, search with grep, or feed to an LLM.

Natural Language Date Parsing

Nobody types ISO dates willingly. The CLI accepts "today", "yesterday", "3 days ago", "last week", or partial dates like "Dec 1". For ranges, combine --since and --until with any format. The parser handles the rest.

The parser normalizes input, handles edge cases (what does "last week" mean on a Monday?), and returns UTC timestamps matching Granola's API expectations. The common case—"show me yesterday's meetings"—becomes a single intuitive flag.

4 Hours with Claude Code Opus 4.5

I built this tool in about 4 to 5 hours pairing with Claude Code Opus 4.5. I focused on architecture and intent while Claude handled implementation. The result: a production-ready CLI with strict TypeScript, 95%+ test coverage across 630+ test cases, and modular design—all in a single afternoon.

This is "vibe engineering" in practice. I skipped the lengthy planning phase, described what I wanted, reviewed the output, and iterated quickly.

Get Started

# Install
npm install -g granola-cli
# Login (reads credentials from your Granola desktop app)
granola auth login
# List your meetings
granola meeting list

The source is at github.com/magarcia/granola-cli. Issues and PRs welcome.

Related posts: