PluginBench
Skill
Fail
Audit score 45

contacts

joelhooks/joelclaw

Add, enrich, and manage contacts in your Vault with multi-source enrichment pipelines.

What is contacts?

Manage contacts stored as markdown files in ~/Vault/Contacts/ with YAML frontmatter. Use the Inngest enrichment pipeline to automatically gather data from Slack, GitHub, Twitter, web search, and other sources, or manually create simple contacts. Ideal for maintaining a rich contact database with relationship context and activity tracking.

  • Fire multi-source enrichment pipeline (7 sources: Slack, Roam, web/GitHub, Granola, memory, Typesense) with LLM synthesis
  • Create contacts manually with markdown + YAML frontmatter or via quick enrichment mode
  • Mark VIP contacts for deep enrichment and ongoing monitoring across web, podcasts, GitHub, Twitter, and RSS feeds
  • Update existing contacts by re-running enrichment, which merges new data without discarding existing facts
  • Search Roam Research archive (egghead-era graph 2019-2024) for historical context and relationship tags
  • Resolve unknown Slack users and cross-reference contacts via wikilinks in index

How to install contacts

npx skills add https://github.com/joelhooks/joelclaw --skill contacts
Prerequisites
  • ~/Vault/Contacts/ directory structure
  • Inngest event endpoint (http://localhost:8288) for enrichment pipeline
  • INNGEST_EVENT_KEY environment variable for pipeline access
  • Optional: Slack token (for user lookup), GitHub API access, X/Twitter API v2 (for VIP monitoring)
  • Optional: Roam Research archive at ~/Code/joelhooks/egghead-roam-research/ for historical enrichment
Claude Code
Cursor
Windsurf
Cline

How to use contacts

  1. 1.Create a contact via enrichment: POST to Inngest with name and optional hints (slack_user_id, github, email, twitter, website)
  2. 2.Choose depth mode: 'full' for comprehensive multi-source synthesis (~60s, ~$0.05) or 'quick' for Slack + memory only (~10s, ~$0.01)
  3. 3.Review the generated markdown file at ~/Vault/Contacts/<Name>.md with frontmatter and populated sections
  4. 4.For manual creation: write markdown directly to ~/Vault/Contacts/<Name>.md and add [[Name]] wikilink to index.md
  5. 5.Update a contact: re-run enrichment with vault_path parameter; the synthesizer merges new data with existing content
  6. 6.For VIPs: mark vip: true in frontmatter and follow deep enrichment playbook (web, podcasts, GitHub, Twitter, relationships, content catalog)
  7. 7.Monitor VIP activity: set up Google Alerts, Twitter list, GitHub polling, and RSS feeds for ongoing signals

Use cases

Good for
  • Onboard a new collaborator: fire full enrichment with hints (Slack ID, GitHub, email) to auto-populate bio, projects, and contact channels
  • Maintain VIP dossiers: deep-enrich key contacts with career timeline, teaching philosophy, audience reach, and collaboration history
  • Quick contact creation: manually add simple contacts (role, email, org) without enrichment overhead
  • Periodic refresh: re-run enrichment on existing contacts to capture new projects, role changes, and recent activity
  • Historical research: query Roam archive to extract past collaboration notes, relationship context, and egghead-era interactions
Who it's for
  • Knowledge workers managing professional networks and collaborations
  • Podcast hosts or course creators tracking guest/instructor relationships
  • Team leads maintaining organizational contact databases with rich context
  • Researchers or writers building subject-matter-expert networks
  • Anyone using a personal vault (Obsidian, Logseq) for relationship intelligence

contacts FAQ

What's the difference between 'full' and 'quick' enrichment depth?

'full' (~60s, ~$0.05) queries all 7 sources (Slack, Roam, web/GitHub, Granola, memory, Typesense) + LLM synthesis for comprehensive dossiers. 'quick' (~10s, ~$0.01) uses only Slack + memory for real-time VIP detection. Use full for new contacts or periodic refresh; quick for rapid triage.

Do I need to provide hints when enriching a contact?

No, hints are optional but recommended. Known identifiers (Slack ID, GitHub username, email, Twitter handle, website) seed the search and improve enrichment quality. Provide what you know.

Will re-running enrichment overwrite my existing notes?

No. The synthesizer merges new data with existing content and only overwrites facts if contradicted by new sources. Your manual edits and context are preserved.

How do I look up a contact by Slack user ID?

Use the Slack API with a leased token: curl to users.info endpoint with the user ID. The skill provides a bash example for resolving unknown Slack users.

What's the VIP contact workflow?

Mark vip: true in frontmatter. Follow the deep enrichment playbook: web presence, podcast/interviews, collaborations, career timeline, GitHub, Twitter, key relationships, content catalog, and audience reach. Then set up ongoing monitoring via Google Alerts, Twitter lists, GitHub polling, and RSS feeds for high-signal changes.

Full instructions (SKILL.md)

Source of truth, from joelhooks/joelclaw.


name: contacts displayName: Contacts description: "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." version: 1.0.0 author: joel tags: [joelclaw, contacts, vault, enrichment, people]

Contacts

Manage contacts in ~/Vault/Contacts/. Each contact is a markdown file with YAML frontmatter.

Contact File Location

~/Vault/Contacts/<Name>.md

Index file: ~/Vault/Contacts/index.md — wikilink list of all contacts.

Frontmatter Schema

---
name: Full Name
aliases: [nickname, handle]
role: Current Role / Title
organizations: [Org1, Org2]
vip: true  # or false
slack_user_id: U0XXXXXXX
slack_dm_channel: D0XXXXXXX  # null if unknown
website: https://example.com
github: username
twitter: handle
email: user@example.com
tags: [vip, instructor, creator, family, employee]
---

Sections

# 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

See ~/Vault/Contacts/Matt Pocock.md for a fully enriched example.

Adding a Contact

Option 1: Fire the Enrichment Pipeline (preferred)

Send an Inngest event. The contact-enrich function fans out across 7 sources (Slack, Roam, web/GitHub, Granola, recall memory, Typesense), synthesizes with LLM, and writes the Vault file.

# 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
  }]'

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.

