🍌 Banana Lab

Core Source Code — Multi-Agent Automation System

Claude API Python 3 BuilderAgent SiteAgent ReporterAgent

About This Code Showcase

This showcase highlights the core logic of Banana Lab's 3-agent automation pipeline — how Claude is called to research and write 50-prompt packs, how the site is rebuilt automatically, and how the weekly report is compiled from local state.

API credentials and Gmail passwords are stored in config.json (not shown). The state file (state.json) is the only database — no SQL server required.

Project File Structure

📁 Banana Lab/
Banana Lab/ ├── main.py ← Scheduler + CLI entry point ├── config.json ← API keys (not committed) ├── state.json ← Live data: products, sales (auto-managed) ├── requirements.txt ← anthropic, schedule, requests │ ├── agents/ │ ├── builder_agent.py ← Monday: research niche + generate 50 prompts │ ├── site_agent.py ← Monday: generate product page + rebuild homepage │ └── reporter_agent.py ← Sunday: email weekly P&L report │ ├── utils/ │ ├── state_manager.py ← CRUD wrapper for state.json │ └── email_sender.py ← Gmail SMTP delivery │ ├── products/ ← Generated each Monday (auto-populated) │ ├── {slug}.md ← Prompt pack (owner converts to PDF) │ └── {slug}-listing.txt ← Ready-to-paste Gumroad listing copy │ └── website/ ← Banana Lab showroom (auto-rebuilt) ├── index.html ← Homepage product grid ├── about.html ← About page ├── style.css ← Banana yellow + black theme └── products/ ← Individual product landing pages

Builder Agent — Niche Research & Prompt Generation

The core of Banana Lab: three Claude API calls per product cycle. First, pick a unique niche. Second, plan five categories. Third, generate 50 prompts in two batches to stay within token limits.

📄 agents/builder_agent.py — _research_idea()
def _research_idea(self, state): existing = [p["title"] for p in state.get("products", [])] prompt = f"""You are the product researcher for Banana Lab. Existing products (do not duplicate): {json.dumps(existing, indent=2) if existing else "None yet"} Pick ONE high-demand prompt pack idea for small business owners. Return ONLY valid JSON, no markdown fences: {{ "title": "e.g. 50 ChatGPT Prompts for Restaurant Owners", "slug": "url-friendly-slug-here", "description": "2-3 sentence listing description that sells the pack", "niche": "target niche label", "price": 9.00, "tags": ["tag1", "tag2", "tag3", "tag4", "tag5"], "what_inside": ["bullet 1", "bullet 2", "bullet 3", "bullet 4"] }}""" msg = self.client.messages.create( model="claude-sonnet-4-6", max_tokens=512, messages=[{"role": "user", "content": prompt}] ) raw = msg.content[0].text.strip() # Strip accidental markdown fences from Claude's response if raw.startswith("```"): raw = raw.split("```")[1] if raw.startswith("json"): raw = raw[4:] return json.loads(raw.strip())
📄 agents/builder_agent.py — _generate_prompts() (batched)
def _generate_prompts(self, idea): """Generate 50 prompts in two batches of 25 to avoid output token limits.""" # Call 2: determine the 5 categories for this niche cat_msg = self.client.messages.create( model="claude-sonnet-4-6", max_tokens=256, messages=[{"role": "user", "content": f"List exactly 5 category names for a ChatGPT prompt pack targeting " f"{idea['niche']} owners. Return ONLY a JSON array of 5 strings, no markdown." }] ) categories = json.loads(cat_msg.content[0].text.strip()) def _batch(cats, start_number): # Calls 3a and 3b: generate prompts for the given categories batch_cats = "\n".join(f"- {c} (10 prompts)" for c in cats) p = f"""Create ChatGPT prompts for: {idea['title']} Write 10 prompts for EACH of these categories: {batch_cats} Rules: niche-specific, use [PLACEHOLDERS IN CAPS], number from {start_number}. Return ONLY a valid JSON array: [{{"number": N, "category": "...", "prompt": "..."}}]""" msg = self.client.messages.create( model="claude-sonnet-4-6", max_tokens=8000, messages=[{"role": "user", "content": p}] ) raw = msg.content[0].text.strip() if raw.startswith("```"): raw = raw.split("```")[1].lstrip("json").strip().rstrip("```") return json.loads(raw.strip()) # Batch 1: categories 0–2 (30 prompts), Batch 2: categories 3–4 (20 prompts) all_prompts = _batch(categories[0:3], 1) all_prompts += _batch(categories[3:5], len(all_prompts) + 1) return all_prompts

