feat: GBrain v0.7.0 — Integration Recipes + SKILLPACK Breakout (#39)

* docs: break SKILLPACK into 17 individual guides

The 1,281-line SKILLPACK monolith is now 17 individually linkable guides
in docs/guides/, organized by category: core patterns, data pipelines,
operations, search, and administration.

GBRAIN_SKILLPACK.md becomes a structured index with categorized tables
linking to each guide. The URL stays stable for backward compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add integration guides, architecture docs, and ethos

New documentation directories:
- docs/integrations/ — "Getting Data In" landing page, credential gateway,
  meeting webhooks. Includes recipe format documentation.
- docs/architecture/ — Infrastructure layer doc (import, chunk, embed, search)
- docs/ethos/ — "Thin Harness, Fat Skills" essay with agent decision guide
- docs/designs/ — "Homebrew for Personal AI" 10-star vision document

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add gbrain integrations command + voice-to-brain recipe

New CLI command: gbrain integrations (list/show/status/doctor/stats/test)
- Standalone command, no database connection needed
- Uses gray-matter directly for recipe parsing (not parseMarkdown)
- --json flag on every subcommand for agent-parseable output
- Bare command shows senses/reflexes dashboard
- Health heartbeat via ~/.gbrain/integrations/<id>/heartbeat.jsonl

First recipe: recipes/twilio-voice-brain.md
- Phone calls create brain pages via Twilio + OpenAI Realtime
- Opinionated defaults: caller screening, brain-first lookup, quiet hours
- Outbound call smoke test (GBrain calls the user to prove it works)
- Validate-as-you-go credential testing
- Twilio signature validation for webhook security

Migration file for v0.7.0 with agent-readable changelog.
13 unit tests covering parseRecipe, CLI routing, and recipe validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add Getting Data In to README, update CLAUDE.md and manifest

README: voice calls in intro bullet list, new "Getting Data In" section
with integration table (voice, email, X, calendar) and recipe philosophy.

CLAUDE.md: reference new files (integrations.ts, recipes/, docs/guides/,
docs/integrations/, docs/architecture/, docs/ethos/).

manifest.json: bump to v0.7.0, add recipes_dir field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: v0.7.0 CHANGELOG, TODOS, VERSION bump

CHANGELOG: v0.7.0 entry covering integration recipes, voice-to-brain,
gbrain integrations command, SKILLPACK breakout, and new documentation.

TODOS: 3 new items from CEO/DX reviews (constrained health_check DSL,
community recipe submission, always-on deployment recipes).

VERSION + package.json: bump to 0.7.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: rewrite voice recipe with agent instructions and verified links

Major improvements to recipes/twilio-voice-brain.md:

- Agent preamble: explains WHY sequential execution matters (each step
  depends on the previous), defines 4 stop points where the agent MUST
  pause and verify, tells agent to never say "something went wrong"
  but instead explain the exact error and fix

- User actions are now specific: exact URLs for every credential
  (Twilio console, OpenAI API keys page, ngrok dashboard), what
  buttons to click, what fields to copy, common failure modes

- All URLs verified via web search against current 2026 documentation:
  Twilio SID/token at twilio.com/console, OpenAI keys at
  platform.openai.com/api-keys, ngrok token at
  dashboard.ngrok.com/get-started/your-authtoken

- Cost estimate corrected: OpenAI Realtime is $0.06/min input +
  $0.24/min output (was understated), total ~$20-22/mo for 100 min

- Validate-as-you-go: each credential tested immediately with exact
  curl commands, failure messages explain what went wrong and how to fix

- Smoke test flow: tells user exactly what to say, verifies ALL
  three outputs (messaging notification + brain page + search result)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add "Homebrew for Personal AI" essay (markdown is code)

New essay at docs/ethos/MARKDOWN_SKILLS_AS_RECIPES.md — the distribution
corollary to "Thin Harness, Fat Skills." Argues that markdown skill files
are simultaneously documentation, specification, package, and source code.
The agent is the package manager. The git repo is the app store.

Referenced from SKILLPACK index and CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: rewrite agent instructions as command language, promote skills

The OpenClaw/Hermes install block is now a drill sergeant, not a tour guide.
Every step is an imperative command with exact verification criteria and
explicit stop-on-failure behavior. No FYI, no suggestions, just rails.

Key changes:
- 11-step setup with STOP points after each step
- Exact user instructions for Supabase connection string (what to click,
  what NOT to give the agent, what the string looks like)
- "Verify: run X. You must see Y. If not: Z" after every step
- Skills table now links to both skill files AND guide docs
- Integration recipes table simplified (no "coming soon" placeholders)
- Docs section reorganized: for agents / for humans / reference

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: 4 codex findings + add email-to-brain recipe

Codex review found 4 issues, all fixed:

1. getStatus() returned "configured" if ANY secret was set (e.g. just
   OPENAI_API_KEY). Now requires ALL required secrets before marking
   configured. Prevents false "configured" status and spurious doctor runs.

2. Twilio health check hit unauthenticated endpoint (always 401). Now
   uses authenticated curl with SID:token, matching the setup validation.

3. README anchor docs/GBRAIN_SKILLPACK.md#the-dream-cycle broken after
   SKILLPACK rewrite. Updated to point to docs/guides/cron-schedule.md.

4. Compiled binary can't find recipes/ via import.meta.dir. Added
   GBRAIN_RECIPES_DIR env var override + global bun install path fallback.

Also adds recipes/email-to-brain.md: Gmail deterministic collector pattern
with ClawVisor credential gateway, validate-as-you-go, agent instructions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add email, X, calendar, and meeting sync recipes

Four new integration recipes extracted from production wintermute patterns:

- recipes/email-to-brain.md: Gmail via ClawVisor, deterministic collector
  pattern (code pulls emails with baked-in links, agent does judgment),
  noise filtering, signature detection, digest generation

- recipes/x-to-brain.md: X API v2, timeline + mentions + keyword search,
  deletion detection (diffs previous run, verifies 404), engagement
  velocity tracking, rate limit awareness

- recipes/calendar-to-brain.md: Google Calendar via ClawVisor, historical
  backfill (years of data), daily markdown files with attendees + locations,
  attendee enrichment for brain pages

- recipes/meeting-sync.md: Circleback API, transcript import with speaker
  labels, attendee detection + filtering, entity propagation to people/
  company pages, action item extraction, idempotent by source_id

All recipes follow the same format: agent preamble with sequential execution
rules, validate-as-you-go credentials, exact URLs for API key setup,
stop-on-failure verification, and heartbeat logging.

Updated README, SKILLPACK index, and integrations landing page with all 5 recipes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add Google OAuth as alternative to ClawVisor in email + calendar recipes

Both recipes now offer two auth options:
- Option A: ClawVisor (recommended, handles OAuth + token refresh)
- Option B: Google OAuth2 directly (no extra service, you manage tokens)

Option B includes step-by-step instructions for Google Cloud Console:
exact URLs, which buttons to click, which scopes to add, how to enable
the API, and the OAuth flow for token exchange.

This removes ClawVisor as a hard dependency for getting started.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add implementation guides with pseudocode and test suggestions

Every recipe now includes an "Implementation Guide" section with:

- Production-tested pseudocode the agent can follow to build each collector
- Edge cases and failure modes discovered in real deployment
- Non-obvious implementation details (why the 48h staleness heuristic,
  why Gmail links need authuser, why SSE responses need double-parsing)
- Test suggestions: what the agent should verify after setup

email-to-brain: noise filtering algorithm, signature detection patterns,
  Gmail link generation (authuser is critical), sent-mail dedup

x-to-brain: deletion detection with 3 heuristics (7-day, 48h staleness,
  API verification), engagement velocity thresholds (50 min for 2x, 100
  absolute jump), atomic writes, stdout contract, rate limit handling

calendar-to-brain: smart chunking (monthly for sparse years, weekly for
  dense), attendee filtering (rooms, groups, distros), merge-with-existing
  (only replace ## Calendar section), date/time parsing edge cases

meeting-sync: SSE double-JSON parsing, idempotency double-check (grep +
  filename), auto-tagging from meeting names, git commit after sync

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: 6 new guides from production patterns (wintermute extraction)

New guides extracted and generalized from production deployment:

- repo-architecture.md: Two-repo pattern (agent behavior vs world knowledge).
  Strict boundary rules, decision tree, hard rule: never write knowledge
  to the agent repo.

- sub-agent-routing.md: Model routing table by task type. Signal detector
  pattern (spawn Sonnet on every message). Research pipeline pattern
  (Opus plans, DeepSeek executes, Opus synthesizes). Cost optimization.

- skill-development.md: 5-step cycle (concept, prototype, evaluate, codify,
  cron). MECE discipline (no overlapping skills). Quality bar checklist.
  "If you ask twice, it should already be a skill."

- idea-capture.md: Originality distribution rating (0-100 across 4
  populations). Depth test ("could someone unfamiliar understand WHY?").
  Deep cross-linking mandate. Notability filtering.

- quiet-hours.md: Hold notifications 11pm-8am local time. Held messages
  directory pattern. Timezone-aware delivery. Morning briefing pickup.

- diligence-ingestion.md: 9-step pipeline for data room materials. Detection
  patterns (PDF filenames, spreadsheet tabs, user language). Index.md
  template with bull/bear case. Company page enrichment.

All PII scrubbed. Patterns generalized for any user.
SKILLPACK index updated with 6 new entries. CLAUDE.md references added.
All 37 SKILLPACK links verified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: upgrade all guides to operational playbooks with pseudocode

Every guide now follows the playbook structure:
- Goal: one sentence, what this achieves
- What the User Gets: without this / with this
- Implementation: pseudocode with actual gbrain commands
- Tricky Spots: production-tested gotchas
- How to Verify: test steps the agent runs after setup

Guides upgraded (15 files):
- brain-agent-loop: on_message() loop with read/write/sync pseudocode
- brain-first-lookup: 4-step lookup cascade with exact commands
- brain-vs-memory: routing algorithm for 3 knowledge layers
- compiled-truth: page structure + rewrite vs append rules
- content-media: 3 ingest patterns (YouTube, social, PDFs)
- cron-schedule: full schedule table + dream cycle pseudocode
- enrichment-pipeline: 7-step protocol with tier classification
- entity-detection: spawn pattern + detection prompt + notability filter
- executive-assistant: 3 workflow algorithms (triage, prep, post-inbox)
- meeting-ingestion: 6-step transcript-to-brain flow
- operational-disciplines: 5 executable discipline blocks
- originals-folder: detection + exact-phrasing capture + cross-linking
- search-modes: decision tree for keyword vs hybrid vs direct
- source-attribution: citation format + hierarchy + conflict resolution
- Plus Goal/What User Gets headers on 6 newer guides

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add WebRTC to voice recipe + ngrok Hobby setup guide

Voice recipe updates:
- Added WebRTC endpoint (POST /session, GET /call, POST /tool) for
  browser-based calling with RNNoise noise suppression
- WebRTC pseudocode with the 4 non-obvious gotchas from production
  (voice under audio.output.voice, no turn_detection, no session.update
  on connect, trigger greeting via data channel)
- Recommend ngrok Hobby ($8/mo) for fixed domain instead of free tier
- Fixed domain means URLs never change, Twilio never breaks

New guide: docs/mcp/NGROK_SETUP.md
- How to set up ngrok Hobby for both MCP and voice agent
- Fixed domain setup, watchdog pattern, AI client configuration
- Claude Desktop requires Settings > Integrations (not JSON config)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: add dependency graph + ngrok-tunnel + credential-gateway recipes

Recipes now have real dependencies via the `requires` field:
- voice-to-brain requires ngrok-tunnel (needs public URL for Twilio)
- email-to-brain requires credential-gateway (needs Gmail access)
- calendar-to-brain requires credential-gateway (needs Calendar access)
- x-to-brain and meeting-sync are standalone (direct API keys)

Two new infrastructure recipes:
- ngrok-tunnel: fixed public URL for MCP + voice. Recommends Hobby
  ($8/mo) for a domain that never changes. Includes watchdog pattern.
- credential-gateway: secure Google service access via ClawVisor
  (recommended) or direct OAuth2. One setup, all Google recipes use it.

Moved ngrok from docs/mcp/ to recipes/ — it's shared infrastructure,
not MCP-specific.

README and integrations landing page show dependency chains.
When agent installs voice-to-brain, it sets up ngrok-tunnel first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add infra category, fix dashboard alignment, show dependencies

DX audit found two bugs in gbrain integrations dashboard:

1. Column alignment broken — IDs > 18 chars ran into descriptions
   with no space. Fixed: pad to 22 chars.

2. ngrok-tunnel and credential-gateway showed as SENSES but they're
   infrastructure. Added 'infra' category. Dashboard now shows three
   sections: INFRASTRUCTURE (set up first), SENSES, REFLEXES.

3. Dependencies now shown inline: "AVAILABLE (needs credential-gateway)"

Also added 'requires' field to JSON output for agent consumption.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add frontier model requirement disclaimer to README

GBrain's markdown-is-code approach requires models capable of
interpreting intent and implementing from architecture descriptions.
Tested with Claude Opus 4.6 and GPT-5.4 Thinking. Smaller models
will struggle with the recipe format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add PGLite → Supabase upgrade path to README

Clarify the database progression: start with PGLite (Postgres as WASM,
zero infrastructure, pgvector built in, nothing to install). Graduate
to Supabase or self-hosted Postgres when you need connection pooling,
concurrency, and remote MCP access from Claude Desktop, Cowork,
ChatGPT, Perplexity Computer, or any MCP-compatible agent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: revert PGLite mention (coming in next branch)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: make all 23 guides consistent (Goal/Impl/Tricky/Verify)

Every guide now has exactly these sections in this order:
- ## Goal (one sentence)
- ## What the User Gets (without this / with this)
- ## Implementation (pseudocode with gbrain commands)
- ## Tricky Spots (3-5 numbered gotchas)
- ## How to Verify (3-5 numbered test steps)

11 guides restructured from non-standard headings:
- deterministic-collectors, live-sync, upgrades-auto-update (full rewrites)
- entity-detection, diligence-ingestion, idea-capture, quiet-hours,
  repo-architecture, skill-development, sub-agent-routing (restructured)

23/23 guides now pass consistency audit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: restructure README around the #1 blocker (getting data in)

The README was leading with Postgres and database architecture. Most
users are stuck at step zero: "I have an agent but it doesn't know
anything about my life."

New structure:
1. The Problem — your agent doesn't know your life
2. Getting Data In — integration recipes, front and center
3. The Compounding Thesis — why this matters
4. How this happened — credibility, origin story
5. When you need Postgres — scale, not starting point

Postgres is de-emphasized from a full section to two paragraphs:
"You don't need Postgres to start" and "When you need Postgres"
(1,000+ files, remote MCP access, multiple AI clients).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: move Install to top of README, remove duplicate section

Install now appears right after Getting Data In (line 38), not buried
at line 295. The user sees: Problem → Getting Data In → Install.

Removed the duplicate Install section (262 lines) that was lower in
the README. The agent instructions block, CLI quickstart, and all
content is now in the single Install section near the top.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: move agent install block to first thing in README

"Start here: paste this into your agent" is now the first section,
right after the one-line pitch. No scrolling, no context, no preamble.
User opens the README, sees the paste block, copies it into OpenClaw
or Hermes, and the agent takes over.

Flow: pitch → paste block → Getting Data In → Compounding Thesis → origin story

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: compress install block from 11 steps to 5

The agent install block was 102 lines and 11 steps. Now it's 40 lines
and 5 steps. Same coverage, half the text.

Changes:
- Merged "prove keyword search" + "embed" + "prove hybrid search"
  into one SEARCH step (the user doesn't care about the intermediate)
- Merged skillpack, sync, auto-update, integrations, verification
  into one GO LIVE step with sub-items (post-install polish, not install)
- Shortened database instructions (one line instead of 5 sub-steps)
- Removed redundant preamble ("YOU MUST COMPLETE EVERY STEP" is now
  just "Do not skip steps. Verify each step.")

The 5 steps: INSTALL → DATABASE → IMPORT → SEARCH → GO LIVE

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* security: gitignore all .env files, not just specific ones

CSO audit found .gitignore covered .env.testing and .env.production
but not bare .env. A user creating .env with database credentials
could accidentally commit it.

Fix: .env and .env.* are now gitignored. .env.*.example files are
explicitly un-ignored so templates remain tracked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* security: scrub PII from essay and recipe examples

- 510-MY-GARRY phone mnemonic → "Your Phone Number"
- "Garry → Authenticated Mode" → "Owner → Authenticated Mode"
- "Telegram" → "secure channel" in auth example
- @garrytan → @yourhandle in X recipe example

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-10 23:39:06 -10:00
committed by GitHub
parent 8de04d3827
commit ce15062694
50 changed files with 7428 additions and 1494 deletions

View File

@@ -0,0 +1,396 @@
---
id: calendar-to-brain
name: Calendar-to-Brain
version: 0.7.0
description: Google Calendar events become searchable brain pages. Daily files with attendees, locations, and meeting prep context.
category: sense
requires: [credential-gateway]
secrets:
- name: CLAWVISOR_URL
description: ClawVisor gateway URL (Option A — recommended, handles OAuth for you)
where: https://clawvisor.com — create an agent, activate Google Calendar service
- name: CLAWVISOR_AGENT_TOKEN
description: ClawVisor agent token (Option A)
where: https://clawvisor.com — agent settings, copy the agent token
- name: GOOGLE_CLIENT_ID
description: Google OAuth2 client ID (Option B — direct API access, you manage tokens)
where: https://console.cloud.google.com/apis/credentials — create OAuth 2.0 Client ID
- name: GOOGLE_CLIENT_SECRET
description: Google OAuth2 client secret (Option B)
where: https://console.cloud.google.com/apis/credentials — same page as client ID
health_checks:
- "[ -n \"$CLAWVISOR_URL\" ] && curl -sf $CLAWVISOR_URL/health > /dev/null && echo 'ClawVisor: OK' || [ -n \"$GOOGLE_CLIENT_ID\" ] && echo 'Google OAuth: configured' || echo 'No calendar auth configured'"
setup_time: 20 min
cost_estimate: "$0 (both options are free)"
---
# Calendar-to-Brain: Your Schedule Becomes Searchable Memory
Every calendar event becomes a searchable brain page. Your agent knows who you're
meeting tomorrow, what you discussed last time, and what context matters. Meeting
prep happens automatically because the brain already has the history.
## IMPORTANT: Instructions for the Agent
**You are the installer.** Follow these steps precisely.
**Why this matters:** Calendar data is the richest source of relationship history.
13 years of calendar data tells you who you've met with, how often, where, and
with whom. When someone emails you, the brain already knows your meeting history.
When you have a meeting tomorrow, the agent pulls attendee dossiers automatically.
**The output is daily markdown files:** One file per day at
`brain/daily/calendar/{YYYY}/{YYYY-MM-DD}.md` with all events, attendees, and
locations. These files are the foundation for meeting prep, relationship tracking,
and pattern detection.
**Do not skip steps. Verify after each step.**
## Architecture
```
Google Calendar (multiple accounts)
↓ (ClawVisor credential gateway, paginated)
Calendar Sync Script (deterministic Node.js)
↓ Outputs:
├── brain/daily/calendar/{YYYY}/{YYYY-MM-DD}.md (daily event files)
├── brain/daily/calendar/.raw/events-{range}.json (raw API responses)
└── brain/daily/calendar/INDEX.md (date ranges + monthly summary)
Agent reads daily files
↓ Judgment calls:
├── Attendee enrichment (create/update brain pages for people)
├── Meeting prep (pull context before tomorrow's meetings)
└── Pattern detection (meeting frequency, relationship temperature)
```
## Opinionated Defaults
**Multiple calendar accounts:**
- Work calendar (company domain)
- Personal calendar (gmail.com)
- Previous company calendars (if still accessible)
**Daily file format:**
```markdown
# 2026-04-10 (Thursday)
- 09:00-09:30 **Team standup** (Work) — with Alice, Bob, Carol
- 10:00-11:00 **Board meeting** (Work) 📍 Office — with Diana, Eduardo, Fiona
- 12:00-13:00 **Lunch with Pedro** (Personal) 📍 Chez Panisse — with Pedro Franceschi
- 14:00-14:30 **1:1 with Jordan** (Work) — with Jordan Lee
```
All-day events listed first. Timed events sorted by start time.
Cancelled events are skipped. Attendee names extracted (no email addresses in output).
Calendar label in parentheses. Location with 📍 emoji.
**Historical backfill:** Sync years of calendar data, not just recent. Common ranges:
- Work: 2020-present
- Personal: 2014-present
This builds the full relationship graph from day one.
## Prerequisites
1. **GBrain installed and configured** (`gbrain doctor` passes)
2. **Node.js 18+** (for the sync script)
3. **Google Calendar access** via ONE of:
- **Option A: ClawVisor** (recommended, handles OAuth for you, no token management)
- **Option B: Google OAuth2 directly** (you manage tokens, no extra service needed)
## Setup Flow
### Step 1: Choose and Configure Calendar Access
Ask the user: "How do you want to connect to Google Calendar?
**Option A: ClawVisor (recommended)**
ClawVisor handles OAuth, token refresh, and encryption. You never touch Google
credentials directly. If you already use ClawVisor for email, this uses the same setup.
**Option B: Google OAuth2 directly**
Connect to Google Calendar API directly. No extra service needed, but you manage
OAuth tokens yourself. Good if you don't want another dependency."
#### Option A: ClawVisor Setup
Tell the user:
"I need your ClawVisor URL and agent token.
1. Go to https://clawvisor.com
2. Create an agent (or use existing)
3. Activate the **Google Calendar** service
4. Create a standing task with purpose: 'Full calendar access for historical
backfill and ongoing sync. List events, read event details, search across
all calendars.'
IMPORTANT: Be EXPANSIVE in the task purpose. Narrow purposes block requests.
5. Copy the gateway URL and agent token"
Validate:
```bash
curl -sf "$CLAWVISOR_URL/health" && echo "PASS: ClawVisor reachable" || echo "FAIL"
```
**STOP until ClawVisor validates.**
#### Option B: Google OAuth2 Setup
Tell the user:
"I need Google OAuth2 credentials. Here's exactly how to set them up:
1. Go to https://console.cloud.google.com/apis/credentials
(create a Google Cloud project if you don't have one)
2. Click **'+ CREATE CREDENTIALS'** at the top, select **'OAuth client ID'**
3. If prompted, configure the OAuth consent screen first:
- User type: **External** (or Internal if you have Google Workspace)
- App name: anything (e.g., 'GBrain Calendar')
- Scopes: add **'Google Calendar API .../auth/calendar.readonly'**
- Test users: add your own email
4. Back on Credentials, create the OAuth client ID:
- Application type: **Desktop app**
- Name: anything (e.g., 'GBrain')
5. Click **'Create'**. You'll see the Client ID and Client Secret.
6. Copy both and paste them to me.
Also enable the Calendar API:
7. Go to https://console.cloud.google.com/apis/library/calendar-json.googleapis.com
8. Click **'Enable'**"
Validate the credentials are set:
```bash
[ -n "$GOOGLE_CLIENT_ID" ] && [ -n "$GOOGLE_CLIENT_SECRET" ] \
&& echo "PASS: Google OAuth credentials set" \
|| echo "FAIL: Missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET"
```
Then run the OAuth flow to get an access token:
```bash
# The sync script should handle the OAuth flow:
# 1. Open browser to Google auth URL with calendar.readonly scope
# 2. User grants access
# 3. Script receives auth code, exchanges for access + refresh token
# 4. Stores tokens in ~/.gbrain/google-tokens.json
# 5. Auto-refreshes on expiry
```
**STOP until OAuth flow completes and tokens are stored.**
### Step 2: Identify Calendar Accounts
Ask the user: "Which Google Calendar accounts should I sync? Common setup:
- Work email (e.g., you@company.com)
- Personal email (e.g., you@gmail.com)
- Any previous company emails with calendar history"
For each account, note:
- Email address
- Start year (how far back to sync)
- Label (Work, Personal, etc.)
### Step 3: Set Up the Calendar Sync Script
Create the sync directory:
```bash
mkdir -p calendar-sync
cd calendar-sync
npm init -y
```
The sync script needs these capabilities:
1. **Paginated event retrieval** — Google Calendar API returns max 50 events per
request. The script must paginate through large date ranges. Use monthly chunks
for sparse periods, weekly for dense ones.
2. **Daily markdown generation** — group events by date, format as markdown with
times, attendees, locations, calendar labels
3. **Merge with existing files** — if a daily file already has manual notes, preserve
them when updating calendar data
4. **Index generation** — create INDEX.md with date ranges, event counts, monthly summary
5. **Raw JSON preservation** — save raw API responses to `.raw/` for provenance
### Step 4: Run Historical Backfill
This is the big initial sync. It may take 10-30 minutes depending on how many
years of calendar data you have.
```bash
node calendar-sync.mjs --start 2020-01-01 --end $(date +%Y-%m-%d)
```
Tell the user: "Syncing calendar history from [start year]. This creates one
markdown file per day. For 4 years of data, expect ~1,400 daily files."
Verify:
```bash
ls brain/daily/calendar/2026/ | head -10
```
Should show daily files like `2026-04-01.md`, `2026-04-02.md`, etc.
### Step 5: Import Calendar Data to GBrain
```bash
gbrain import brain/daily/calendar/ --no-embed
gbrain embed --stale
```
Verify:
```bash
gbrain search "meeting" --limit 3
```
Should return calendar pages with event details.
### Step 6: Attendee Enrichment
This is YOUR job (the agent). For each person who appears in calendar events:
1. **Check brain**: `gbrain search "attendee name"` — do they have a page?
2. **Create page if missing**: notable attendees (appears 3+ times) get a brain page
3. **Update existing pages**: add meeting history to timeline:
`- YYYY-MM-DD | Meeting: {event title} [Source: Google Calendar]`
4. **Relationship tracking**: note meeting frequency in compiled truth:
"Met 12 times in last 6 months. Regular 1:1 cadence."
### Step 7: Set Up Weekly Sync
The calendar should sync weekly to stay current:
```bash
# Cron: every Sunday at 10 AM
0 10 * * 0 cd /path/to/calendar-sync && node calendar-sync.mjs --start $(date -v-7d +%Y-%m-%d) --end $(date +%Y-%m-%d)
```
After sync, import new data:
```bash
gbrain sync --no-pull --no-embed && gbrain embed --stale
```
### Step 8: Log Setup Completion
```bash
mkdir -p ~/.gbrain/integrations/calendar-to-brain
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.7.0","status":"ok","details":{"accounts":"ACCOUNT_COUNT","start_year":"YYYY"}}' >> ~/.gbrain/integrations/calendar-to-brain/heartbeat.jsonl
```
Tell the user: "Calendar-to-brain is set up. You have [N] days of calendar history
indexed. I can now prep you for meetings by pulling attendee context from the brain.
Weekly sync keeps it current."
## Implementation Guide
These are production-tested patterns from syncing 13 years of calendar data.
### Smart Chunking (Monthly vs Weekly)
```
generate_chunks(start, end, dense_after='2023-01-01'):
chunks = []
current = start
while current < end:
if current < dense_after:
next = current + 1_MONTH // sparse period: monthly
else:
next = current + 7_DAYS // dense period: weekly
chunks.append({from: current, to: min(next, end)})
current = next
return chunks
```
**Why:** Monthly chunks for sparse years (2014-2023) = ~96 API calls for 8 years.
Weekly for everything would be ~600+ calls. Per-calendar `startYear` avoids
pulling empty months (e.g., don't query 2014-2020 for a calendar created in 2020).
### Attendee Filtering
```
filter_attendees(attendees):
return attendees.filter(a =>
!a.email?.includes('@resource.calendar.google.com') AND // conference rooms
!a.email?.includes('@group.calendar.google.com') AND // mailing lists
!a.name?.startsWith('YC-SF-') // internal distros
)
```
Without this, your attendee list is polluted with "Conference Room A" and
"engineering-all@company.com". You want actual people.
### Merge with Existing Files (Preserve Manual Notes)
```
write_daily_file(date, events, dir):
path = f'{dir}/{date}.md'
calendar_md = format_events(events)
if file_exists(path):
existing = read(path)
if '## Calendar' in existing:
// Replace ONLY the calendar section, keep everything else
before = existing.split('## Calendar')[0]
after_match = regex_search(existing, /## [A-Z](?!alendar)/) // next section
after = after_match ? existing[match_index:] : ''
write(path, f'{before}## Calendar\n\n{calendar_md}\n{after}')
else:
write(path, f'## Calendar\n\n{calendar_md}\n---\n\n{existing}')
else:
write(path, calendar_md)
```
**Critical:** Only touch `## Calendar`. Everything else is preserved. If you
manually added `## Notes` to a daily file, it survives re-sync.
### Date/Time Parsing Edge Cases
```
parse_event_date(event):
// All-day: event.start = "2024-01-15" (no T)
// Timed: event.start = "2024-01-15T10:00:00-08:00" (with T)
if 'T' in event.start:
return event.start[0:10] // extract date from datetime
return event.start // already a date
format_time(iso_str):
if not iso_str or 'T' not in iso_str: return 'all-day'
// Extract hours:minutes, convert to 12-hour
// Edge: 00:00 = 12:00 AM, 12:00 = 12:00 PM, 13:00 = 1:00 PM
```
### What the Agent Should Test After Setup
1. **Monthly vs weekly:** Run from 2014 with dense_after=2023. Verify pre-2023
makes ~12 API calls per year, post-2023 makes ~4 per month.
2. **Attendee filtering:** Create a meeting with a conference room and a mailing
list. Sync. Verify neither appears in the daily file.
3. **Merge preservation:** Add `## Notes` to a daily file manually. Sync calendar.
Verify notes are preserved.
4. **All-day events:** Create an all-day event and a timed event on the same day.
Verify all-day appears first, timed events sorted by start time.
5. **Cancelled events:** Cancel a meeting. Sync. Verify it doesn't appear.
6. **Per-calendar startYear:** Sync a calendar created in 2022 with startYear=2022.
Verify no API calls for years before 2022.
## Cost Estimate
| Component | Monthly Cost |
|-----------|-------------|
| ClawVisor (free tier) | $0 |
| Google Calendar API | $0 (within free quota) |
| **Total** | **$0** |
## Troubleshooting
**No events returned:**
- Check the calendar account email is correct
- Check ClawVisor has Google Calendar service activated
- Check the standing task purpose is expansive enough
- Some calendars may be empty for the requested date range
**Attendee names missing:**
- Google Calendar sometimes returns email addresses instead of display names
- The sync script should extract the display name from the attendee object
- If no display name, use the email prefix (before @)
**Duplicate events:**
- The sync script should be idempotent (same date range = same output)
- If running multiple times, existing daily files are overwritten (not appended)

View File

@@ -0,0 +1,180 @@
---
id: credential-gateway
name: Credential Gateway
version: 0.7.0
description: Secure access to Gmail, Google Calendar, and other Google services. ClawVisor (recommended) or direct Google OAuth.
category: infra
requires: []
secrets:
- name: CLAWVISOR_URL
description: ClawVisor gateway URL (Option A — recommended)
where: https://clawvisor.com — create an agent, copy the gateway URL
- name: CLAWVISOR_AGENT_TOKEN
description: ClawVisor agent token (Option A)
where: https://clawvisor.com — agent settings, copy the agent token
- name: GOOGLE_CLIENT_ID
description: Google OAuth2 client ID (Option B — direct API)
where: https://console.cloud.google.com/apis/credentials — create OAuth 2.0 Client ID
- name: GOOGLE_CLIENT_SECRET
description: Google OAuth2 client secret (Option B)
where: https://console.cloud.google.com/apis/credentials — same page as client ID
health_checks:
- "[ -n \"$CLAWVISOR_URL\" ] && curl -sf $CLAWVISOR_URL/health > /dev/null && echo 'ClawVisor: OK' || [ -n \"$GOOGLE_CLIENT_ID\" ] && echo 'Google OAuth: configured' || echo 'No credential gateway configured'"
setup_time: 15 min
cost_estimate: "$0 (both options are free)"
---
# Credential Gateway: Secure Access to Google Services
Gmail, Google Calendar, Google Contacts, and other services require OAuth
credentials. This recipe sets up secure access that email-to-brain and
calendar-to-brain depend on.
## IMPORTANT: Instructions for the Agent
**You are the installer.** Other recipes depend on this one. If the user wants
email-to-brain or calendar-to-brain, set up credential-gateway FIRST.
**Two options, both free:**
- **Option A: ClawVisor** — handles OAuth, token refresh, and encryption for you.
No token management. If you use multiple Google services, set up ClawVisor once
and all recipes use it.
- **Option B: Google OAuth directly** — no extra service, but you manage tokens
yourself. Good if you don't want another dependency.
**Do not skip steps. Verify after each step.**
## Setup Flow
### Step 1: Choose Your Gateway
Ask the user: "How do you want to connect to Google services (Gmail, Calendar)?
**Option A: ClawVisor (recommended)**
ClawVisor handles OAuth, token refresh, and encryption. Set it up once and
email-to-brain, calendar-to-brain, and any future Google service recipes
all use the same credentials. No token management on your end.
**Option B: Google OAuth2 directly**
Connect to Google APIs directly. No extra service. But you manage OAuth
tokens yourself (they expire, need refresh)."
#### Option A: ClawVisor Setup
Tell the user:
"1. Go to https://clawvisor.com and create an account
2. Create an agent (or use existing one)
3. Activate the services you need:
- **Gmail** (for email-to-brain)
- **Google Calendar** (for calendar-to-brain)
- **Google Contacts** (for enrichment)
4. Create a standing task with a broad purpose. CRITICAL: be EXPANSIVE.
Good purpose: 'Full executive assistant access to Gmail, Calendar, and
Contacts including inbox triage, event listing, contact lookup, and
historical data access for all connected Google accounts.'
Bad purpose: 'email triage' — too narrow, blocks legitimate requests.
5. Copy the **Gateway URL** and **Agent Token** and paste them to me"
Validate:
```bash
curl -sf "$CLAWVISOR_URL/health" \
&& echo "PASS: ClawVisor reachable" \
|| echo "FAIL: ClawVisor not reachable — check the URL"
```
**STOP until ClawVisor validates.**
#### Option B: Google OAuth2 Setup
Tell the user:
"I need Google OAuth2 credentials. Here's exactly how:
1. Go to https://console.cloud.google.com/apis/credentials
(create a Google Cloud project if you don't have one — it's free)
2. Click **'+ CREATE CREDENTIALS'** at the top > **'OAuth client ID'**
3. If prompted to configure the consent screen:
- User type: **External** (or Internal for Google Workspace)
- App name: 'GBrain' (anything works)
- Scopes: add the ones you need:
- Gmail: `https://www.googleapis.com/auth/gmail.readonly`
- Calendar: `https://www.googleapis.com/auth/calendar.readonly`
- Contacts: `https://www.googleapis.com/auth/contacts.readonly`
- Test users: add your own email address
4. Create the OAuth client ID:
- Application type: **Desktop app**
- Name: 'GBrain'
5. Click **'Create'** — copy the **Client ID** and **Client Secret**
6. Enable the APIs you need:
- Gmail: https://console.cloud.google.com/apis/library/gmail.googleapis.com
- Calendar: https://console.cloud.google.com/apis/library/calendar-json.googleapis.com
Click **'Enable'** on each one.
Paste the Client ID and Client Secret to me."
Validate:
```bash
[ -n "$GOOGLE_CLIENT_ID" ] && [ -n "$GOOGLE_CLIENT_SECRET" ] \
&& echo "PASS: Google OAuth credentials set" \
|| echo "FAIL: Missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET"
```
Then run the OAuth flow:
```
// The first time a recipe uses these credentials, it will:
// 1. Open a browser to the Google consent URL
// 2. User grants access
// 3. Script receives auth code, exchanges for access + refresh token
// 4. Stores tokens in ~/.gbrain/google-tokens.json
// 5. Auto-refreshes when tokens expire (refresh token is long-lived)
```
**STOP until OAuth credentials validate.**
### Step 2: Log Setup Completion
```bash
mkdir -p ~/.gbrain/integrations/credential-gateway
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.7.0","status":"ok","details":{"type":"CLAWVISOR_OR_GOOGLE"}}' >> ~/.gbrain/integrations/credential-gateway/heartbeat.jsonl
```
Tell the user: "Credential gateway is set up. Email-to-brain and calendar-to-brain
can now access your Google services."
## Tricky Spots
1. **ClawVisor task purpose must be EXPANSIVE.** "Email triage" is too narrow and
blocks legitimate requests. Use a broad purpose that covers everything you
might want to do with email. The intent verification model checks each
request against the purpose. Narrow = blocked.
2. **Google OAuth tokens expire.** Access tokens last ~1 hour. The refresh token
is long-lived but can be revoked. Store both in `~/.gbrain/google-tokens.json`
with 0600 permissions. The script should auto-refresh on 401.
3. **Google consent screen in "Testing" mode** limits to 100 users and tokens
expire weekly. For personal use this is fine. For production, publish the app.
4. **Multiple Google accounts.** If you have work + personal Gmail, you need to
authorize each one separately in the OAuth flow. ClawVisor handles this
automatically.
## How to Verify
1. **ClawVisor:** `curl $CLAWVISOR_URL/health` returns OK.
2. **Google OAuth:** Tokens exist at `~/.gbrain/google-tokens.json`.
3. **Gmail access:** Run the email collector — it should pull recent messages.
4. **Calendar access:** Run the calendar sync — it should pull today's events.
## Cost Estimate
| Component | Monthly Cost |
|-----------|-------------|
| ClawVisor | $0 (free tier) |
| Google OAuth | $0 (free, no billing needed for personal use) |
---
*Part of the [GBrain Skillpack](../docs/GBRAIN_SKILLPACK.md). See also: [Email-to-Brain](email-to-brain.md), [Calendar-to-Brain](calendar-to-brain.md)*

333
recipes/email-to-brain.md Normal file
View File

@@ -0,0 +1,333 @@
---
id: email-to-brain
name: Email-to-Brain
version: 0.7.0
description: Gmail messages flow into brain pages. Deterministic collector pulls emails, agent analyzes and enriches entities.
category: sense
requires: [credential-gateway]
secrets:
- name: CLAWVISOR_URL
description: ClawVisor gateway URL (Option A — recommended, handles OAuth for you)
where: https://clawvisor.com — create an agent, activate Gmail service
- name: CLAWVISOR_AGENT_TOKEN
description: ClawVisor agent token (Option A)
where: https://clawvisor.com — agent settings, copy the agent token
- name: GOOGLE_CLIENT_ID
description: Google OAuth2 client ID (Option B — direct Gmail API access)
where: https://console.cloud.google.com/apis/credentials — create OAuth 2.0 Client ID
- name: GOOGLE_CLIENT_SECRET
description: Google OAuth2 client secret (Option B)
where: https://console.cloud.google.com/apis/credentials — same page as client ID
health_checks:
- "[ -n \"$CLAWVISOR_URL\" ] && curl -sf $CLAWVISOR_URL/health > /dev/null && echo 'ClawVisor: OK' || [ -n \"$GOOGLE_CLIENT_ID\" ] && echo 'Google OAuth: configured' || echo 'No email auth configured'"
setup_time: 20 min
cost_estimate: "$0 (both options are free)"
---
# Email-to-Brain: Gmail Messages That Update Your Brain
Emails arrive. Brain pages get smarter. The agent reads your inbox, detects
entities, updates person and company pages, extracts action items, and files
everything with source attribution.
## IMPORTANT: Instructions for the Agent
**You are the installer.** Follow these steps precisely.
**The core pattern: code for data, LLMs for judgment.**
Email collection is split into two layers:
1. DETERMINISTIC: code pulls emails, generates Gmail links, detects noise/signatures.
This never fails. Links are always correct. Timestamps are always accurate.
2. LATENT: you (the agent) read the collected emails and make judgment calls.
Who is important? What entities are mentioned? What action items exist?
**Do not try to pull emails yourself.** Use the collector script. It handles
pagination, deduplication, Gmail link generation, and noise filtering. If you
try to do this via raw API calls, you WILL forget links, miss emails, or break
pagination. The collector exists because LLMs kept failing at this.
**Why sequential execution matters:**
- Step 1 validates the credential gateway. Without it, nothing connects to Gmail.
- Step 2 sets up the collector. Without it, you have no emails to analyze.
- Step 3 does the first collection. Without data, Step 4 can't enrich.
- Step 4 is YOUR job: read the digest, update brain pages.
## Architecture
```
Gmail Account(s)
↓ (ClawVisor E2E encrypted gateway)
Email Collector (deterministic Node.js script)
↓ Outputs:
├── messages/{YYYY-MM-DD}.json (structured email data)
├── digests/{YYYY-MM-DD}.md (markdown digest for agent)
└── state.json (pagination state, known IDs)
Agent reads digest
↓ Judgment calls:
├── Entity detection (people, companies mentioned)
├── Brain page updates (timeline entries, compiled truth)
├── Action item extraction
└── Priority classification (urgent / normal / noise)
```
## Opinionated Defaults
**Noise filtering (deterministic, in collector):**
- Skip: noreply@, notifications@, calendar-notification@
- Flag: DocuSign, Dropbox Sign, HelloSign, PandaDoc (signatures needing action)
- Keep: everything else
**Email accounts:** Configure multiple accounts. Common setup:
- Work email (company domain)
- Personal email (gmail.com)
**Digest format:** Daily markdown with sections:
- Signatures pending (DocuSign etc. needing action)
- Messages to triage (real emails from real people)
- Noise (filtered, available if needed)
Every email gets a baked-in Gmail link: `[Open in Gmail](https://mail.google.com/mail/u/?authuser=ACCOUNT#inbox/MESSAGE_ID)` — these are generated by code, never by the LLM, so they are always correct.
## Prerequisites
1. **GBrain installed and configured** (`gbrain doctor` passes)
2. **Node.js 18+** (for the collector script)
3. **Gmail access** via one of:
- ClawVisor (recommended: E2E encrypted credential gateway)
- Google OAuth credentials (direct API access)
- Hermes Gateway (built-in Gmail connector)
## Setup Flow
### Step 1: Validate Credential Gateway
Ask the user: "How do you access Gmail programmatically? Options:
1. ClawVisor (recommended, handles OAuth and encryption)
2. Google OAuth credentials (you manage tokens yourself)
3. Hermes Gateway (if you're using Hermes Agent)"
#### Option A: ClawVisor (recommended)
Tell the user:
"I need your ClawVisor URL and agent token.
1. Go to https://clawvisor.com
2. Create an agent (or use existing)
3. Activate the Gmail service
4. Create a standing task with purpose: 'Full executive assistant email management
including inbox triage, searching by any criteria, reading emails, tracking threads'
IMPORTANT: Be EXPANSIVE in the task purpose. Narrow purposes like 'email triage'
will cause legitimate requests to fail verification.
5. Copy the gateway URL and agent token"
Validate:
```bash
curl -sf "$CLAWVISOR_URL/health" && echo "PASS: ClawVisor reachable" || echo "FAIL"
```
**STOP until ClawVisor validates.**
#### Option B: Google OAuth2 directly
Tell the user:
"I need Google OAuth2 credentials for Gmail access. Here's how:
1. Go to https://console.cloud.google.com/apis/credentials
(create a Google Cloud project if you don't have one)
2. Click **'+ CREATE CREDENTIALS'** > **'OAuth client ID'**
3. If prompted, configure the OAuth consent screen:
- User type: **External** (or Internal for Google Workspace)
- App name: 'GBrain Email' (anything works)
- Scopes: add **'Gmail API .../auth/gmail.readonly'**
- Test users: add your own email address
4. Create the OAuth client ID:
- Application type: **Desktop app**
- Name: 'GBrain'
5. Copy the **Client ID** and **Client Secret**
6. Also enable the Gmail API:
Go to https://console.cloud.google.com/apis/library/gmail.googleapis.com
Click **'Enable'**"
Validate:
```bash
[ -n "$GOOGLE_CLIENT_ID" ] && [ -n "$GOOGLE_CLIENT_SECRET" ] \
&& echo "PASS: Google OAuth credentials set" \
|| echo "FAIL: Missing GOOGLE_CLIENT_ID or GOOGLE_CLIENT_SECRET"
```
Then run the OAuth flow to get tokens:
```bash
# The collector script handles the OAuth flow:
# 1. Opens browser to Google consent URL with gmail.readonly scope
# 2. User grants access
# 3. Script receives auth code, exchanges for access + refresh token
# 4. Stores tokens in ~/.gbrain/google-tokens.json
# 5. Auto-refreshes on expiry
```
**STOP until OAuth flow completes and tokens are stored.**
### Step 2: Set Up the Email Collector
Create the collector directory and script:
```bash
mkdir -p email-collector/data/{messages,digests}
cd email-collector
npm init -y
```
The collector script needs these capabilities:
1. **collect** — pull emails from Gmail via credential gateway, deduplicate by message ID, store as JSON with Gmail links baked in
2. **digest** — generate a markdown digest from collected emails, grouped by: signatures pending, messages to triage, noise
3. **state tracking** — remember last collection timestamp and known message IDs to avoid re-processing
Key design rules for the collector:
- Gmail links are generated by CODE, not by the LLM. Format: `[Open in Gmail](https://mail.google.com/mail/u/?authuser=ACCOUNT#inbox/MESSAGE_ID)`
- Noise filtering is deterministic: noreply, notifications, calendar invites
- Signature detection uses known patterns: DocuSign envelope, Dropbox Sign, HelloSign, PandaDoc
- All state persisted to `data/state.json` (last collect timestamp, known message IDs)
- Output is structured JSON (machine-readable) AND markdown digest (agent-readable)
### Step 3: Run First Collection
```bash
node email-collector.mjs collect
node email-collector.mjs digest
```
Verify: `ls data/digests/` should show today's digest file.
Read the digest. Confirm it contains real emails with working Gmail links.
### Step 4: Enrich Brain Pages
This is YOUR job (the agent). Read the digest. For each email:
1. **Detect entities**: who sent it? Who is mentioned? What companies?
2. **Check the brain**: `gbrain search "sender name"` — do we have a page?
3. **Update brain pages**: if sender has a brain page, append a timeline entry:
`- YYYY-MM-DD | Email from {sender}: {subject} [Source: Gmail, {date}]`
4. **Create new pages**: if sender is notable and has no page, create one
5. **Extract action items**: if the email requires a response or action, log it
6. **Sync**: run `gbrain sync --no-pull --no-embed` to index changes
### Step 5: Set Up Cron
The collector should run every 30 minutes:
```bash
*/30 * * * * cd /path/to/email-collector && node email-collector.mjs collect && node email-collector.mjs digest
```
The agent should read the digest on a schedule (e.g., 3x/day: 9 AM, 12 PM, 3 PM)
and run the enrichment flow from Step 4.
### Step 6: Log Setup Completion
```bash
mkdir -p ~/.gbrain/integrations/email-to-brain
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.7.0","status":"ok"}' >> ~/.gbrain/integrations/email-to-brain/heartbeat.jsonl
```
## Implementation Guide
These are production-tested patterns. Follow them exactly.
### Noise Filtering (Deterministic)
```
NOISE_SENDERS = ['noreply', 'no-reply', 'notifications@', 'calendar-notification',
'mailer-daemon', 'postmaster', 'donotreply']
is_noise(email):
from = email.from.toLowerCase()
return NOISE_SENDERS.some(p => from.includes(p)) // substring match
```
Simple substring matching, not regex. `notifications@slack.com` matches because
`notifications@` is in the pattern list. Order doesn't matter.
### Signature Detection
```
SIGNATURE_PATTERNS = [
/docusign/i, /dropbox sign/i, /hellosign/i, /pandadoc/i,
/please sign/i, /signature needed/i, /ready for your signature/i,
/everyone has signed/i, /you just signed/i
]
is_signature(email):
subject = email.subject || ''
from = email.from || ''
return SIGNATURE_PATTERNS.some(p => p.test(subject) || p.test(from))
```
Test BOTH subject AND from. Signature requests come from services that have
"docusign" in the sender address, not just the subject.
### Gmail Link Generation (CRITICAL)
```
gmail_link(messageId, authuser):
return `https://mail.google.com/mail/u/?authuser=${authuser}#inbox/${messageId}`
```
The `authuser` parameter is CRITICAL. Without it, the link opens in the default
Gmail account, not the right one. Each email record stores its account separately.
Generate these in CODE, never by the LLM. Links must be 100% reliable.
### Deduplication
```
collect():
state = load_state()
since = state.lastCollect ? `newer_than:${hours_since}h` : 'newer_than:1d'
for account in accounts:
inbox = gmail.list(query=since, max=50)
for msg in inbox:
if msg.id in state.knownMessageIds: continue // already seen
record = build_record(msg)
state.knownMessageIds[msg.id] = record
// ALSO pull sent mail to detect replies
sent = gmail.list(query=`from:${account.email} ${since}`, max=30)
for msg in sent:
state.knownMessageIds[msg.id] = {is_sent: true}
```
**Why sent mail matters:** Without it, the digest shows "awaiting response" on
threads you already replied to. Sent mail acts as a negative filter.
### What the Agent Should Test After Setup
1. **Noise filtering:** Send a test email from `noreply@test.com`. Run collect.
Verify it appears in noise section, not triage section.
2. **Gmail links:** Click a link from the digest. Verify it opens the correct
account (not the default one).
3. **Deduplication:** Run collect twice in 1 minute. Verify no duplicate messages.
4. **Sent mail:** Reply to an email manually. Run collect. Verify the thread is
marked as replied-to in the digest.
## Cost Estimate
| Component | Monthly Cost |
|-----------|-------------|
| ClawVisor (free tier) | $0 |
| Gmail API | $0 (within free quota) |
| **Total** | **$0** |
## Troubleshooting
**No emails collected:**
- Check ClawVisor health: `curl $CLAWVISOR_URL/health`
- Check standing task is active and has Gmail service enabled
- Check task purpose is expansive enough (narrow purposes block requests)
**Gmail links don't work:**
- Verify the `authuser` parameter matches the account email
- Gmail links require being logged into the correct Google account
**Digest is empty but collection ran:**
- Check `data/messages/` for JSON files
- All emails might be filtered as noise — check noise filtering rules

380
recipes/meeting-sync.md Normal file
View File

@@ -0,0 +1,380 @@
---
id: meeting-sync
name: Meeting Sync
version: 0.7.0
description: Meeting transcripts from Circleback auto-import into brain pages with attendee detection and entity propagation.
category: sense
requires: []
secrets:
- name: CIRCLEBACK_TOKEN
description: Circleback API token for meeting data access
where: https://app.circleback.ai — Settings > API > generate token
health_checks:
- "curl -sf -H \"Authorization: Bearer $CIRCLEBACK_TOKEN\" \"https://app.circleback.ai/api/mcp\" -X POST -H \"Content-Type: application/json\" -d '{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":1}' > /dev/null && echo 'Circleback: OK' || echo 'Circleback: FAIL'"
setup_time: 15 min
cost_estimate: "$0-17/mo (Circleback free for 10 meetings/mo, Pro $17/mo unlimited)"
---
# Meeting Sync: Transcripts That Become Brain Pages
Every meeting is automatically recorded, transcribed, and imported into your brain
with attendee detection, entity propagation, and action item extraction. You never
take notes again. The brain remembers what was said, who said it, and what needs
to happen next.
## IMPORTANT: Instructions for the Agent
**You are the installer.** Follow these steps precisely.
**Why this is high-value:** Meeting transcripts are the richest signal source.
A 30-minute meeting mentions 5-10 people, 3-5 companies, and generates 2-3 action
items. Each one should propagate to the relevant brain pages. Without this recipe,
meetings are black holes. With it, every meeting compounds the brain.
**The flow:**
1. Circleback records and transcribes the meeting (automatic, no user action)
2. The sync script pulls completed meetings from Circleback API
3. Each meeting becomes a brain page at `brain/meetings/{YYYY-MM-DD}-{slug}.md`
4. YOU (the agent) propagate entities to people/company pages
**Do not skip steps. Verify after each step.**
## Architecture
```
Video Call (Zoom, Google Meet, Teams)
↓ Circleback bot joins automatically
Circleback (recording + transcription + AI summary)
↓ API (JSONRPC 2.0 over HTTP, SSE responses)
Meeting Sync Script (deterministic Node.js)
↓ Outputs:
└── brain/meetings/{YYYY-MM-DD}-{slug}.md
- Frontmatter: source_id, date, duration, attendees, location
- Transcript with speaker labels and timestamps
- Tags inferred from title
Agent reads meeting page
↓ Judgment calls:
├── Entity detection (people, companies, topics)
├── Propagate to attendee brain pages (timeline entries)
├── Action item extraction
└── Cross-reference with calendar data
```
## Opinionated Defaults
**Meeting page format:**
```markdown
---
type: meeting
source_id: cb_abc123
source_type: circleback
title: Weekly Team Sync
date: 2026-04-10
duration: 32 min
attendees: [Alice Chen, Bob Park, Carol Wu]
location: Google Meet
tags: [team, weekly, sync]
---
## Key Points
- Discussed Q2 roadmap priorities
- Alice is blocked on the API migration
- Bob's prototype is ready for review
## Action Items
- [ ] Alice: unblock API migration by Friday
- [ ] Bob: share prototype link in Slack
- [ ] Carol: schedule design review for next week
---
## Transcript
**Alice Chen** (00:00): Let's start with the roadmap update...
**Bob Park** (02:15): The prototype is basically done...
**Carol Wu** (05:30): I have some design feedback on the new flow...
```
**Attendee filtering:**
- Skip calendar resources (e.g., "YC-SF Conference Room")
- Skip group addresses (e.g., "team@company.com")
- Extract display names, not email addresses
**Idempotent by source_id:** If a meeting with the same `source_id` already exists
in the brain, skip it. No duplicates.
## Prerequisites
1. **GBrain installed and configured** (`gbrain doctor` passes)
2. **Node.js 18+** (for the sync script)
3. **Circleback account** (https://circleback.ai) with meetings recorded
## Setup Flow
### Step 1: Get Circleback API Token
Tell the user:
"I need your Circleback API token. Here's where to find it:
1. Go to https://app.circleback.ai
2. Click your profile icon (top right) > Settings
3. Go to the API section
4. Generate a new API token (or copy existing)
5. Paste it to me
Note: Circleback's free tier records up to 10 meetings/month. Pro ($17/mo)
is unlimited. You need at least one recorded meeting for the sync to work."
Validate immediately:
```bash
curl -sf -H "Authorization: Bearer $CIRCLEBACK_TOKEN" \
"https://app.circleback.ai/api/mcp" \
-X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
| grep -q '"result"' \
&& echo "PASS: Circleback API connected" \
|| echo "FAIL: Circleback token invalid"
```
**If validation fails:** "That didn't work. Common issues: (1) make sure you copied
the full token, (2) tokens are long hex strings, (3) check that your Circleback
account is active."
**STOP until Circleback validates.**
### Step 2: Set Up the Meeting Sync Script
```bash
mkdir -p meeting-sync
cd meeting-sync
npm init -y
```
The sync script needs these capabilities:
1. **List meetings** — call Circleback API `list_meetings` with date range
(SSE response format, parse streaming events)
2. **Extract meeting data** — title, attendees, transcript, duration, date
3. **Slugify title** — "Weekly Team Sync" → `weekly-team-sync`
4. **Check for existing** — skip if `brain/meetings/{date}-{slug}.md` exists
5. **Format as markdown** — frontmatter + key points + action items + transcript
6. **Filter attendees** — remove calendar resources, groups, extract display names
7. **Infer tags** — from title keywords (e.g., "board" → board, "1:1" → 1-on-1)
### Step 3: Run First Sync
```bash
node meeting-sync.mjs --days 7
```
This syncs the last 7 days of meetings. For a full backfill:
```bash
node meeting-sync.mjs --start 2026-01-01 --end $(date +%Y-%m-%d)
```
Verify:
```bash
ls brain/meetings/ | head -10
```
Should show files like `2026-04-10-weekly-team-sync.md`.
Tell the user: "Found and synced N meetings. Here are the most recent: [list 3]."
### Step 4: Import to GBrain
```bash
gbrain import brain/meetings/ --no-embed
gbrain embed --stale
```
Verify:
```bash
gbrain search "meeting" --limit 3
```
### Step 5: Propagate to Entity Pages
This is YOUR job (the agent). For each meeting:
1. **Read the meeting page** — understand who attended and what was discussed
2. **For each attendee**, check brain: `gbrain search "attendee name"`
- If page exists: append timeline entry:
`- YYYY-MM-DD | Meeting: {title}. Discussed: {key points relevant to this person} [Source: Circleback]`
- If no page and person is notable: create a brain page
3. **For each company mentioned**: update company page timeline
4. **Action items**: if the meeting has action items, ensure they're tracked
5. **Cross-reference with calendar**: link meeting page to the calendar event
6. **Sync**: `gbrain sync --no-pull --no-embed`
### Step 6: Set Up Cron
Sync 3x daily on weekdays:
```bash
# 10 AM, 4 PM, 9 PM PT on weekdays
0 10,16,21 * * 1-5 cd /path/to/meeting-sync && node meeting-sync.mjs >> /tmp/meeting-sync.log 2>&1
```
Default (no flags): syncs yesterday and today.
### Step 7: Log Setup Completion
```bash
mkdir -p ~/.gbrain/integrations/meeting-sync
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.7.0","status":"ok"}' >> ~/.gbrain/integrations/meeting-sync/heartbeat.jsonl
```
Tell the user: "Meeting sync is set up. Every meeting recorded by Circleback
automatically becomes a searchable brain page. Attendee pages get updated with
meeting history. Action items are extracted. Sync runs 3x daily on weekdays."
## Implementation Guide
These are production-tested patterns from syncing 280+ meeting transcripts.
### SSE Response Parsing
Circleback returns JSONRPC 2.0 over SSE (Server-Sent Events):
```
call_circleback(tool_name, args):
body = {jsonrpc: '2.0', id: next_id(), method: 'tools/call',
params: {name: tool_name, arguments: args}}
res = POST CIRCLEBACK_ENDPOINT, body,
headers: {Authorization: Bearer TOKEN, Accept: 'application/json, text/event-stream'}
text = res.text()
for line in text.split('\n'):
if line.startsWith('data: '):
json = JSON.parse(line[6:]) // strip "data: "
if json.result?.content?.[0]?.text:
return JSON.parse(json.result.content[0].text) // double-parse
if json.error:
throw json.error
```
**Non-obvious:** The response is JSON inside SSE inside JSONRPC. You have to:
1. Strip `data: ` prefix
2. Parse the SSE line as JSON
3. Drill into `result.content[0].text`
4. Parse THAT as JSON again (it's a string containing JSON)
### Idempotency (Double-Check)
```
meeting_exists(source_id):
// Method 1: grep all meeting files for source_id
result = shell(f'grep -rl "source_id: {source_id}" {MEETINGS_DIR}/')
if result: return true
// Method 2: check filename (backup)
slug = slugify(meeting.name)
if file_exists(f'{MEETINGS_DIR}/{date}-{slug}.md'): return true
return false
```
**Why double-check:** grep catches source_id matches even if the filename changed.
File existence catches cases where grep fails (e.g., permission issues).
### Auto-Tagging from Meeting Name
```
auto_tag(meeting_name):
name = meeting_name.toLowerCase()
tags = []
if 'office hours' in name or ' oh ' in name: tags.push('oh')
if 'standup' in name or 'sync' in name: tags.push('sync')
if '1:1' in name or '1on1' in name: tags.push('1on1')
if 'board' in name: tags.push('board')
if 'policy' in name or 'civic' in name: tags.push('civic')
if not tags: tags.push('meeting')
return tags
```
### Meeting Page Structure
```
---
title: "Weekly Team Sync"
type: meeting
date: 2026-04-10
duration: 32 min
source: circleback
source_id: cb_abc123
attendees:
- {name: Alice Chen, email: alice@company.com}
- {name: Bob Park, email: bob@company.com}
tags: [sync]
---
# Weekly Team Sync
## Summary
[Circleback AI summary]
## Attendees
- Alice Chen
- Bob Park
## Action Items
- [ ] Alice: unblock API migration by Friday
---
## Transcript
**Alice Chen** (00:00): Let's start with the roadmap...
**Bob Park** (02:15): The prototype is basically done...
```
### Git Commit After Sync
```
if new_meetings_created > 0:
shell('git add -A', cwd=BRAIN_DIR)
msg = f'sync: {count} meeting(s) from Circleback ({start} to {end})'
shell(f'git commit -m "{msg}"', cwd=BRAIN_DIR)
shell('git push', cwd=BRAIN_DIR)
```
The sync script commits and pushes automatically. This triggers GBrain's
live sync to index the new pages.
### What the Agent Should Test After Setup
1. **SSE parsing:** Verify `SearchMeetings` returns parseable data (the double-JSON
parsing is the most common failure point).
2. **Idempotency:** Sync a meeting, add a note to the file manually, sync again.
Verify the meeting is skipped (not re-created or overwritten).
3. **Attendee filtering:** Sync a meeting that includes a conference room in attendees.
Verify the room doesn't appear in the attendee list.
4. **Auto-tagging:** Sync a meeting named "1:1 with Sarah". Verify tag is `1on1`.
5. **Transcript formatting:** Verify speaker names and timestamps are formatted
correctly (speaker bold, timestamp in parentheses).
6. **Git commit:** Sync 2+ meetings. Verify the git commit message includes the count.
## Cost Estimate
| Component | Monthly Cost |
|-----------|-------------|
| Circleback Free tier | $0 (10 meetings/mo) |
| Circleback Pro | $17/mo (unlimited) |
| **Recommended** | **$17/mo (Pro)** |
## Troubleshooting
**No meetings found:**
- Check that Circleback has recorded meetings (open the Circleback dashboard)
- The Circleback bot must join the meeting for recording to work
- Check the date range: `--days 30` to widen the search
**Transcript is empty:**
- Some meetings may not have transcripts (e.g., no audio, bot was removed)
- Check the Circleback dashboard for the specific meeting's status
**Duplicate meetings:**
- The sync script checks for existing files by source_id
- If duplicates appear, the idempotency check may be failing
- Delete duplicates manually and re-run sync

245
recipes/ngrok-tunnel.md Normal file
View File

@@ -0,0 +1,245 @@
---
id: ngrok-tunnel
name: Public Tunnel
version: 0.7.0
description: Fixed public URL for your brain (MCP server, voice agent, any service). One ngrok account, never changes.
category: infra
requires: []
secrets:
- name: NGROK_AUTHTOKEN
description: ngrok auth token (Hobby tier recommended for fixed domain)
where: https://dashboard.ngrok.com/get-started/your-authtoken — sign up, then copy your authtoken
health_checks:
- "pgrep -f 'ngrok.*http' > /dev/null && echo 'ngrok: running' || echo 'ngrok: NOT running'"
- "curl -sf http://localhost:4040/api/tunnels > /dev/null && echo 'ngrok API: OK' || echo 'ngrok API: FAIL'"
setup_time: 10 min
cost_estimate: "$8/mo for Hobby tier (fixed domain). Free tier works but URLs change on restart."
---
# Public Tunnel: Fixed URL for Your Brain
Your GBrain MCP server and voice agent need public URLs so Claude Desktop,
Perplexity, and Twilio can reach them. ngrok gives you a fixed domain that
never changes.
## IMPORTANT: Instructions for the Agent
**You are the installer.** This is foundational infrastructure. Other recipes
(voice-to-brain, remote MCP) depend on this. Set it up first.
**Why this matters:**
- Voice-to-brain needs a public URL for Twilio webhooks
- Remote MCP needs a public URL for Claude Desktop and Perplexity
- Free ngrok URLs change on every restart, breaking all integrations
- Hobby tier ($8/mo) gives a fixed domain. Set it once, never touch it again.
**Do not skip steps. Verify after each step.**
## Architecture
```
Local services (your machine)
├── GBrain MCP server (port 3000) gbrain serve
└── Voice agent (port 8765) node server.mjs
ngrok tunnel (fixed domain)
└── https://your-brain.ngrok.app
├── /mcp → Claude Desktop, Claude Code, Perplexity
└── /voice → Twilio webhooks
```
## Setup Flow
### Step 1: Create ngrok Account + Get Hobby Tier
Tell the user:
"I need you to create an ngrok account. I strongly recommend Hobby tier ($8/mo)
for a fixed domain that never changes. Without it, every restart breaks your
Twilio webhooks and Claude Desktop connection.
1. Go to https://dashboard.ngrok.com/signup (sign up)
2. Go to https://dashboard.ngrok.com/billing and upgrade to **Hobby** ($8/mo)
3. Go to https://dashboard.ngrok.com/get-started/your-authtoken
4. Copy your **Authtoken** and paste it to me"
Validate:
```bash
ngrok config add-authtoken $NGROK_AUTHTOKEN \
&& echo "PASS: ngrok configured" \
|| echo "FAIL: ngrok auth token rejected"
```
If ngrok is not installed:
- **Mac:** `brew install ngrok`
- **Linux:** `curl -sL https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz | tar xz -C /usr/local/bin`
**STOP until ngrok validates.**
### Step 2: Claim a Fixed Domain
Tell the user:
"1. Go to https://dashboard.ngrok.com/domains
2. Click **'+ New Domain'**
3. Choose a name (e.g., `your-brain.ngrok.app`)
4. Click **'Create'**
5. Tell me the domain name you chose"
If user stayed on free tier (no fixed domain), note that URLs will change on
restart and the watchdog will need to update Twilio. Recommend upgrading later.
### Step 3: Start the Tunnel
```bash
# With fixed domain (Hobby):
ngrok http 8765 --url your-brain.ngrok.app
# Without fixed domain (free):
ngrok http 8765
```
Verify:
```bash
curl -sf http://localhost:4040/api/tunnels \
&& echo "PASS: ngrok tunnel active" \
|| echo "FAIL: ngrok not running"
```
### Step 4: Set Up Watchdog
The tunnel must auto-restart if it dies. Create a watchdog:
```bash
#!/bin/bash
# ngrok-watchdog.sh — run via cron every 2 minutes
# Check if ngrok is running
if ! pgrep -f "ngrok.*http" > /dev/null 2>&1; then
echo "[watchdog] ngrok not running — starting..."
# Install if missing
if ! command -v ngrok > /dev/null 2>&1; then
echo "[watchdog] ngrok not installed"
exit 1
fi
# Start with fixed domain (if configured) or free
if [ -n "$NGROK_DOMAIN" ]; then
nohup ngrok http 8765 --url "$NGROK_DOMAIN" > /dev/null 2>&1 &
else
nohup ngrok http 8765 > /dev/null 2>&1 &
fi
sleep 5
# If no fixed domain, update Twilio webhook with new URL
if [ -z "$NGROK_DOMAIN" ] && [ -n "$TWILIO_ACCOUNT_SID" ]; then
NGROK_URL=$(curl -s http://localhost:4040/api/tunnels 2>/dev/null \
| grep -o '"public_url":"https://[^"]*' | grep -o 'https://.*')
if [ -n "$NGROK_URL" ] && [ -n "$TWILIO_NUMBER_SID" ]; then
curl -s -X POST -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/IncomingPhoneNumbers/$TWILIO_NUMBER_SID.json" \
-d "VoiceUrl=${NGROK_URL}/voice" > /dev/null
echo "[watchdog] Twilio updated: $NGROK_URL"
fi
fi
echo "[watchdog] ngrok started"
else
echo "[watchdog] ngrok running"
fi
```
Add to crontab:
```bash
*/2 * * * * NGROK_DOMAIN=your-brain.ngrok.app /path/to/ngrok-watchdog.sh >> /tmp/ngrok-watchdog.log 2>&1
```
### Step 5: Log Setup Completion
```bash
mkdir -p ~/.gbrain/integrations/ngrok-tunnel
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.7.0","status":"ok","details":{"domain":"NGROK_DOMAIN","tier":"hobby"}}' >> ~/.gbrain/integrations/ngrok-tunnel/heartbeat.jsonl
```
## Connecting AI Clients (after tunnel is running)
**Claude Code:**
```bash
claude mcp add gbrain -t http https://your-brain.ngrok.app/mcp \
-H "Authorization: Bearer YOUR_GBRAIN_TOKEN"
```
**Claude Desktop:**
Go to Settings > Integrations > Add. Enter:
`https://your-brain.ngrok.app/mcp`
IMPORTANT: Claude Desktop does NOT support remote MCP via JSON config.
You MUST use Settings > Integrations in the GUI. This is the #1 setup failure.
**Perplexity Computer:**
Settings > Connectors > Add Remote MCP.
URL: `https://your-brain.ngrok.app/mcp`
## Implementation Guide
### The Watchdog Pattern (from production)
```
watchdog():
// Check: is ngrok running?
if not process_running("ngrok.*http"):
start_ngrok()
sleep(5)
// If no fixed domain, must update Twilio
if no_fixed_domain AND twilio_configured:
new_url = get_ngrok_url() // from localhost:4040/api/tunnels
update_twilio_webhook(new_url + "/voice")
// Check: is the service behind ngrok running?
if not curl_succeeds("http://localhost:PORT/health"):
restart_service()
```
### ngrok Inspect Dashboard
`http://localhost:4040` shows all requests flowing through the tunnel. Use this
to debug MCP connection issues (see request/response headers, latency, errors).
## Tricky Spots
1. **Claude Desktop requires GUI setup.** Adding remote MCP servers via
`claude_desktop_config.json` does NOT work. It silently fails with no error.
You MUST use Settings > Integrations.
2. **Free tier URLs are ephemeral.** They change on every ngrok restart. The
watchdog handles Twilio, but Claude Desktop and Perplexity must be manually
reconfigured. This is why Hobby ($8/mo) is worth it.
3. **One domain, multiple services.** Hobby gives 1 free domain. Route by path
(`/mcp`, `/voice`) on one domain, or pay $8/mo more for a second domain.
4. **The watchdog must run on startup.** If the machine reboots, ngrok won't
auto-start unless you have a watchdog cron or systemd service.
## How to Verify
1. Start tunnel. Visit `https://your-brain.ngrok.app` in a browser.
You should see a response (health check or default page).
2. From Claude Desktop, run `gbrain search "test"`. Results should come back.
3. Kill ngrok. Wait 2 minutes. Check the watchdog restarted it.
4. From a different device (phone), access the same URL. Verify it works.
## Cost Estimate
| Component | Monthly Cost |
|-----------|-------------|
| ngrok Free | $0 (ephemeral URLs, change on restart) |
| ngrok Hobby | $8/mo (1 fixed domain, enough for MCP + voice) |
| ngrok Pro | $20/mo (2+ domains, IP restrictions) |
| **Recommended** | **$8/mo (Hobby)** |
---
*Part of the [GBrain Skillpack](../docs/GBRAIN_SKILLPACK.md). See also: [Voice-to-Brain](twilio-voice-brain.md), [Remote MCP Deployment](../docs/mcp/DEPLOY.md)*

View File

@@ -0,0 +1,466 @@
---
id: twilio-voice-brain
name: Voice-to-Brain
version: 0.7.0
description: Phone calls create brain pages via Twilio + OpenAI Realtime + GBrain MCP. Callers talk, brain pages appear.
category: sense
requires: [ngrok-tunnel]
secrets:
- name: TWILIO_ACCOUNT_SID
description: Twilio account SID (starts with AC)
where: https://www.twilio.com/console — visible on the main dashboard after login
- name: TWILIO_AUTH_TOKEN
description: Twilio auth token (click "Show" next to the SID on the dashboard)
where: https://www.twilio.com/console — click "Show" under Auth Token on the main dashboard
- name: OPENAI_API_KEY
description: OpenAI API key (needs Realtime API access enabled on your account)
where: https://platform.openai.com/api-keys — click "+ Create new secret key", copy immediately (you can't see it again)
health_checks:
- "curl -sf -u \"$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN\" \"https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID.json\" > /dev/null && echo 'Twilio: OK' || echo 'Twilio: FAIL'"
- "curl -sf -H \"Authorization: Bearer $OPENAI_API_KEY\" https://api.openai.com/v1/models > /dev/null && echo 'OpenAI: OK' || echo 'OpenAI: FAIL'"
setup_time: 30 min
cost_estimate: "$15-25/mo (Twilio number $1-2 + voice $0.01/min, OpenAI Realtime $0.06/min input + $0.24/min output)"
---
# Voice-to-Brain: Phone Calls That Create Brain Pages
Call a phone number. Talk. A structured brain page appears with entity detection,
cross-references, and a summary posted to your messaging app.
## IMPORTANT: Instructions for the Agent
**You are the installer.** This recipe is written for YOU (the AI agent) to execute
on behalf of the user. Follow these instructions precisely.
**Why sequential execution matters:** Each step depends on the previous one:
- Step 1 validates prerequisites. If GBrain isn't configured, nothing else works.
- Step 2 collects credentials. If a credential is wrong, Steps 5-7 will silently fail.
- Step 3 creates the ngrok tunnel. Step 5 needs the ngrok URL for the Twilio webhook.
- Step 5 configures Twilio. Step 7 (smoke test) needs Twilio configured to reach your server.
**Do not skip steps. Do not reorder steps. Do not batch multiple steps.**
**Stop points (MUST pause and verify before continuing):**
- After Step 1: all prerequisites pass? If not, fix before proceeding.
- After each credential in Step 2: validation passes? If not, help the user fix it.
- After Step 6: health check passes? If not, debug before smoke test.
- After Step 7: brain page created? If not, troubleshoot before declaring success.
**When something fails:** Tell the user EXACTLY what failed, what it means, and what
to try. Never say "something went wrong." Say "Twilio returned a 401, which means the
auth token is incorrect. Let's re-enter it."
## Architecture
```
Caller (phone)
↓ Twilio (WebSocket, g711_ulaw audio — no transcoding)
Voice Server (Node.js, your machine or cloud)
↓↑ OpenAI Realtime API (STT + LLM + TTS in one pipeline)
↓ Function calls during conversation
GBrain MCP (semantic search, page reads, page writes)
↓ Post-call
Brain page created (meetings/YYYY-MM-DD-call-{caller}.md)
Summary posted to messaging app (Telegram/Slack/Discord)
```
## Opinionated Defaults
These are production-tested defaults from a real deployment. Customize after setup.
**Caller routing (prompt-based, enforced server-side):**
- Owner: OTP challenge via secure channel, then full access (read + write + gateway)
- Trusted contacts: callback verification, scoped write access
- Known contacts (brain score >= 4): warm greeting by name, offer to transfer
- Unknown callers: screen, ask name + reason, take message
**Security:**
- Twilio signature validation on `/voice` endpoint (X-Twilio-Signature header)
- Unauthenticated callers never see write tools
- Caller ID is NOT trusted for auth (OTP or callback required)
---
## Setup Flow
### Step 1: Check Prerequisites
**STOP if any check fails. Fix before proceeding.**
Run these checks and report results to the user:
```bash
# 1. Verify GBrain is configured
gbrain doctor --json
```
If this fails: "GBrain isn't set up yet. Let's run `gbrain init --supabase` first."
```bash
# 2. Verify Node.js 18+
node --version
```
If missing or < 18: "Node.js 18+ is required. Install it: https://nodejs.org/en/download"
```bash
# 3. Check if ngrok is installed
which ngrok
```
If missing:
- **Mac:** "Run `brew install ngrok` in your terminal."
- **Linux:** "Run `snap install ngrok` or download from https://ngrok.com/download"
Tell the user: "All prerequisites checked. [N/3 passed]. [List any that failed and how to fix.]"
### Step 2: Collect and Validate Credentials
Ask for each credential ONE AT A TIME. Validate IMMEDIATELY. Do not proceed to
the next credential until the current one validates.
**Credential 1: Twilio Account SID + Auth Token**
Tell the user:
"I need your Twilio Account SID and Auth Token. Here's exactly where to find them:
1. Go to https://www.twilio.com/console (sign up free if you don't have an account)
2. After logging in, you'll see your **Account SID** right on the main dashboard
(it starts with 'AC' followed by 32 characters)
3. Below it you'll see **Auth Token** — click **'Show'** to reveal it
4. Copy both values and paste them to me"
After the user provides them, validate immediately:
```bash
curl -s -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID.json" \
| grep -q '"status"' \
&& echo "PASS: Twilio credentials valid" \
|| echo "FAIL: Twilio credentials invalid — double-check the SID starts with AC and the auth token is correct"
```
**If validation fails:** "That didn't work. Common issues: (1) the SID should start
with 'AC', (2) make sure you clicked 'Show' to reveal the auth token and copied the
full value, (3) if you just created the account, wait 30 seconds and try again."
**STOP HERE until Twilio validates.**
**Credential 2: OpenAI API Key**
Tell the user:
"I need your OpenAI API key. Here's exactly where to get one:
1. Go to https://platform.openai.com/api-keys
2. Click **'+ Create new secret key'** (top right)
3. Name it something like 'gbrain-voice'
4. Click **'Create secret key'**
5. **Copy the key immediately** — you won't be able to see it again after closing the dialog
6. Paste it to me
Note: your OpenAI account needs Realtime API access. Most accounts have it by default."
After the user provides it, validate immediately:
```bash
curl -sf -H "Authorization: Bearer $OPENAI_API_KEY" \
https://api.openai.com/v1/models > /dev/null \
&& echo "PASS: OpenAI key valid" \
|| echo "FAIL: OpenAI key invalid — make sure you copied the full key (starts with sk-)"
```
**If validation fails:** "That didn't work. Common issues: (1) the key starts with
'sk-', (2) make sure you copied the entire key (it's long), (3) if you just created
it, it's active immediately — no delay needed."
**STOP HERE until OpenAI validates.**
**Credential 3: ngrok Account (Hobby tier recommended)**
Tell the user:
"I need your ngrok auth token. **I strongly recommend the Hobby tier ($8/mo)**
because it gives you a fixed domain that never changes. With the free tier,
your URL changes every time ngrok restarts, breaking Twilio and Claude Desktop.
1. Go to https://dashboard.ngrok.com/signup (sign up)
2. **Recommended:** Go to https://dashboard.ngrok.com/billing and upgrade to
**Hobby** ($8/mo). This gives you a fixed domain.
3. If you upgraded: go to https://dashboard.ngrok.com/domains and click
**'+ New Domain'**. Choose a name (e.g., `your-brain-voice.ngrok.app`).
4. Go to https://dashboard.ngrok.com/get-started/your-authtoken
5. Copy your **Authtoken** and paste it to me
6. Also tell me your fixed domain name (if you created one)"
```bash
ngrok config add-authtoken $NGROK_TOKEN \
&& echo "PASS: ngrok configured" \
|| echo "FAIL: ngrok auth token rejected"
```
If user has a fixed domain, use `--url` flag (Step 3 below).
If user stayed on free tier, URLs will change on restart (the watchdog handles this).
**Credential 4: Messaging Platform (for call summaries)**
Ask the user: "Where should I send call summaries? Options: Telegram, Slack, or Discord."
Based on their choice:
- **Telegram:** "Create a bot via @BotFather on Telegram, copy the bot token, and
tell me which chat/group to send summaries to."
Validate: `curl -sf "https://api.telegram.org/bot$TOKEN/getMe" | grep -q '"ok":true'`
- **Slack:** "Create an Incoming Webhook at https://api.slack.com/apps → your app →
Incoming Webhooks → Add New. Copy the webhook URL."
Validate: `curl -sf -X POST -d '{"text":"GBrain voice test"}' $WEBHOOK_URL`
- **Discord:** "Go to your server → channel settings → Integrations → Webhooks →
New Webhook. Copy the webhook URL."
Validate: `curl -sf -X POST -H "Content-Type: application/json" -d '{"content":"GBrain voice test"}' $WEBHOOK_URL`
Tell the user: "All credentials validated. Moving to server setup."
### Step 3: Start ngrok Tunnel
```bash
# With fixed domain (Hobby tier — recommended):
ngrok http 8765 --url your-brain-voice.ngrok.app
# Without fixed domain (free tier — URL changes on restart):
ngrok http 8765
```
If using a fixed domain, the URL is always `https://your-brain-voice.ngrok.app`.
If using free tier, copy the URL from the ngrok output (changes every restart).
Note: ngrok runs in the foreground. Run it in a background process or new terminal tab.
The same ngrok account can also serve your GBrain MCP server (see
[ngrok Setup](docs/mcp/NGROK_SETUP.md) for the full multi-service pattern).
### Step 4: Create Voice Server
Create the voice server directory and install dependencies:
```bash
mkdir -p voice-agent && cd voice-agent
npm init -y
npm install ws express
```
The voice server needs these components in `server.mjs`:
1. **HTTP server** on port 8765 with:
- `POST /voice` — returns TwiML that opens a WebSocket media stream to `/ws`
- `GET /health` — returns `{ ok: true }`
- Twilio signature validation (`X-Twilio-Signature` header) on `/voice`
2. **WebSocket handler** at `/ws` that:
- Accepts Twilio media stream (g711_ulaw audio)
- Opens a second WebSocket to `wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview`
- Bridges audio bidirectionally (no transcoding — both sides use g711_ulaw)
- Handles `response.function_call_arguments.done` events from OpenAI (tool execution)
- Sends tool results back via `conversation.item.create` with type `function_call_output`
3. **System prompt builder** that takes caller phone number and returns:
- Appropriate greeting based on caller routing rules
- Available tools (read-only for unauthenticated, full for authenticated)
- Instructions: "You are a voice assistant. Search the brain before answering
questions. Take messages from unknown callers. Never hang up first."
4. **Tool executor** that:
- Spawns GBrain MCP client (`gbrain serve` as stdio child process)
- Routes function calls: `search_brain``gbrain query`, `lookup_person``gbrain search` + `gbrain get`
- Gates write tools behind authentication
5. **Post-call handler** that:
- Saves transcript to `brain/meetings/YYYY-MM-DD-call-{caller}.md`
- Posts summary to the user's messaging platform
- Runs `gbrain sync --no-pull --no-embed` to index the new page
6. **WebRTC endpoint** (optional, for browser-based calling):
- `POST /session` — accepts SDP offer, forwards to OpenAI Realtime `/v1/realtime/calls` as multipart form-data, returns SDP answer
- `GET /call` — serves a web client HTML page with:
- WebRTC connection to OpenAI Realtime API
- RNNoise WASM noise suppression (AudioWorklet)
- Push-to-talk AND auto-VAD mode switching
- Pipeline: Microphone → RNNoise denoise → MediaStream → WebRTC → OpenAI
- `POST /tool` — receives tool calls from the WebRTC data channel, executes them, returns results
- This lets users call the voice agent from a browser tab instead of a phone
**WebRTC session creation pseudocode:**
```
POST /session:
sdp = request.body // caller's SDP offer
form = new FormData()
form.append('sdp', sdp)
form.append('session', JSON.stringify({
type: 'realtime',
model: 'gpt-4o-realtime-preview',
audio: {output: {voice: VOICE}},
instructions: buildPrompt(null)
}))
response = POST 'https://api.openai.com/v1/realtime/calls'
Authorization: Bearer OPENAI_API_KEY
body: form
return response.text() // SDP answer
```
**Important WebRTC gotchas:**
- `voice` goes under `audio.output.voice`, not top-level
- Do NOT send `turn_detection` in session config (not accepted by `/v1/realtime/calls`)
- Do NOT send `session.update` on connect (server already configured it)
- Trigger greeting via data channel after WebRTC connects
**Reference implementation:** The architecture above and the OpenAI Realtime API
docs (https://platform.openai.com/docs/guides/realtime) provide the building blocks.
### Step 5: Configure Twilio Phone Number
Tell the user:
"Now I need to set up your Twilio phone number. Here's what to do:
1. Go to https://www.twilio.com/console/phone-numbers/search
2. Search for a number (pick your area code or any available number)
3. Click **'Buy'** next to the number you want (costs $1-2/month)
4. After purchase, go to https://www.twilio.com/console/phone-numbers/incoming
5. Click on your new number
6. Scroll to **'Voice Configuration'**
7. Under **'A call comes in'**, select **'Webhook'**
8. Enter: `https://YOUR-NGROK-URL.ngrok-free.app/voice`
9. Method: **HTTP POST**
10. Click **'Save configuration'**
11. Tell me the phone number you purchased"
Or if the user prefers CLI:
```bash
# Buy a number (US local)
twilio phone-numbers:buy:local --area-code 415
# Configure webhook
twilio phone-numbers:update PHONE_SID \
--voice-url https://YOUR-NGROK-URL.ngrok-free.app/voice \
--voice-method POST
```
### Step 6: Start Voice Server and Verify
```bash
cd voice-agent && node server.mjs
```
**STOP and verify:**
```bash
curl -sf http://localhost:8765/health && echo "Voice server: running" || echo "Voice server: NOT running"
```
If not running: check the server logs for errors. Common issues:
- Port 8765 already in use: `lsof -i :8765` to find what's using it
- Missing environment variables: make sure OPENAI_API_KEY is set
- Module not found: run `npm install` again
### Step 7: Smoke Test (Outbound Call)
**This is the magical moment.** The agent calls the USER to prove the system works.
Tell the user: "Your phone is about to ring. Pick up and talk for about 30 seconds.
Say something like 'Hey, I'm testing my new voice-to-brain system. Remind me to
check the quarterly numbers tomorrow.' When you're done, hang up."
```bash
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json" \
--data-urlencode "To=USER_PHONE_NUMBER" \
--data-urlencode "From=TWILIO_PHONE_NUMBER" \
--data-urlencode "Url=https://YOUR-NGROK-URL.ngrok-free.app/voice" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN"
```
**After the call ends, verify ALL of these:**
1. Messaging notification arrived with call summary
2. Brain page exists:
```bash
gbrain search "call" --limit 1
```
3. The brain page has: transcript, entity mentions, action items
**If the smoke test fails:**
- No ring: check Twilio console for error logs at https://www.twilio.com/console/debugger
- Ring but no voice: check ngrok tunnel is up, check OpenAI key is valid
- Voice works but no brain page: check post-call handler logs, run `gbrain sync` manually
- Brain page but no messaging: check messaging bot token is valid
**STOP HERE until the smoke test passes. Do not declare success until the user
confirms they received the messaging notification AND the brain page exists.**
### Step 8: Set Up Inbound Calling
Tell the user: "The smoke test passed — voice-to-brain is live! Your number is
[TWILIO_NUMBER]. Now let's set up inbound calling."
1. Twilio webhook is already configured from Step 5
2. Ask: "Do you want calls to your existing phone to forward to this number
after a few rings? That way you answer if you can, and the voice agent
picks up if you don't."
3. Configure caller routing rules in the system prompt
4. Add the user's phone number as the "owner" number for full access
### Step 9: Watchdog (Auto-restart)
```bash
# Cron watchdog (every 2 minutes) — add to crontab
*/2 * * * * curl -sf http://localhost:8765/health > /dev/null || (cd /path/to/voice-agent && node server.mjs >> /tmp/voice-agent.log 2>&1 &)
```
If using ngrok, also set up URL monitoring (free ngrok URLs change on restart):
```bash
# Check if ngrok URL changed, update Twilio if so
NGROK_URL=$(curl -s http://localhost:4040/api/tunnels 2>/dev/null | grep -o '"public_url":"https://[^"]*' | grep -o 'https://.*')
if [ -n "$NGROK_URL" ]; then
twilio phone-numbers:update PHONE_SID --voice-url "$NGROK_URL/voice"
fi
```
### Step 10: Log Setup Completion
```bash
mkdir -p ~/.gbrain/integrations/twilio-voice-brain
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.7.0","status":"ok","details":{"phone":"TWILIO_NUMBER","deployment":"local+ngrok"}}' >> ~/.gbrain/integrations/twilio-voice-brain/heartbeat.jsonl
```
Tell the user: "Voice-to-brain is fully set up. Your number is [NUMBER]. Here's
what happens now: anyone who calls gets screened by the voice agent. Known contacts
get a warm greeting. Unknown callers leave a message. Every call creates a brain
page with the full transcript, and you get a summary on [their messaging platform].
The watchdog restarts the server if it crashes."
## Cost Estimate
| Component | Monthly Cost | Source |
|-----------|-------------|--------|
| Twilio phone number | $1-2/mo | [Twilio pricing](https://www.twilio.com/en-us/voice/pricing) |
| Twilio voice minutes (100 min) | $1-2/mo | $0.0085-0.015/min depending on direction |
| OpenAI Realtime input (100 min) | $6/mo | [$0.06/min](https://openai.com/api/pricing/) |
| OpenAI Realtime output (50 min) | $12/mo | [$0.24/min](https://openai.com/api/pricing/) |
| ngrok (free tier) | $0 | Static domain: $8/mo |
| **Total estimate** | **$20-22/mo** | For ~100 min of calls |
## Troubleshooting
**Calls don't connect:**
- Check ngrok: `curl http://localhost:4040/api/tunnels` — if empty, ngrok isn't running
- Check voice server: `curl http://localhost:8765/health` — should return `{"ok":true}`
- Check Twilio debugger: https://www.twilio.com/console/debugger — shows webhook errors
- Check webhook URL: go to https://www.twilio.com/console/phone-numbers/incoming, click your number, verify the webhook URL matches your ngrok URL
**Voice agent doesn't respond:**
- Check OpenAI key: the validation command from Step 2 should still pass
- Check server logs for WebSocket errors (look for "connection refused" or "401")
- Verify Realtime API access: not all OpenAI accounts have it. Check https://platform.openai.com/docs/guides/realtime
**Brain pages not created after call:**
- Run `gbrain doctor` — if it fails, the database connection is broken
- Check if the post-call handler ran (look in server logs for "transcript saved")
- Run `gbrain sync` manually to force indexing
- Check file permissions on the brain repo directory
**ngrok URL keeps changing:**
- Free ngrok URLs change every time ngrok restarts
- The watchdog (Step 9) handles this automatically
- For a permanent URL: upgrade to ngrok paid ($8/mo) for a static domain, or deploy to Fly.io/Railway instead

353
recipes/x-to-brain.md Normal file
View File

@@ -0,0 +1,353 @@
---
id: x-to-brain
name: X-to-Brain
version: 0.7.0
description: Twitter timeline, mentions, and keyword monitoring flow into brain pages. Tracks deletions and engagement velocity.
category: sense
requires: []
secrets:
- name: X_BEARER_TOKEN
description: X API v2 Bearer token (Basic tier minimum, $200/mo for full archive search)
where: https://developer.x.com/en/portal/dashboard — create a project + app, copy the Bearer Token from "Keys and tokens"
health_checks:
- "curl -sf -H \"Authorization: Bearer $X_BEARER_TOKEN\" \"https://api.x.com/2/users/me\" > /dev/null && echo 'X API: OK' || echo 'X API: FAIL'"
setup_time: 15 min
cost_estimate: "$0-200/mo (Free tier: 1 app, read-only. Basic: $200/mo for search + higher limits)"
---
# X-to-Brain: Twitter Monitoring That Updates Your Brain
Your timeline, mentions, and keyword searches flow into brain pages. The collector
tracks deletions, engagement velocity, and narrative patterns. You wake up knowing
what happened on X while you slept.
## IMPORTANT: Instructions for the Agent
**You are the installer.** Follow these steps precisely.
**The core pattern: code for data, LLMs for judgment.**
The X collector is deterministic code. It pulls tweets, detects deletions, tracks
engagement. It NEVER interprets content. YOU (the agent) read the collected data
and make judgment calls: who is important, what entities are mentioned, what
narratives are forming.
**Why sequential execution matters:**
- Step 1 validates the API key. Without it, nothing connects to X.
- Step 2 sets up the collector. Without it, you have no data.
- Step 3 runs the first collection. Without data, you can't enrich.
- Step 4 is YOUR job: read the collected tweets, update brain pages.
**Do not skip steps. Do not reorder. Verify after each step.**
## Architecture
```
X API v2 (Bearer token auth)
↓ Three collection streams:
├── Own timeline: GET /users/{id}/tweets
├── Mentions: GET /users/{id}/mentions
└── Keyword searches: GET /tweets/search/recent
X Collector (deterministic Node.js script)
↓ Outputs:
├── data/tweets/{own,mentions,searches}/{id}.json
├── data/deletions/{id}.json (detected via diff)
├── data/engagement/{id}.json (velocity snapshots)
└── data/state.json (pagination, rate limits)
Agent reads collected data
↓ Judgment calls:
├── Entity detection (people, companies mentioned)
├── Brain page updates (timeline entries)
├── Narrative pattern detection
└── Engagement spike alerts
```
## Opinionated Defaults
**Three collection streams:**
1. **Own timeline** — your tweets, for your own archive and engagement tracking
2. **Mentions** — who is talking about you, for relationship tracking
3. **Keyword searches** — topics you care about, for signal detection
**Deletion detection:**
- Compare tweet IDs from previous run vs current
- If an ID is missing AND the tweet is < 7 days old, call GET /tweets/{id}
- 404 = confirmed deleted. Save the original tweet + deletion timestamp.
- Alert on deletions from accounts you track.
**Engagement velocity:**
- Snapshot likes/retweets/replies for tracked tweets
- Alert if likes doubled AND previous count >= 50
- Alert if likes gained > 100 absolute since last check
- Only write snapshot if metrics actually changed (idempotent)
**Rate limit awareness:**
- Basic tier: 1500 req/15min for timeline, 450 for mentions, 60 for search
- Collector tracks rate limits in state.json
- Back off automatically when approaching limits
## Prerequisites
1. **GBrain installed and configured** (`gbrain doctor` passes)
2. **Node.js 18+** (for the collector script)
3. **X Developer account** with API access
## Setup Flow
### Step 1: Get X API Credentials
Tell the user:
"I need your X API Bearer token. Here's exactly where to get it:
1. Go to https://developer.x.com/en/portal/dashboard
2. If you don't have a developer account, click 'Sign up' (free tier available)
3. Create a new Project (name it anything, e.g., 'GBrain')
4. Inside the project, create a new App
5. Go to the app's 'Keys and tokens' tab
6. Under 'Bearer Token', click 'Generate' (or 'Regenerate')
7. Copy the Bearer Token and paste it to me
Note: Free tier gives read-only access with low limits. Basic tier ($200/mo)
gives search/recent endpoint and higher limits. Pro tier gets full archive search."
Validate immediately:
```bash
curl -sf -H "Authorization: Bearer $X_BEARER_TOKEN" \
"https://api.x.com/2/users/me" \
&& echo "PASS: X API connected" \
|| echo "FAIL: X API token invalid"
```
**If validation fails:** "That didn't work. Common issues: (1) make sure you copied
the Bearer Token, not the API Key or API Secret, (2) Bearer Tokens are long strings
starting with 'AAA...', (3) if you just created the app, the token is valid immediately."
**STOP until X API validates.**
### Step 2: Get Your X User ID
```bash
# Look up the user's X user ID from their handle
curl -sf -H "Authorization: Bearer $X_BEARER_TOKEN" \
"https://api.x.com/2/users/by/username/USERNAME" | grep -o '"id":"[^"]*"'
```
Ask the user for their X handle (e.g., @yourhandle). Look up their user ID.
Save it — the collector needs the numeric ID, not the handle.
### Step 3: Configure the Collector
Create the collector directory:
```bash
mkdir -p x-collector/data/{tweets/{own,mentions,searches},deletions,engagement}
cd x-collector
```
The collector script needs these capabilities:
1. **collect** — pull tweets from three streams:
- Own timeline: `GET /2/users/{id}/tweets` with max_results=100
- Mentions: `GET /2/users/{id}/mentions` with max_results=100
- Keyword searches: configurable search terms via `GET /2/tweets/search/recent`
2. **Deletion detection** — compare previous run's tweet IDs vs current. For missing IDs, verify with individual tweet lookup. 404 = deleted.
3. **Engagement tracking** — snapshot metrics for tracked tweets. Only write if metrics changed.
4. **State management** — save pagination tokens, last run timestamp, rate limit state to `data/state.json`
5. **Atomic writes** — write to .tmp file, then rename (prevents corrupt data on crash)
Configure keyword searches based on what the user cares about:
```json
{
"searches": [
"\"your name\" -from:yourhandle",
"\"your company\" OR \"your product\"",
"topic you track"
]
}
```
### Step 4: Run First Collection
```bash
node x-collector.mjs collect
```
Verify: `ls data/tweets/own/` should contain tweet JSON files.
Show the user a sample: "Found N tweets from your timeline, M mentions, K search results."
### Step 5: Enrich Brain Pages
This is YOUR job (the agent). Read the collected tweets:
1. **Detect entities**: who tweeted? Who is mentioned? What companies/topics?
2. **Check the brain**: `gbrain search "person name"` — do we have a page?
3. **Update brain pages**: for each notable person or company mentioned:
`- YYYY-MM-DD | Tweeted about {topic} [Source: X, @handle, {date}]`
4. **Track narratives**: if someone tweets about the same topic 3+ times in a week, note the pattern in their compiled truth
5. **Flag deletions**: if a tracked account deleted a tweet, note it:
`- YYYY-MM-DD | Deleted tweet: "{content}" [Source: X deletion, detected {date}]`
6. **Sync**: `gbrain sync --no-pull --no-embed`
### Step 6: Set Up Cron
The collector should run every 30 minutes:
```bash
*/30 * * * * cd /path/to/x-collector && node x-collector.mjs collect >> /tmp/x-collector.log 2>&1
```
The agent should review collected data 2-3x daily and run enrichment.
### Step 7: Log Setup Completion
```bash
mkdir -p ~/.gbrain/integrations/x-to-brain
echo '{"ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","event":"setup_complete","source_version":"0.7.0","status":"ok","details":{"user_id":"X_USER_ID"}}' >> ~/.gbrain/integrations/x-to-brain/heartbeat.jsonl
```
## Implementation Guide
These are production-tested patterns from a deployment tracking 19+ accounts.
### Deletion Detection Algorithm
```
detect_deletions(prevIds, currentIds):
for id in prevIds:
if id in currentIds: continue // still exists
stored = load_tweet(id)
if not stored: continue // never stored
// HEURISTIC 1: Only check tweets < 7 days old
age = now - stored.created_at
if age > 7_DAYS: continue // aged out of API window
// HEURISTIC 2: Skip if last seen > 48h ago
staleness = now - stored.last_updated
if staleness > 48_HOURS: continue // fell out of window, not deleted
// HEURISTIC 3: Already logged?
if deletion_file_exists(id): continue
// VERIFY via direct API call
res = GET /tweets/{id}
if res.status == 404 OR (res.ok AND no data):
save_deletion(id, original_tweet, detected_at)
alert(f"DELETION: {author} deleted: {preview}")
```
**Why the heuristics matter:** Without #2 (48h staleness check), you get false
positives on old tweets that just aged out of the API search window. Without #1
(7-day cap), you'd investigate thousands of old tweets on every run.
### Engagement Velocity Tracking
```
track_engagement(id, metrics):
snapshots = load_snapshots(id)
last = snapshots[-1] if snapshots else null
if last AND metrics_equal(last, metrics): return // no change
snapshots.append({timestamp: now, metrics})
if len(snapshots) > 100: snapshots = snapshots[-100:] // cap growth
// Alert conditions (OR logic):
if last:
old_likes = last.like_count
new_likes = metrics.like_count
// Condition 1: 2x on established tweets (>= 50 likes)
if old_likes >= 50 AND new_likes >= old_likes * 2:
alert(f"VELOCITY: {id} likes {old_likes} -> {new_likes}")
// Condition 2: Absolute jump > 100
elif (new_likes - old_likes) > 100:
alert(f"VELOCITY: {id} likes {old_likes} -> {new_likes}")
```
**Threshold design:** `50` minimum prevents noise from small tweets going 2→4.
The `100` absolute jump catches big spikes on tweets with any baseline.
### Atomic File Writes
```
atomic_write(path, obj):
tmp = path + '.tmp'
writeFileSync(tmp, JSON.stringify(obj, null, 2))
renameSync(tmp, path) // atomic on most filesystems
```
If the process dies mid-write, the `.tmp` file is left behind but the original
is untouched. Critical when you have thousands of per-tweet JSON files.
### Rate Limit Handling
```
rate_limits = {} // per endpoint
after_each_request(endpoint, headers):
rate_limits[endpoint] = {
remaining: headers['x-rate-limit-remaining'],
reset: headers['x-rate-limit-reset']
}
is_rate_limited(endpoint, min_remaining=2):
r = rate_limits[endpoint]
return r AND r.remaining <= min_remaining
```
Reserve 2 requests per endpoint so other streams still work. If mentions
hits the limit, own timeline and searches can still run.
### Stdout Contract
The collector prints structured lines the cron agent can parse:
```
RUN_START:{timestamp}
OWN_TWEETS:{total} ({new} new)
MENTIONS:{total} ({new} new)
DELETION_DETECTED:{id}:{author}:{preview}
VELOCITY_ALERT:{id}:likes:{old}->{new}:{minutes}min
RUN_COMPLETE:{timestamp}:tweets_stored={N}:deletions={N}:velocity_alerts={N}
```
### What the Agent Should Test After Setup
1. **Deletion detection:** Post a tweet, collect, delete it, collect again.
Verify deletion is detected on second run.
2. **Rate limit:** Run collect with very low remaining quota. Verify it stops
gracefully and reports which streams were skipped.
3. **Engagement:** Find a tweet with 45 likes. Mock it jumping to 90 (no alert,
< 50 threshold). Then 50→100 (alert: 2x). Then 30→150 (alert: >100 jump).
4. **Deduplication:** Collect, then like one of your own tweets, collect again.
Verify `_collected_at` is preserved (not overwritten).
5. **Atomic writes:** Kill the process mid-collection. Verify no corrupted JSON.
## Cost Estimate
| Component | Monthly Cost |
|-----------|-------------|
| X API Free tier | $0 (read-only, low limits) |
| X API Basic tier | $200/mo (search + higher limits) |
| X API Pro tier | $5,000/mo (full archive) |
| **Recommended** | **$0 (free) or $200 (basic)** |
Free tier works for personal monitoring. Basic tier needed for keyword search.
## Troubleshooting
**API returns 403:**
- Check your app has the right access level (Read or Read+Write)
- Free tier apps can only use basic endpoints
- Some endpoints require Basic or Pro tier
**Rate limited (429):**
- The collector respects rate limits automatically
- If hitting limits frequently, increase the cron interval to 60 minutes
- Check `data/state.json` for rate limit tracking
**No tweets collected:**
- Verify the user ID is correct (numeric, not handle)
- Check the Bearer Token is valid (Step 1 validation)
- Some accounts may have protected tweets (requires OAuth 2.0 user context)