graincrawl creates a private, local SQLite archive of Granola notes, transcripts, panels, people, workspaces, and sync metadata. It is for people who want searchable, exportable meeting data without writing to their Granola profile.
With Homebrew:
brew install openclaw/tap/graincrawlFrom source (Go 1.26.5 or newer):
go install github.com/openclaw/graincrawl/cmd/graincrawl@latestInitialize the local config and archive, inspect the available Granola state, then sync:
graincrawl init
graincrawl doctor
graincrawl sync
graincrawl status
graincrawl notes
graincrawl tuisync uses the configured source, which defaults to Granola's private desktop API session. When that session is unavailable and a readable plaintext desktop cache exists, an implicit sync falls back to the cache. Granola's official public API is also supported when explicitly enabled:
GRANOLA_PUBLIC_API_KEY="..." \
GRAINCRAWL_ALLOW_PUBLIC_API=true \
graincrawl sync --source public-apiFor managed environments, inject GRANOLA_PUBLIC_API_KEY at runtime from the operator's secret manager instead of writing it to the config file.
The default private API source archives notes, transcripts, panels, people, workspaces, and retained source payloads. The desktop cache provides an offline fallback when cache-v6.json is readable. The official public API archives available notes, summaries, and transcripts, but does not expose panels or deletion events. All sources are read-only against Granola; graincrawl writes only to its own config, cache, and SQLite archive.
Granola 7.427 and later can store its data-encryption key in an app-scoped Keychain item that graincrawl cannot access. graincrawl doctor detects this boundary. Existing archived data stays readable, and the official public API remains independent of the local Keychain boundary. See the security model for the exact source and legacy encrypted-JSON behavior.
Search the archive, inspect one note, or export Markdown:
graincrawl search "decision"
graincrawl note get <id>
graincrawl transcripts get <id>
graincrawl export markdown --out ./granola-notesFor automation, put --json before the command. The SQL surface is read-only:
graincrawl --json status
graincrawl --json notes
graincrawl --json sql "select count(*) as notes from notes"Snapshots move the archive between machines without touching the live Granola profile:
graincrawl snapshot create --out ./graincrawl-snapshot
graincrawl import ./graincrawl-snapshotImports merge by default and keep existing local payloads on identity conflicts. graincrawl import --replace ./graincrawl-snapshot performs an exact restore and removes rows that are absent from the snapshot.
See the command reference for every command, flags, JSON output, and shell completion.
graincrawl init writes a private config file and creates the archive directories. Use --config <path> or GRAINCRAWL_CONFIG to select another config, and start from config.example.toml when you need custom source, path, sync, or security settings.
Passive update checks run at most once every 24 hours during interactive use. They are skipped for JSON output, CI, non-terminal stderr, and development builds. Set GRAINCRAWL_NO_UPDATE_CHECK=1 or CRAWLKIT_NO_UPDATE_CHECK=1 to disable them.
Release packaging and verification are documented in docs/distribution.md.
make build
make checkContributions must preserve graincrawl's read-only boundary. See CONTRIBUTING.md.
MIT. See LICENSE.
