Skill: tonbistudio/birdfolio (version 1.0.0)

--- name: birdfolio description: > Bird identification, life list tracking, and trading card generation. Use this skill when the user: sends a bird photo to identify, says "set up my Birdfolio" or "set my region", asks "how's my checklist", asks "Birdfolio progress", asks "how many birds have I found", asks "show my Birdfolio" or "show my life list", asks "what's my rarest bird", or asks "tell me about [bird species]". Handles everything from first-time setup through ongoing life list tracking and visual trading card generation. --- # 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:** `{baseDir}/references/you-search-queries.md` > **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: ``` exec: python {baseDir}/scripts/init_birdfolio.py \ --telegram-id {senderTelegramId} \ --region "{region}" \ --api-url "https://api-production-d0e2.up.railway.app" \ --workspace <absolute path to birdfolio/ in your workspace> ``` 3. Search You.com (run all three): ``` "{region} most common backyard birds eBird species list" "{region} uncommon seasonal rare birds eBird checklist" "{region} rare vagrant endangered birds eBird" ``` 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: ```json { "{region}": { "common": [ { "species": "American Robin", "slug": "american-robin", "found": false, "dateFound": null } ], "rare": [...], "superRare": [...] } } ``` 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: > ``` > exec: copy "<attachment path>" "birdfolio/birds/<slug>-<timestamp>.jpg" > ``` ### 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): ``` Identify the bird species in this photo. Return JSON only: { "commonName": "...", "scientificName": "...", "confidence": "high|medium|low", "features": ["visible feature 1", "visible feature 2"] } ``` **Rarity rules:** - Bird IS on the checklist → use its tier: `common`, `rare`, or `superRare` - 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 - `"medium"` → ask: *"I think this might be a [species] — based on [features]. Does that look right to you?"* → wait for confirmation before continuing - `"low"` → 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: ``` "{commonName} {homeRegion} eBird frequency how common rare" ``` Classify using these signals: | Tier | Script value | Signals | |------|-------------|---------| | Common 🟢 | `common` | "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: ``` "{commonName} bird interesting facts habitat behavior" ``` Extract one punchy fact (1–2 sentences). ### Step 4 — Log the sighting Save the sighting to `birdfolio/lifeList.json` in your workspace: ``` exec: python {baseDir}/scripts/log_sighting.py \ --species "{commonName}" \ --scientific-name "{scientificName}" \ --rarity "{rarity}" \ --region "{homeRegion}" \ --notes "" \ --workspace <absolute path to birdfolio/ in your workspace> ``` Capture from output: `isLifer`, `totalSightings`, `totalSpecies`. ### Step 5 — Update checklist Mark the species as found in `birdfolio/checklist.json`: ``` exec: python {baseDir}/scripts/update_checklist.py \ --species "{commonName}" \ --region "{homeRegion}" \ --workspace <absolute path to birdfolio/ in your workspace> ``` ### 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:** ``` 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 "<absolute path to submitted photo>" \ --object-position "{objectPosition}" \ --life-count {totalSpecies} \ --workspace <absolute path to birdfolio/ in your workspace> ``` `--image-path` 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): ``` PATCH /users/{telegram_id}/sightings/{sighting_id}/card Body: {"card_png_url": "<url>"} ``` Send the PNG via Telegram: ``` message(action="send", media="<pngPath>") ``` ### Step 7 — Reply - If `isLifer` is true: *"🎉 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. - Otherwise: *"[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?" ``` exec: python {baseDir}/scripts/get_stats.py \ --workspace <absolute path to birdfolio/ in your workspace> ``` Format response using `checklistProgress` from output: ``` 📋 {region} Checklist Common ✅✅✅⬜⬜⬜⬜⬜⬜⬜ 3/10 Rare ✅⬜⬜⬜⬜ 1/5 Super Rare ⬜ 0/1 🐦 {totalSpecies} species | {totalSightings} total sightings 📍 Last spotted: {mostRecentSighting.commonName} on {date} 🏆 Rarest find: {rarestBird.commonName} ({rarity}) ``` 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: ``` "{species} bird facts habitat range behavior diet" "{species} bird {homeRegion} eBird frequency resident or migratory" ``` 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" ``` exec: python {baseDir}/scripts/get_stats.py \ --workspace <absolute path to birdfolio/ in your workspace> ``` Read `rarestBird` from output and reply with species name, rarity, date spotted, and region. --- ## Quick Reference | Script | Key args | Returns | |--------|----------|---------| | `init_birdfolio.py` | `--telegram-id`, `--region`, `--api-url`, `--workspace` | `{status, workspace, files_created, next}` | | `log_sighting.py` | `--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).


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (CRITICAL)