A CLI that fetches new Gmail messages as JSON and triages them via Omnara, sorting your inbox into priority, junk, and follow-up automatically.
This project was built entirely with Omnara across three surfaces:
- Omnara CLI — used to scaffold the project from scratch, write the Gmail integration, and iterate on the codebase from the terminal
- Omnara iOS (voice) — used voice on mobile to design and add the
--triagefeature, define the classification prompt, and wire up the Omnara API integration — all without touching a keyboard - Omnara API — the app itself calls the Omnara API at runtime to create triage sessions, sending fetched emails as a prompt and getting back priority/junk/follow-up classifications
Omnara powered both the development workflow and the end-user product.
- Node.js 18+
- pnpm
- Google OAuth credentials (
credentials.json) - Omnara account with daemon configured (for
--triage)
pnpm install
pnpm run buildCreate a .env file in the project root:
GMAIL_REFRESH_TOKEN=<your-refresh-token>
GMAIL_ACCESS_TOKEN=<your-access-token>
GMAIL_ACCOUNT_NUMBER=1
The access token is auto-refreshed on each run. You also need a credentials.json file with your Google OAuth app config at the project root.
For the --triage flag, Omnara credentials are read from ~/.omnara/creds.json and ~/.omnara/daemon.json, which are created by the Omnara daemon setup.
# Fetch new emails since last run
node --env-file=.env dist/cli.js
# Look back 60 minutes
node --env-file=.env dist/cli.js --minutes-ago 60
# Pipe to jq for filtering
node --env-file=.env dist/cli.js --minutes-ago 200 | jq '.[] | {from, subject}'
# Fetch and triage via Omnara
node --env-file=.env dist/cli.js --minutes-ago 60 --triage--max-body-size <n> Truncate body to N chars (default: 10000)
--max-results <n> Max emails to fetch (default: 100)
--since <epoch> Override last-checked timestamp (Unix seconds)
--minutes-ago <n> Look back N minutes from now
--triage Classify emails via Omnara (creates a session)
--help, -h Show help
When --triage is passed, the CLI sends your fetched emails to Omnara as a session prompt. Omnara classifies each email as:
- PRIORITY — important, needs a response soon
- JUNK — marketing, newsletters, or spam; should be archived
- FOLLOW-UP — not urgent, but worth revisiting later
The triage prompt template lives in src/prompts.ts and can be edited to customize the classification behavior.
JSON output still goes to stdout regardless of --triage, so it composes with pipes.
The CLI saves a timestamp after each run to data/state.json. On the next run (without --since or --minutes-ago), it only fetches emails that arrived after that timestamp. State is saved before fetching begins so emails arriving during a fetch aren't missed.
CLI args → OAuth2 auth (credentials.json + .env tokens)
→ Gmail API query (after:<epoch>)
→ Paginated message ID fetch → concurrent full-message fetch (10 parallel)
→ Recursive body extraction (multipart MIME handling)
→ JSON array to stdout, progress/errors to stderr
→ (if --triage) build prompt → Omnara API → create session
→ Save state (data/state.json) for next run
| File | Role |
|---|---|
cli.ts |
Entry point — arg parsing, orchestration, output |
auth.ts |
OAuth2 client setup, token refresh |
gmail.ts |
Gmail API client — paginated listing, concurrent message fetching |
body.ts |
Recursive multipart MIME body extraction, base64 decoding |
state.ts |
Persists last-checked timestamp between runs |
prompts.ts |
Triage prompt template for Omnara |
omnara.ts |
Omnara API integration — workspace resolution, session creation |
types.ts |
TypeScript interfaces |
pnpm run dev # watch mode (recompiles on change)
pnpm run build # one-time compile
pnpm run check # run CLI with .env loaded