Option 2: Quick Manual Create

For simple contacts where enrichment is overkill:

---
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
- ...

Write to ~/Vault/Contacts/Person Name.md and add [[Person Name]] to index.md.

Updating Contacts

Re-run enrichment with the existing vault path:

{
  "name": "contact/enrich.requested",
  "data": {
    "name": "Person Name",
    "vault_path": "Contacts/Person Name.md",
    "depth": "full"
  }
}

The synthesizer merges new data with existing content — it won't discard existing facts unless contradicted.

VIP Contacts (ADR-0151)

Mark vip: true in frontmatter. VIPs get deep enrichment + ongoing monitoring.

Deep Enrichment Playbook (one-time)

Every VIP gets the full treatment. This is what we did for Kent C. Dodds (Feb 26, 2026):

StepSourceWhat to Capture
1. Web presenceWeb search {name} + {org}Bio, role, location, personal details
2. Podcast/interviewsWeb search {name} podcast interviewAppearance list, own podcasts, audiences
3. Joel collaborationsTheir website, appearances pagesJoint podcasts, co-organized events, shared projects
4. Career timelineDefuddle 2-3 key interview transcriptsOrigin story, career arc, key decisions, values
5. GitHub profileGitHub API or webRepos, followers, orgs, contribution patterns
6. X/Twitter profileX API v2 (use x-api skill)Bio, followers, recent tweets, engagement
7. Key relationshipsCross-reference transcripts + contactsWho they work with, who they mention, who we know in common
8. Content catalogWebsite crawl (defuddle)Courses, blog posts, open source projects
9. Audience reachPodcast counts, social followersConference circuit, community presence

Index to Typesense after enrichment:

  • Batch-import appearances/content to discoveries collection (NDJSON, action=upsert)
  • Tag all docs with person's name slug (e.g. kent-c-dodds) for filtering
  • Fields: id, title, url, summary, tags[], timestamp
  • Write a Vault/Resources/{name}-media-appearances.md reference doc linking back to contact

Output sections in the vault note:

  • Background & Story (origin, career timeline)
  • Teaching/Work Philosophy (or equivalent for non-educators)
  • Key Relationships (cross-linked [[wikilinks]] to other contacts)
  • Audience & Reach
  • Content/Products
  • Podcast/Collaboration History with Joel
  • Recent Activity (timestamped)

