graincrawl reads Granola data into its own local SQLite archive. Commands that inspect or sync Granola never write to the Granola profile.
graincrawl [--json] [--config <path>] [--version] <command> [args]
| Option | Purpose |
|---|---|
--json |
Write structured JSON. Put it before the command so free-form search and SQL arguments remain unambiguous. |
--config <path> |
Use a specific TOML config instead of the default or GRAINCRAWL_CONFIG. |
--version, -v |
Print build metadata. |
--help, -h |
Print the built-in command overview. |
| Command | Purpose |
|---|---|
graincrawl init |
Create the config and archive directories. |
graincrawl doctor |
Inspect Granola, config, archive, and unlock state without prompting for Keychain access. |
graincrawl sources |
Show which source adapters the current config allows. |
graincrawl sync |
Sync from the configured source; refresh is an alias. |
graincrawl sync --source private-api |
Require the private desktop API session. |
graincrawl sync --source public-api |
Use Granola's official API with GRANOLA_PUBLIC_API_KEY. |
graincrawl sync --source desktop-cache |
Import the local plaintext desktop cache. |
graincrawl sync --limit <n> |
Limit the number of notes processed. |
graincrawl sync --no-transcripts --no-panels |
Skip transcript and panel hydration for this run. |
graincrawl runs --limit <n> |
List recent sync runs. |
graincrawl status |
Show archive counts and the SQLite path. |
private-api, public-api, and desktop-cache are supported sync sources. public-api must be explicitly enabled with allow_public_api = true or GRAINCRAWL_ALLOW_PUBLIC_API=true; its key is read only from GRANOLA_PUBLIC_API_KEY. It archives notes, summaries, and transcripts available to the key, but the official API does not expose panels or deletion events.
Public API sync rejects missing or repeated pagination cursors instead of requesting the same pages indefinitely. Notes already archived remain available, but the failed run is not recorded as successful.
Private API document hydration uses sequential batches of at most 50 IDs, skipping the request when there are no IDs. All batches must succeed before notes are written. A failed batch request stops sync with a nonzero exit and no successful sync-run record; the deletion feed is also deferred, even when earlier batches succeeded. Existing archived notes stay unchanged, though workspace metadata may already have been retained. A successful empty batch response keeps the listed documents. Rerun sync after the upstream failure clears.
Private API sync stops and exits nonzero if an enabled transcript or panel fetch fails. It does not record a successful sync run. Writes completed before the error remain in the archive; later documents and the deletion feed are not processed. Rerun the same sync after the upstream failure clears to finish hydration and process deletions. Existing records are updated by identity rather than duplicated. Successful empty responses are valid, and --no-transcripts or --no-panels skips the corresponding fetch entirely.
Public API rate-limit responses are retried up to three times. Each Retry-After wait is capped at 60 seconds, so a distant retry date or oversized delay cannot stall sync for hours.
All sync sources exit nonzero if the completed run cannot be recorded in the archive. Already archived data remains available; rerun sync once the database write failure is resolved.
| Command | Purpose |
|---|---|
graincrawl notes --limit <n> |
List archived notes. |
graincrawl search <query> --limit <n> |
Search archived notes. |
graincrawl note get <id> |
Show one archived note. |
graincrawl transcripts get <id> |
Show transcript chunks for a note. |
graincrawl panels get <id> |
Show panels for a note. |
graincrawl people --limit <n> |
List retained people source objects. |
graincrawl workspaces --limit <n> |
List retained workspace source objects. |
graincrawl sql <query> |
Run a read-only SQL query against the archive. |
graincrawl tui --limit <n> |
Browse archived notes in the terminal. |
Search matches literal substrings across note titles, bodies, summaries, transcripts, and panel content. Queries and archived text use the same Unicode lowercase conversion, so 脡MILE matches 脡mile. Percent signs, underscores, and backslashes remain literal characters; search does not rewrite archived text.
The TUI detail pane is assembled from the SQLite archive, including note text, transcript chunks, panels, and retained source metadata.
| Command | Purpose |
|---|---|
graincrawl export markdown --out <dir> --limit <n> |
Export notes as Markdown. |
graincrawl snapshot create --out <dir> |
Create a portable crawlkit snapshot. |
graincrawl import <snapshot-dir> |
Merge a snapshot into the archive. |
graincrawl import --replace <snapshot-dir> |
Restore exactly, removing rows absent from the snapshot. |
Merge imports keep existing local payloads on identity conflicts and preserve tombstones found on either side.
Markdown export exits nonzero if a note's transcript or panels cannot be read. The affected note's output file is left unchanged; files exported earlier in the run remain. Empty transcript and panel sections are valid.
Each Markdown file is written completely before replacing its destination, with owner-only read/write permissions (0600). Existing symbolic and hard links are replaced without changing their targets. A write or replacement failure leaves the previous destination intact.
Snapshot shard paths must be relative to the snapshot directory. Imports reject absolute paths and paths that traverse outside that directory.
| Command | Purpose |
|---|---|
graincrawl unlock |
Report whether an explicit unlock is possible. |
graincrawl unlock encrypted-json |
Explicitly access legacy encrypted JSON when enabled and still supported. |
graincrawl sync --source <source> --unlock encrypted-json |
Unlock legacy encrypted source state in memory for that sync. |
graincrawl secrets |
Report graincrawl-managed secret policy without printing secret values. |
graincrawl metadata |
Print crawlkit control metadata for scripts and dashboards. |
graincrawl check-update |
Check GitHub Releases for a newer version. |
graincrawl version |
Show version, commit, and build date. |
Legacy encrypted JSON requires allow_encrypted_json = true and an explicit unlock command or sync flag. Decrypted payloads stay in process memory. The security model documents the Keychain boundary and Granola 7.427+ behavior.
The public API key is never written to graincrawl config or SQLite. Inject it at runtime, for example with op run --env-file <template> -- graincrawl sync --source public-api.
Release-check HTTP requests time out after 30 seconds if the server does not respond.
Every command that returns data accepts the global --json option. For example:
graincrawl --json doctor
graincrawl --json status
graincrawl --json search "decision"
graincrawl --json sql "select source, count(*) as notes from notes group by source"graincrawl metadata describes the stable crawlkit control surfaces available to scripts and status dashboards.
# bash
source <(graincrawl completion bash)
# zsh
source <(graincrawl completion zsh)