Birdfolio
Birdfolio turns bird photos into a personal life list. Users photograph birds in the wild, send the photo to you, and you identify the species with Vision. You.com provides real-time rarity and regional data. Each sighting is logged to a life list with a Pokémon-inspired rarity tier (Common / Rare / Super Rare) and gets a visual trading card sent back via Telegram.
Data lives in: Railway PostgreSQL (via API) + local birdfolio/ folder (cards, birds, config)
Scripts live in: {baseDir}/scripts/
API: https://api-production-d0e2.up.railway.app (also saved to birdfolio/config.json after init)
Schema reference: {baseDir}/references/data-schema.md
Search queries: INLINECODE5
Note on --workspace & --api-url: Every data script accepts --workspace (absolute path to birdfolio/) and --api-url (API base URL). After init_birdfolio.py runs, both the API URL and Telegram ID are saved to birdfolio/config.json and read automatically — subsequent scripts only need --workspace.
Telegram ID: Read from the inbound message metadata (sender_id). Pass as --telegram-id to init_birdfolio.py on first setup.
1. Setup Flow
Trigger: User says "Set up my Birdfolio", "set my region", or sends a photo before setup exists.
Check first: If birdfolio/config.json exists in your workspace, setup is already done — skip to the relevant flow.
Steps:
- 1. Ask: "What's your home region? (e.g. California, Texas, United Kingdom)"
- 2. Run to create the workspace folder structure and register the user in the API:
CODEBLOCK0
- 3. Search You.com (run all three):
CODEBLOCK1
- 4. From results, build a checklist with 10 common, 5 rare, 1 super rare species. Use classification signals from
{baseDir}/references/you-search-queries.md.
- 5. Write the populated checklist to
birdfolio/checklist.json in your workspace:
CODEBLOCK2
- 6. Reply with a welcome message and checklist preview:
🦅 Birdfolio is set up for {region}!
Your checklist:
Common (10): American Robin, House Sparrow, ...
Rare (5): Great Blue Heron, ...
Super Rare: California Condor
Send me a bird photo to start collecting!
2. Bird Identification Flow
Trigger: User sends a photo.
Getting the photo file path: When a user sends a photo via Telegram, OpenClaw downloads it and makes the local file path available in the message attachment metadata. Capture this path — you'll need it for card generation in Step 5. If OpenClaw provides the image inline without a path, use exec to find the most recently downloaded file in OpenClaw's temp/media folder, or check %APPDATA%\openclaw\media\ on Windows. Save the photo to birdfolio/birds/{slug}-{timestamp}.jpg for permanent storage:
CODEBLOCK4
Step 1 — Identify with Vision
The submitted photo is directly visible in your context. Analyze it (or use the image tool if it's not inline):
CODEBLOCK5
Rarity rules:
- - Bird IS on the checklist → use its tier:
common, rare, or INLINECODE24 - Bird is NOT on the checklist → use
bonus (shows a neutral "Bonus Find" badge, no rarity assigned)
Confidence rules:
- -
"high" → proceed automatically, no confirmation needed - INLINECODE27 → ask: "I think this might be a [species] — based on [features]. Does that look right to you?" → wait for confirmation before continuing
- INLINECODE28 → reply: "This photo isn't clear enough for me to be confident. Could you send a clearer shot?" → stop, do not log anything
Step 2 — Rarity lookup
Search You.com:
CODEBLOCK6
Classify using these signals:
| Tier | Script value | Signals |
|---|
| Common 🟢 | INLINECODE29 | "abundant", "widespread", "year-round resident", >50% of checklists |
| Rare 🟡 |
rare | "uncommon", "seasonal", "migratory", "occasional", 5–50% of checklists |
| Super Rare 🔴 |
superRare | "rare", "vagrant", "accidental", "endangered", <5% of checklists |
When unsure → default to rare. Always use the script value (e.g. superRare, not Super Rare) when passing --rarity to any script.
Step 3 — Get a fun fact
Search You.com:
CODEBLOCK7
Extract one punchy fact (1–2 sentences).
Step 4 — Log the sighting
Save the sighting to birdfolio/lifeList.json in your workspace:
CODEBLOCK8
Capture from output: isLifer, totalSightings, totalSpecies.
Step 5 — Update checklist
Mark the species as found in birdfolio/checklist.json:
CODEBLOCK9
Step 6 — Generate trading card
The card is a two-column design: the user's photo fills the left panel (280px), a solid dark info panel sits on the right. Always use the user's actual submitted photo — not a stock image.
Step 6a — Detect bird position with Vision:
Use the image tool on the submitted photo:
"Where is the bird positioned horizontally in this photo? Give me approximately what percentage from the left edge the bird's center is (0–100)."
Convert the answer to a CSS value: "40% center", "60% center", "center center", etc. Use this as --object-position.
Step 6b — Generate the card HTML with the embedded photo:
CODEBLOCK10
INLINECODE46 embeds the user's actual photo as base64 directly into the HTML. No separate embed step needed.
Fallback if photo path is unavailable: omit --image-path and pass --image-url "<stock photo URL>" instead (find a URL via You.com: "{commonName} bird photo wildlife").
Capture cardPath from output.
Step 6c — Screenshot, save, upload, and send:
Run the screenshot script to render the card at 600×400 and save a PNG:
exec: node {baseDir}/scripts/screenshot_card.js "<cardPath>"
Capture
pngPath from output.
Upload to Cloudflare R2 and get a public URL:
exec: python {baseDir}/scripts/upload_card.py "<pngPath>"
Capture
url from output.
Update the sighting's card URL in the API (use the id from the log_sighting output):
CODEBLOCK13
Send the PNG via Telegram:
CODEBLOCK14
Step 7 — Reply
"🎉 New lifer! That's your first ever [commonName]! Bird #[totalSpecies] in your Birdfolio."
If totalSpecies == 1 (this is their very first bird ever): also send their personal PWA link:
*"🦅 Your Birdfolio is live! Bookmark this link to see your life list:
https://birdfolio.tonbistudio.com/app/[telegram_id]"*
The telegram_id is the sender's Telegram ID from the inbound message metadata (sender_id). This is also stored in birdfolio/config.json after init.
"[commonName] spotted! You've now seen [N] species in your Birdfolio."
Include: rarity badge emoji, the fun fact, checklist status (if species was on checklist, mention it).
Fallback if screenshot fails: Send a formatted text card:
🦅 [RARITY_EMOJI] [Common Name]
Scientific: [Scientific Name]
Region: [Region] | Spotted: [Date]
Rarity: [Rarity]
💡 [Fun Fact]
Bird #[N] in your Birdfolio
3. Checklist & Stats
Trigger: "How's my checklist?", "Birdfolio progress", "How many birds have I found?"
CODEBLOCK16
Format response using checklistProgress from output:
CODEBLOCK17
Use ✅ for found, ⬜ for not found. One box per species.
Optional visual checklist card: Generate a visual HTML checklist card and screenshot it:
exec: python {baseDir}/scripts/generate_checklist_card.py \
--workspace <absolute path to birdfolio/ in your workspace>
Then screenshot with
screenshot_card.js and send the PNG.
4. Life List View
Trigger: "Show my Birdfolio", "Show my life list"
Read birdfolio/lifeList.json from your workspace.
Group lifers by rarity (Super Rare first, then Rare, then Common). Format as a text list or generate an HTML gallery, save it to birdfolio/my-birdfolio.html in your workspace, and screenshot it.
5. Species Lookup (no logging)
Trigger: "Tell me about [species]"
Search You.com:
CODEBLOCK19
Return a conversational summary. Do not log a sighting or generate a card.
6. Rarest Bird
Trigger: "What's my rarest bird?", "Show my best find"
CODEBLOCK20
Read rarestBird from output and reply with species name, rarity, date spotted, and region.
Quick Reference
| Script | Key args | Returns |
|---|
| INLINECODE64 | INLINECODE65 , --region, --api-url, INLINECODE68 | INLINECODE69 |
| INLINECODE70 |
--species,
--scientific-name,
--rarity,
--region,
--date,
--workspace |
{status, sighting, isLifer, totalSightings, totalSpecies} |
|
update_checklist.py |
--species,
--region,
--workspace |
{status, tier, dateFound} or
{status: not_on_checklist} |
|
get_stats.py |
--workspace |
{totalSightings, totalSpecies, checklistProgress, mostRecentSighting, rarestBird} |
|
generate_card.py |
--species,
--scientific-name,
--rarity,
--region,
--date,
--fun-fact,
--image-path (preferred) OR
--image-url,
--object-position,
--life-count,
--workspace |
{status, cardPath, filename} |
|
generate_checklist_card.py |
--workspace |
{status, cardPath} — visual HTML checklist card |
|
screenshot_card.js |
<cardPath> [outputPath] |
{status, pngPath} — saves PNG to
birdfolio/cards/ |
|
upload_card.py |
<pngPath> [--secrets path] |
{status, url} — uploads to R2, returns public URL |
All Python scripts output JSON to stdout. Always pass absolute --workspace path.
screenshot_card.js uses OpenClaw's bundled playwright-core + system Chrome/Edge (no separate install needed).
Birdfolio
Birdfolio 将鸟类照片转化为个人生命清单。用户在野外拍摄鸟类照片,将照片发送给您,您通过 Vision 识别物种。You.com 提供实时稀有度和区域数据。每次目击记录都会存入生命清单,并附带宝可梦风格的稀有度等级(常见/稀有/超稀有),通过 Telegram 返回一张视觉化的交易卡。
数据存储位置: Railway PostgreSQL(通过 API)+ 本地 birdfolio/ 文件夹(卡片、鸟类、配置)
脚本存储位置: {baseDir}/scripts/
API: https://api-production-d0e2.up.railway.app(初始化后也会保存到 birdfolio/config.json)
模式参考: {baseDir}/references/data-schema.md
搜索查询: {baseDir}/references/you-search-queries.md
关于 --workspace 和 --api-url 的说明: 每个数据脚本都接受 --workspace(birdfolio/ 的绝对路径)和 --api-url(API 基础 URL)。运行 init_birdfolio.py 后,API URL 和 Telegram ID 都会保存到 birdfolio/config.json 并自动读取——后续脚本只需 --workspace。
Telegram ID: 从入站消息元数据(senderid)中读取。首次设置时作为 --telegram-id 传递给 initbirdfolio.py。
1. 设置流程
触发条件: 用户说设置我的 Birdfolio、设置我的区域,或在设置完成前发送照片。
先检查: 如果工作区中存在 birdfolio/config.json,说明设置已完成——跳转到相关流程。
步骤:
- 1. 询问:您的家乡区域是哪里?(例如:加利福尼亚、德克萨斯、英国)
- 2. 运行以下命令创建工作区文件夹结构并在 API 中注册用户:
exec: python {baseDir}/scripts/init_birdfolio.py \
--telegram-id {senderTelegramId} \
--region {region} \
--api-url https://api-production-d0e2.up.railway.app \
--workspace <工作区中 birdfolio/ 的绝对路径>
- 3. 搜索 You.com(运行全部三个):
{region} most common backyard birds eBird species list
{region} uncommon seasonal rare birds eBird checklist
{region} rare vagrant endangered birds eBird
- 4. 从结果中构建一个包含 10 种常见、5 种稀有、1 种超稀有 物种的清单。使用 {baseDir}/references/you-search-queries.md 中的分类信号。
- 5. 将填充好的清单写入工作区中的 birdfolio/checklist.json:
json
{
{region}: {
common: [
{ species: American Robin, slug: american-robin, found: false, dateFound: null }
],
rare: [...],
superRare: [...]
}
}
- 6. 回复欢迎消息和清单预览:
🦅 Birdfolio 已为 {region} 设置完成!
您的清单:
常见(10 种):American Robin、House Sparrow、...
稀有(5 种):Great Blue Heron、...
超稀有:California Condor
发送一张鸟类照片开始收集吧!
2. 鸟类识别流程
触发条件: 用户发送照片。
获取照片文件路径: 当用户通过 Telegram 发送照片时,OpenClaw 会下载该照片,并将本地文件路径提供在消息附件元数据中。捕获此路径——您将在步骤 5 中用于生成卡片。如果 OpenClaw 内联提供图像但没有路径,请使用 exec 在 OpenClaw 的临时/媒体文件夹中找到最近下载的文件,或在 Windows 上检查 %APPDATA%\openclaw\media\。将照片保存到 birdfolio/birds/{slug}-{timestamp}.jpg 以永久存储:
exec: copy birdfolio/birds/-.jpg
步骤 1 — 使用 Vision 识别
提交的照片直接在您的上下文中可见。分析它(如果不是内联的,则使用 image 工具):
识别此照片中的鸟类物种。仅返回 JSON:
{
commonName: ...,
scientificName: ...,
confidence: high|medium|low,
features: [可见特征 1, 可见特征 2]
}
稀有度规则:
- - 鸟类在清单上 → 使用其等级:common、rare 或 superRare
- 鸟类不在清单上 → 使用 bonus(显示中性的额外发现徽章,不分配稀有度)
置信度规则:
- - high → 自动继续,无需确认
- medium → 询问:我认为这可能是 [物种]——基于 [特征]。您觉得对吗? → 等待确认后再继续
- low → 回复:这张照片不够清晰,我无法确定。您能发送一张更清晰的照片吗? → 停止,不记录任何内容
步骤 2 — 稀有度查询
搜索 You.com:
{commonName} {homeRegion} eBird frequency how common rare
使用以下信号进行分类:
| 等级 | 脚本值 | 信号 |
|---|
| 常见 🟢 | common | 丰富、广泛分布、全年留鸟、>50% 的清单 |
| 稀有 🟡 |
rare | 不常见、季节性、迁徙性、偶见、5–50% 的清单 |
| 超稀有 🔴 | superRare | 稀有、迷鸟、偶遇、濒危、<5% 的清单 |
不确定时 → 默认为 rare。在将 --rarity 传递给任何脚本时,始终使用脚本值(例如 superRare,而不是 Super Rare)。
步骤 3 — 获取趣味事实
搜索 You.com:
{commonName} bird interesting facts habitat behavior
提取一个简洁的事实(1–2 句话)。
步骤 4 — 记录目击
将目击记录保存到工作区中的 birdfolio/lifeList.json:
exec: python {baseDir}/scripts/log_sighting.py \
--species {commonName} \
--scientific-name {scientificName} \
--rarity {rarity} \
--region {homeRegion} \
--notes \
--workspace <工作区中 birdfolio/ 的绝对路径>
从输出中捕获:isLifer、totalSightings、totalSpecies。
步骤 5 — 更新清单
在 birdfolio/checklist.json 中将该物种标记为已发现:
exec: python {baseDir}/scripts/update_checklist.py \
--species {commonName} \
--region {homeRegion} \
--workspace <工作区中 birdfolio/ 的绝对路径>
步骤 6 — 生成交易卡
卡片采用两栏设计:用户的照片填充左面板(280px),右侧为纯色深色信息面板。始终使用用户实际提交的照片——而不是库存图片。
步骤 6a — 使用 Vision 检测鸟类位置:
在提交的照片上使用 image 工具:
这张照片中鸟类的水平位置在哪里?请告诉我鸟类中心距离左边缘的大致百分比(0–100)。
将答案转换为 CSS 值:40% center、60% center、center center 等。将其用作 --object-position。
步骤 6b — 使用嵌入的照片生成卡片 HTML:
exec: python {baseDir}/scripts/generate_card.py \
--species {commonName} \
--scientific-name {scientificName} \
--rarity {rarity} \
--region {homeRegion} \
--date {YYYY-MM-DD} \
--fun-fact {funFact} \
--image-path <提交照片的绝对路径> \
--object-position {objectPosition} \
--life-count {totalSpecies} \
--workspace <工作区中 birdfolio/ 的绝对路径>
--image-path 将用户的实际照片以 base64 格式直接嵌入到 HTML 中。无需单独的嵌入步骤。
如果照片路径不可用的备用方案: 省略 --image-path,改为传递 --image-url <库存照片 URL>(通过 You.com 查找 URL:{commonName} bird photo wildlife)。
从输出中捕获 cardPath。
步骤 6c