Site Agent — Auto-Rebuild Homepage

After every Builder run, Site Agent reads all products from state.json and rewrites index.html from scratch — newest products first. Each registered product gets its own landing page too.

📄 agents/site_agent.py — _rebuild_homepage()
def _rebuild_homepage(self, products): cards = "" for p in reversed(products): # newest first cards += f""" <div class="product-card"> <div class="card-badge">AI Prompt Pack</div> <h3><a href="products/{p['slug']}.html">{p['title']}</a></h3> <p>{p['description']}</p> <div class="card-footer"> <span class="price">${p['price']}</span> <a href="{p['gumroad_url']}" class="btn-buy-small">Get It Now</a> </div> </div>""" html = f"""<!DOCTYPE html> ... <section class="products-grid"> <h2>All Packs ({len(products)} available)</h2> <div class="grid">{cards}</div> </section> ...""" path = os.path.join(self.website_dir, "index.html") with open(path, "w", encoding="utf-8") as f: f.write(html) print(f"[Site] Homepage rebuilt: {path}")

Reporter Agent — Weekly P&L Email

Every Sunday the Reporter reads state.json, calculates revenue and costs entirely from local data (no Gumroad API), and emails a plain-text weekly summary.

📄 agents/reporter_agent.py — _build_report()
def _build_report(self, products, week): live = [p for p in products if p.get("status") == "live"] pending = [p for p in products if p.get("status") == "pending_upload"] new_week = [p for p in products if self._days_ago(p.get("created_date","")) <= 7] revenue_gross = sum(p.get("sales",0) * p.get("price",0) for p in live) gumroad_fees = revenue_gross * 0.10 api_cost_myr = len(new_week) * 0.40 # ~RM 0.40 per product build net_myr = (revenue_gross - gumroad_fees) * 4.7 - api_cost_myr lines = [ f"Banana Lab — Week {week} Report", "=" * 45, f"Products live: {len(live)}", f"Pending upload: {len(pending)}", f"Total sales (all time): {sum(p.get('sales',0) for p in live)}", f"Gross revenue: ${revenue_gross:.2f}", f"Gumroad fee (10%): -${gumroad_fees:.2f}", f"Est. API cost: -RM {api_cost_myr:.2f}", f"Net profit: RM {net_myr:.2f}", ] # Flag underperformers: 3+ weeks live, 0 sales for u in live: if u.get("weeks_live",0) >= 3 and u.get("sales",0) == 0: lines.append(f"Low performer: \"{u['title']}\" — consider price cut or removal") return "\n".join(lines)

Main Scheduler — Weekly Automation

main.py wires the three agents to a weekly schedule using the Python schedule library, and exposes CLI flags for manual testing and product registration.

📄 main.py — Scheduler Setup
# Scheduled mode — runs indefinitely schedule.every().monday.at("09:00").do(run_monday_pipeline, config=config) schedule.every().sunday.at("18:00").do(run_reporter, config=config) while True: schedule.run_pending() time.sleep(30) # --register flag: attach a Gumroad URL after manual upload if "--register" in args: slug = args[idx + 1] url = args[idx + 2] if BuilderAgent(config).register(slug, url): run_site(config) # Rebuild site with new Gumroad link

Technical Implementation Notes

Key Design Decisions