npx skills add ...
npx skills add joelhooks/joelclaw --skill contacts
Add, enrich, and manage contacts in Joel's Vault. Fire the Inngest enrichment pipeline for full multi-source dossiers, or create quick contacts manually. Use when: 'add a contact', 'enrich this person', 'who is X', 'VIP contact', 'update contact', or any task involving the Vault/Contacts directory.
npx skills add joelhooks/joelclaw --skill contacts
Manage contacts in ~/Vault/Contacts/. Each contact is a markdown file with YAML frontmatter.
Index file: ~/Vault/Contacts/index.md — wikilink list of all contacts.
See ~/Vault/Contacts/Matt Pocock.md for a fully enriched example.
Send an Inngest event. The contact-enrich function fans out across Slack, Roam, web/GitHub, Granola, Brain-backed recall, and other indexed sources, synthesizes with LLM, and writes the Vault file.
Depth modes:
full (~60s, ~$0.05): All 7 sources + LLM synthesis. Use for new contacts or periodic refresh.quick (~10s, ~$0.01): Slack + memory only. Good for real-time VIP detection.Hints are optional but help: Any known identifiers (Slack ID, GitHub, email, Twitter, website) seed the search and improve results.
For simple contacts where enrichment is overkill:
Write to ~/Vault/Contacts/Person Name.md and add [[Person Name]] to index.md.
Re-run enrichment with the existing vault path:
The synthesizer merges new data with existing content — it won't discard existing facts unless contradicted.
Mark vip: true in frontmatter. VIPs get deep enrichment + ongoing monitoring.
Every VIP gets the full treatment. This is what we did for Kent C. Dodds (Feb 26, 2026):
| Step | Source | What to Capture |
|---|---|---|
| 1. Web presence | Web search {name} + {org} | Bio, role, location, personal details |
| 2. Podcast/interviews | Web search {name} podcast interview | Appearance list, own podcasts, audiences |
| 3. Joel collaborations | Their website, appearances pages | Joint podcasts, co-organized events, shared projects |
| 4. Career timeline | Defuddle 2-3 key interview transcripts | Origin story, career arc, key decisions, values |
| 5. GitHub profile | GitHub API or web | Repos, followers, orgs, contribution patterns |
| 6. X/Twitter profile | X API v2 (use x-api skill) | Bio, followers, recent tweets, engagement |
| 7. Key relationships | Cross-reference transcripts + contacts | Who they work with, who they mention, who we know in common |
| 8. Content catalog | Website crawl (defuddle) | Courses, blog posts, open source projects |
| 9. Audience reach | Podcast counts, social followers | Conference circuit, community presence |
Index to Typesense after enrichment:
discoveries collection (NDJSON, action=upsert)kent-c-dodds) for filteringid, title, url, summary, tags[], timestampVault/Resources/{name}-media-appearances.md reference doc linking back to contactOutput sections in the vault note:
[[wikilinks]] to other contacts)| Channel | Tool | Signal |
|---|---|---|
| Google Alerts | joelclawbot Google account | Name mentions in news, blogs, press |
| X/Twitter list | joelclaw X account | Tweets, engagement |
| GitHub activity | GitHub API (polling) | New repos, releases |
| Podcast RSS | Feed monitoring | New episodes |
| Website changes | Periodic defuddle + diff | Blog posts, launches, bio changes |
High-signal (immediate): course launches, role changes, mentions of Joel/egghead/Skill, fundraising. Low-signal (daily/weekly digest): regular tweets, blog posts, OSS activity.
Joel's Roam archive (~/Code/joelhooks/egghead-roam-research/) contains the full egghead-era graph (2019-2024). Many contacts have extensive history there.
People are tagged with relationship prefixes in Roam:
[[collaborator/Name]] — Strategic partners (Ian Jones, Alex Hillman)[[client/Name]] — egghead instructors (Matt Pocock, Jacob Paris)[[staff/Name]] — egghead team (Will Johnson, Daniel Miller, Maggie Appleton)[[name]] (no prefix) — Informal references (Zac is [[zac]])When extracting person data from Roam, add roam_tag to frontmatter:
This enables future re-queries and cross-referencing.
The EDN file is Datomic-style. Clojure scripts exist at scripts/ for structured analysis. See the roam-research skill for full Datalog patterns.
When you encounter a Slack user ID (<@U0XXXXXXX>):
Then fire enrichment with the resolved name and hints.
contact-enrich (packages/system-bus/src/inngest/functions/contact-enrich.ts)contact/enrich.requested~/Vault/docs/decisions/0133-contact-enrichment-pipeline.md# Name
## Contact Channels
- Slack, email, social handles, website
## Projects
- Active projects, courses, collaborations
## Key Context
- Relationship notes, working style, history
## Recent Activity
- YYYY-MM-DD | channel | summary# Via curl (CLI has OTEL import bug under Bun v1.3.9)
curl -s -X POST "http://localhost:8288/e/$INNGEST_EVENT_KEY" \
-H "Content-Type: application/json" \
-d '[{
"name": "contact/enrich.requested",
"data": {
"name": "Person Name",
"depth": "full",
"hints": {
"slack_user_id": "U0XXXXXXX",
"github": "username",
"twitter": "handle",
"email": "user@example.com",
"website": "https://example.com"
}
},
"ts": EPOCH_MS
}]'---
name: Person Name
aliases: []
role: Role
organizations: [Org]
vip: false
slack_user_id: null
website: null
github: null
twitter: null
email: null
tags: [tag1]
---
# Person Name
## Contact Channels
- ...
## Key Context
- ...{
"name": "contact/enrich.requested",
"data": {
"name": "Person Name",
"vault_path": "Contacts/Person Name.md",
"depth": "full"
}
}cd ~/Code/joelhooks/egghead-roam-research
python3 -c "
import re
with open('egghead-2026-01-19-13-09-38.edn', 'r') as f:
content = f.read()
pattern = r':block/string\s+\"([^\"]*?)\"'
matches = []
for m in re.findall(pattern, content):
if '[[SEARCH_TAG]]' in m.lower():
matches.append(m)
print(f'Found {len(matches)} blocks')
for m in matches[:30]:
print(f' - {m[:200]}')
"python3 -c "
import re
with open('egghead-2026-01-19-13-09-38.edn', 'r') as f:
content = f.read()
pattern = r':node/title\s+\"([^\"]*?SEARCH_TERM[^\"]*?)\"'
for m in re.findall(pattern, content):
print(f' page: {m}')
"roam_tag: "[[collaborator/Ian Jones]]"# Lease token and look up profile
SLACK_USER=$(secrets lease slack_user_token --ttl 5m)
curl -s "https://slack.com/api/users.info?user=U0XXXXXXX" \
-H "Authorization: Bearer $SLACK_USER" | jq '.user.real_name, .user.profile.email'
# Revoke only the exact lease ids acquired by this task; never all sessions’ leases.