Ongoing Monitoring (Phase 2-4 of ADR-0151)

ChannelToolSignal
Google Alertsjoelclawbot Google accountName mentions in news, blogs, press
X/Twitter listjoelclaw X accountTweets, engagement
GitHub activityGitHub API (polling)New repos, releases
Podcast RSSFeed monitoringNew episodes
Website changesPeriodic defuddle + diffBlog 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.

Current VIPs

  • Get notified to Joel via gateway after enrichment
  • Are refreshed weekly via scheduled cron
  • Have priority in channel intelligence pipeline (ADR-0131, ADR-0132)
  • Get ongoing monitoring when ADR-0151 Phase 2+ is implemented

Roam Research Enrichment

Joel's Roam archive (~/Code/joelhooks/egghead-roam-research/) contains the full egghead-era graph (2019-2024). Many contacts have extensive history there.

Quick Search (Python regex)

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]}')
"

People Taxonomy

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]])

Page Title Search

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}')
"

Adding to Contacts

When extracting person data from Roam, add roam_tag to frontmatter:

roam_tag: "[[collaborator/Ian Jones]]"

This enables future re-queries and cross-referencing.

Datalog Queries (advanced)

The EDN file is Datomic-style. Clojure scripts exist at scripts/ for structured analysis. See the roam-research skill for full Datalog patterns.

Resolving Unknown People

When you encounter a Slack user ID (<@U0XXXXXXX>):

# 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'
secrets revoke --all

Then fire enrichment with the resolved name and hints.

Inngest Function

  • Function: contact-enrich (packages/system-bus/src/inngest/functions/contact-enrich.ts)
  • Event: contact/enrich.requested
  • ADR: ~/Vault/docs/decisions/0133-contact-enrichment-pipeline.md
  • Concurrency: 3 max
  • Sources: Slack, Slack Connect, Roam archive, GitHub/web, Granola meetings, recall memory, Typesense

Privacy

  • Contact files are in Vault (private, not in public repos)
  • Slack data stays private — never surface in public content
  • Email/phone are stored for Joel's reference only

Related skills

More from joelhooks/joelclaw and the wider catalog.

DEdefuddle logo

defuddle

joeseesun/defuddle-skill

Extract clean article content from web pages or local HTML files. Removes clutter (ads, sidebars, nav) and returns readable content with metadata.

1.1k installs
KNknowledge-site-creator logo

knowledge-site-creator

joeseesun/knowledge-site-creator

一句话生成任何领域的知识学习网站。AI自动理解主题、创作内容、生成页面、部署上线。适用于任何需要系统学习的知识领域:进化心理学、大模型术语、化学元素、历史事件等。

840 installs
QIqiaomu-markdown-proxy logo

qiaomu-markdown-proxy

joeseesun/markdown-proxy

|

732 installs
OPopencli logo

opencli

joeseesun/opencli-skill

CLI tool to browse and interact with 20+ social/content sites using Chrome login—no credentials needed.

3.3k installs
QIqiaomu-goal-meta-skill logo

qiaomu-goal-meta-skill

joeseesun/qiaomu-goal-meta-skill

|

630 installsAudited
QIqiaomu-mondo-poster-design logo

qiaomu-mondo-poster-design

joeseesun/qiaomu-mondo-poster-design

一句话生成大师级海报、书籍封面、专辑封面和各类设计作品。无需懂PS、配色或艺术史,AI自动选择最佳风格(基于33+位传奇设计师)。支持多平台多比例:公众号封面(21:9)、小红书配图(3:4)、文章配图(16:9)、书籍封面(9:16)、专辑封面(1:1)、电影海报(9:16)。包含AI提示词优化、风格对比、图生图转换功能。触发词:"Mondo风格"、"书籍封面设计"、"专辑封面"、"海报设计"、"读书笔记配图"、"公众号封面"、"小红书配图"、"文章配图"。One-sentence generation of master-level posters, book covers, album covers and designs. 33+ legendary designer styles with multi-platform aspect ratio support (21:9, 16:9, 3:4, 1:1, 9:16).

1.0k installs