在 Joel 的 Vault 中新增、豐富與管理聯絡人。觸發 Inngest 豐富化管線以取得完整的多來源檔案,或手動快速建立聯絡人。適用於:「新增聯絡人」、「豐富此人資料」、「X 是誰」、「VIP 聯絡人」、「更新聯絡人」或任何涉及 Vault/Contacts 目錄的任務。
Contacts
管理 ~/Vault/Contacts/ 中的聯絡人。每個聯絡人是一個帶有 YAML 前置資料的 Markdown 檔案。
聯絡人檔案位置
~/Vault/Contacts/<Name>.md
索引檔案:~/Vault/Contacts/index.md — 所有聯絡人的 wikilink 列表。
前置資料結構
---
name: Full Name
aliases: [nickname, handle]
role: Current Role / Title
organizations: [Org1, Org2]
vip: true # 或 false
slack_user_id: U0XXXXXXX
slack_dm_channel: D0XXXXXXX # 未知則為 null
website: https://example.com
github: username
twitter: handle
email: user@example.com
tags: [vip, instructor, creator, family, employee]
---
區段
# Name
## Contact Channels
- Slack, email, 社群帳號, 網站
## Projects
- 進行中的專案、課程、合作
## Key Context
- 關係備註、工作風格、歷史
## Recent Activity
- YYYY-MM-DD | 頻道 | 摘要
完整豐富範例請見 ~/Vault/Contacts/Matt Pocock.md。
新增聯絡人
選項 1:觸發豐富化管線(建議)
發送 Inngest 事件。contact-enrich 函數會分散查詢 Slack、Roam、網頁/GitHub、Granola、Brain-backed 回憶及其他索引來源,透過 LLM 綜合處理,並寫入 Vault 檔案。
# 透過 curl(CLI 在 Bun v1.3.9 下有 OTEL 匯入錯誤)
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
}]'
深度模式:
full(約 60 秒,約 $0.05):所有 7 個來源 + LLM 綜合。適用於新聯絡人或定期更新。quick(約 10 秒,約 $0.01):僅 Slack + 記憶。適合即時 VIP 偵測。
提示為選填,但有助於搜尋: 任何已知識別碼(Slack ID、GitHub、email、Twitter、網站)可引導搜尋並改善結果。
選項 2:快速手動建立
適用於簡單聯絡人,無需豐富化:
---
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
- ...
寫入 ~/Vault/Contacts/Person Name.md,並將 [[Person Name]] 加入 index.md。
更新聯絡人
使用現有 Vault 路徑重新執行豐富化:
{
"name": "contact/enrich.requested",
"data": {
"name": "Person Name",
"vault_path": "Contacts/Person Name.md",
"depth": "full"
}
}
綜合器會將新資料與現有內容合併 — 除非矛盾,否則不會捨棄既有事實。
VIP 聯絡人(ADR-0151)
在前置資料中標記 vip: true。VIP 會獲得深度豐富化 + 持續監控。
深度豐富化操作手冊(一次性)
每位 VIP 都會接受完整處理。以下是我們為 Kent C. Dodds 所做的(2026 年 2 月 26 日):
| 步驟 | 來源 | 要擷取的內容 |
|---|---|---|
| 1. 網路足跡 | 網路搜尋 {name} + {org} |
簡介、角色、地點、個人細節 |
| 2. Podcast/訪談 | 網路搜尋 {name} podcast interview |
露面列表、自有 podcast、聽眾 |
| 3. Joel 合作 | 他們的網站、露面頁面 | 共同 podcast、共同舉辦活動、共享專案 |
| 4. 職涯時間軸 | Defuddle 2-3 篇關鍵訪談逐字稿 | 起源故事、職涯軌跡、關鍵決策、價值觀 |
| 5. GitHub 個人檔案 | GitHub API 或網頁 | 儲存庫、跟隨者、組織、貢獻模式 |
| 6. X/Twitter 個人檔案 | X API v2(使用 x-api 技能) | 簡介、跟隨者、近期推文、互動 |
| 7. 關鍵關係 | 交叉比對逐字稿 + 聯絡人 | 他們與誰合作、提及誰、我們共同認識誰 |
| 8. 內容目錄 | 網站爬取(defuddle) | 課程、部落格文章、開源專案 |
| 9. 受眾觸及 | Podcast 集數、社群跟隨者數 | 會議巡迴、社群影響力 |
豐富化後索引至 Typesense:
- 批次匯入露面/內容至
discoveries集合(NDJSON,action=upsert) - 為所有文件加上人物名稱 slug 標籤(例如
kent-c-dodds)以便篩選 - 欄位:
id、title、url、summary、tags[]、timestamp - 撰寫
Vault/Resources/{name}-media-appearances.md參考文件,連結回聯絡人
Vault 筆記中的輸出區段:
- Background & Story(起源、職涯時間軸)
- Teaching/Work Philosophy(或非教育者的對應內容)
- Key Relationships(交叉連結
[[wikilinks]]至其他聯絡人) - Audience & Reach
- Content/Products
- Podcast/Collaboration History with Joel
- Recent Activity(附時間戳)
持續監控(ADR-0151 第 2-4 階段)
| 頻道 | 工具 | 訊號 |
|---|---|---|
| Google 快訊 | joelclawbot Google 帳號 | 新聞、部落格、媒體中提及姓名 |
| X/Twitter 列表 | joelclaw X 帳號 | 推文、互動 |
| GitHub 活動 | GitHub API(輪詢) | 新儲存庫、發行版本 |
| Podcast RSS | Feed 監控 | 新集數 |
| 網站變更 | 定期 defuddle + 差異比對 | 部落格文章、發布、簡介變更 |
高訊號(立即):課程發布、角色變更、提及 Joel/egghead/Skill、募資。
低訊號(每日/週摘要):一般推文、部落格文章、開源活動。
現有 VIP
- 豐富化後透過閘道通知 Joel
- 每週透過排程 cron 更新
- 在頻道情報管線中擁有優先權(ADR-0131、ADR-0132)
- 當 ADR-0151 第 2 階段以上實作後,將獲得持續監控
Roam Research 豐富化
Joel 的 Roam 存檔(~/Code/joelhooks/egghead-roam-research/)包含完整的 egghead 時代圖譜(2019-2024)。許多聯絡人在其中有豐富的歷史。
快速搜尋(Python 正規表示式)
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'找到 {len(matches)} 個區塊')
for m in matches[:30]:
print(f' - {m[:200]}')
"
人物分類
Roam 中的人物會加上關係前綴標籤:
[[collaborator/Name]]— 策略夥伴(Ian Jones、Alex Hillman)[[client/Name]]— egghead 講師(Matt Pocock、Jacob Paris)[[staff/Name]]— egghead 團隊(Will Johnson、Daniel Miller、Maggie Appleton)[[name]](無前綴)— 非正式提及(Zac 是[[zac]])
頁面標題搜尋
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' 頁面: {m}')
"
加入聯絡人
從 Roam 擷取人物資料時,在前置資料中加入 roam_tag:
roam_tag: "[[collaborator/Ian Jones]]"
這有助於未來重新查詢與交叉比對。
Datalog 查詢(進階)
EDN 檔案是 Datomic 風格。Clojure 腳本位於 scripts/ 目錄,用於結構化分析。完整 Datalog 模式請參閱 roam-research 技能。
解析未知人物
當遇到 Slack 使用者 ID(<@U0XXXXXXX>)時:
# 取得臨時 token 並查詢個人檔案
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
然後使用解析出的名稱與提示觸發豐富化。
Inngest 函數
- 函數:
contact-enrich(packages/system-bus/src/inngest/functions/contact-enrich.ts) - 事件:
contact/enrich.requested - ADR:
~/Vault/docs/decisions/0133-contact-enrichment-pipeline.md - 並行數:最多 3
- 來源:Slack、Slack Connect、Roam 存檔、GitHub/網頁、Granola 會議、Brain-backed 回憶及其他索引來源
隱私
- 聯絡人檔案位於 Vault(私人,不在公開儲存庫中)
- Slack 資料保持私密 — 絕不公開顯示
- Email/電話僅供 Joel 參考






