SkillJavaScriptv1.0.0

birdfolio

Bird identification, life list tracking, and trading card generation.

0 downloads
tonbistudio
Updated Feb 19, 2026

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:

    {
      "{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:

TierScript valueSignals
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

ScriptKey argsReturns
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).

Free
Installation
Reviews

Sign in to leave a review.

No reviews yet. Be the first.