# Project export: Bye! Buy!

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: TreeHacks 2026
- Tagline: Selling stuff online sucks... 50+ messages, lowball offers, sketchy buyers. Bye-Buy handles this mess: our AI agents cross-list on platforms, negotiate for best prices, and guarantee instant payment.
- Devpost: https://devpost.com/software/bye-buy
- GitHub: https://github.com/jpsingaraju/bye-buy
- Demo: https://bye-buy.s3.us-east-1.amazonaws.com/pitch-deck.html
- Video: https://www.youtube.com/embed/Dv-Bqklp-RE?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner ([Google] Cloud AI Track; [Browserbase] Best Web Automation with Stagehand ($1,000))
- Team: 2 GitHub contributor(s) — Jathin Pranav Singaraju (12 commits), Vikram Bhamre (9 commits)

## Devpost submission (written by the team)

### Inspiration

We've all been there: you list your old AirPods on Facebook Marketplace, and within minutes you're drowning in "is this available?" messages, getting offers for half your asking price, and coordinating meetups with people who ghost you. The peer-to-peer resale market is worth $200 billion globally, yet selling is still a painful, manual process that wastes hours of your time and often results in accepting lowball offers just to be done with it. We realized AI agents could fundamentally change this. Instead of you doing all the work (listing, messaging, negotiating, vetting buyers), what if a swarm of AI agents handled everything? That's Bye! Buy! -- your personal selling team that does the work while you just show up and get paid.

### What it does

Bye! Buy! is an AI agent marketplace that sells your stuff for you. Here's how it works: You list once: Upload a photo and basic details of what you're selling (AirPods, couch, bike, whatever) You list once: Upload a photo and basic details of what you're selling (AirPods, couch, bike, whatever) Agents take over: Listing agents automatically cross-post to Facebook Marketplace, Craigslist, OfferUp with optimized descriptions and competitive pricing Negotiation agents handle all buyer messages, filter out scammers and lowballers, and negotiate upward to get you the best price Verification agents screen buyers based on their messaging patterns and response quality Coordinator agents schedule meetups at safe locations and times that work for you Agents take over: Listing agents automatically cross-post to Facebook Marketplace, Craigslist, OfferUp with optimized descriptions and competitive pricing Negotiation agents handle all buyer messages, filter out scammers and lowballers, and negotiate upward to get you the best price Verification agents screen buyers based on their messaging patterns and response quality Coordinator agents schedule meetups at safe locations and times that work for you Secure payment: When a buyer is ready, payment goes through Stripe escrow. Guaranteed funds before you hand over the item. Secure payment: When a buyer is ready, payment goes through Stripe escrow. Guaranteed funds before you hand over the item. You show up once: Meet the verified buyer, hand over the item, and get paid instantly. The agents handled everything else. You show up once: Meet the verified buyer, hand over the item, and get paid instantly. The agents handled everything else. Result: What used to take 2+ weeks and 50+ message exchanges now happens automatically while you do nothing.

### How we built it

System Architecture At a high level, Bye! Buy! is two cooperating FastAPI microservices — a Posting Service and a Messaging Service — sharing a single SQLite database, with a Next.js frontend on top. Cloud browser sessions via Browserbase + Stagehand give our agents real browser access to Facebook Marketplace, and GPT models power the negotiation intelligence. Tech Stack Agent Architecture We built a multi-agent system where specialized agents collaborate through a shared database and event-driven state machine. Each agent is optimized for a single responsibility: Listing Agent — Uses a hybrid automation strategy. Browserbase launches a cloud Chromium session with persistent Facebook cookies (stored in a Browserbase Context so login survives across sessions). Rather than using Stagehand's natural-language act() for form filling (which can be unreliable on complex forms), we connect directly via Playwright CDP for precise DOM manipulation: press_sequentially() with 20-40ms delays to mimic human typing, direct input[type="file"] for image upload, and programmatic selector clicks. The one exception is category selection — Facebook's multi-level category dropdown is too dynamic to hard-code, so we extract all visible options with custom JavaScript, send them to GPT-4o-mini, and let the AI pick the best match. Negotiation Agent — The core intelligence layer. Every time a new message is detected in a buyer conversation, the agent constructs a rich system prompt containing: Listing details (title, description, price, condition) A dynamically computed "visible lowest price" that hides the true floor from the AI to prevent accidental leaks Negotiation rules calibrated by the seller's willing_to_negotiate float (0.0 = firm, 1.0 = very flexible) State-based addenda (competing offers from other buyers, address collection prompts, confirmation requests) Full conversation history with a [NEW MESSAGES] marker so the AI knows exactly what to respond to The AI returns structured JSON: {message, deal_status, agreed_price, delivery_address, buyer_offer}, which drives the deal state machine forward. Payment Agent — When a deal is confirmed, creates a Stripe Checkout Session and sends the payment URL directly to the buyer in the Facebook chat. Listens for Stripe webhooks (checkout.session.completed) with a polling fallback. After payment, the listing is marked as SOLD, a thank-you message is sent, and a background worker monitors for the seller to upload a tracking number. After delivery confirmation, the seller receives an instant payout via stripe.Transfer.create(). If no tracking number is uploaded within 7 days, the buyer is auto-refunded. Negotiation Intelligence Deep Dive The negotiation system is the heart of Bye! Buy!. Here's how a message flows through the AI pipeline: The negotiation rules are dynamically generated based on a single willing_to_negotiate float (0.0 to 1.0): Critically, the AI never sees the actual min_price. Instead, we compute a "visible lowest" price: price - (price - min_price) * flexibility. This prevents the AI from accidentally revealing the true floor to the buyer — it genuinely believes its bottom line is higher than the seller's actual minimum. When multiple buyers are negotiating for the same item simultaneously, the agent leverages competing offers: if Buyer B offers $55 and Buyer A offered $50, the system appends a competing-offer addendum to Buyer A's next prompt, naturally pressuring them upward. Deal Lifecycle State Machine When a deal reaches agreed, all competing conversations for the same listing are automatically closed — the negotiation agent sends a polite "item is no longer available" message to other buyers. Browser Automation: Two Strategies A key architectural decision was using two different browser automation strategies for two different problems: Both strategies share the same bot detection mitigation: Human-like typing delays (20-40ms per character) Random pauses between actions (0.3-0.8s) Session breaks every 75 polling cycles (60-120 second cooldown) Browserbase's built-in CAPTCHA solving

### Challenges we ran into

Browser automation across platforms - Each marketplace has different layouts, anti-bot systems, and messaging interfaces. Making agents handle layout changes and rate limits without breaking was tough. Negotiation strategy - Finding the balance between pushing for higher prices vs. not scaring off real buyers. We built a dynamic pricing model using the seller's willing_to_negotiate parameter and a hidden floor price the AI can't accidentally reveal. Agent coordination - The negotiation agent needs to know when another buyer has a better offer to apply pressure. The payment agent needs to know the exact moment a deal closes to trigger Stripe checkout. Getting agents to communicate through shared state took multiple tries. Real conversations are messy - People send random emojis, ask weird questions, or go off topic. We taught the negotiation agent to stay on track while sounding natural. The system prompt literally says "talk like a real person texting."

### Accomplishments we're proud of

Multi-agent negotiation that works - Tested with real scenarios and the agents successfully pushed prices higher than initial offers in most cases. Cross-platform browser automation - Agents can post listings and respond to messages on Facebook Marketplace and Craigslist without any human help. Full payment flow - Stripe escrow to instant payout, including auto-refunds if seller doesn't ship within 7 days. Agents actually collaborating - Watching the listing, negotiation, payment, and payout agents work together feels like having a real team. Tackling a broken $200B market - P2P resale is massive but the selling experience sucks. We're fixing it.

### What we learned

Specialization beats generalization - One agent trying to do everything failed. Split into specialized agents (listing, negotiation, payment) and it worked. Same as human teams. Browser automation unlocks agent marketplaces - Most platforms have no APIs. Browserbase (cloud browsers) + Stagehand (AI navigation) + Playwright (DOM control) lets agents work anywhere. Negotiation is the worst part - User research showed dealing with lowball offers and endless messages is the top reason people don't sell their stuff. It's a UX problem, not a supply problem - The $200B resale market has buyers and items. What's broken is the interface. Messaging, negotiating, and coordinating payments creates so much friction that people give up or accept bad prices. This works beyond selling - Started with resale but the same agent architecture works for gigs, rentals, services. Any high-friction peer-to-peer market can be fixed with agents as intermediaries.

### What's next

Short term (next 3 months): Launch beta with 100 users selling electronics and furniture. Integrate with more marketplace platforms (OfferUp, Mercari, eBay). Add photo enhancement AI (auto-background removal, better lighting). Build reputation system for tracking successful sales. Medium term (6-12 months): Expand to local services marketplace. Need a plumber? Agents find them, vet them, negotiate rates, schedule appointments, and handle payment. Launch "reverse marketplace" where you post what you want to buy and seller agents compete to offer you the best deal. Long term vision: The Agent Economy Bye! Buy! isn't just a selling app. It's infrastructure for agent-mediated commerce. Today, marketplaces connect buyers and sellers, but humans still do all the work (messaging, negotiating, coordinating). In the future, AI agents will handle all marketplace transactions: Your selling agent negotiates with their buying agent automatically Specialized gig agents bid on tasks (plumber agents, designer agents, tutor agents) Service agents coordinate complex purchases (moving agent books truck + helpers + supplies in one go) The market opportunity: P2P resale: $200B (our entry point) Gig economy: $455B globally Local services: $600B+ Total addressable: over $1 trillion in agent-mediated transactions We're starting with selling your old stuff because it's the simplest wedge. But we're building the rails for every agent-driven transaction.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 102 recognized source files, 425 KB.
- CSS (language) — detected in the code
- FastAPI (technology) — detected in the code
- HTML (language) — detected in the code
- Next.js (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (113 of 113)

```
.DS_Store
backend/.gitignore
backend/.python-version
backend/auth_login.py
backend/auth_session.py
backend/database/__init__.py
backend/database/connection.py
backend/database/models/__init__.py
backend/database/models/listing.py
backend/database/schema.sql
backend/database/seed.py
backend/messaging/__init__.py
backend/messaging/ai/__init__.py
backend/messaging/ai/client.py
backend/messaging/ai/context.py
backend/messaging/ai/prompts.py
backend/messaging/ai/responder.py
backend/messaging/api/__init__.py
backend/messaging/api/conversations.py
backend/messaging/api/payments.py
backend/messaging/api/polling.py
backend/messaging/api/router.py
backend/messaging/api/stats.py
backend/messaging/browser/__init__.py
backend/messaging/browser/actions.py
backend/messaging/browser/auth.py
backend/messaging/browser/client.py
backend/messaging/browser/extractor.py
backend/messaging/browser/monitor.py
backend/messaging/config.py
backend/messaging/main.py
backend/messaging/models/__init__.py
backend/messaging/models/browser_session.py
backend/messaging/models/buyer.py
backend/messaging/models/conversation.py
backend/messaging/models/message.py
backend/messaging/models/response_config.py
backend/messaging/models/transaction.py
backend/messaging/schemas/__init__.py
backend/messaging/schemas/conversation.py
backend/messaging/schemas/message.py
backend/messaging/schemas/payment.py
backend/messaging/schemas/polling.py
backend/messaging/services/__init__.py
backend/messaging/services/buyer_service.py
backend/messaging/services/conversation_service.py
backend/messaging/services/matching_service.py
backend/messaging/services/payment_service.py
backend/messaging/services/payment_worker.py
backend/posting/__init__.py
backend/posting/api/__init__.py
backend/posting/api/jobs.py
backend/posting/api/listings.py
backend/posting/api/router.py
backend/posting/config.py
backend/posting/main.py
backend/posting/models/__init__.py
backend/posting/models/image.py
backend/posting/models/job.py
backend/posting/platforms/__init__.py
backend/posting/platforms/_helpers.py
backend/posting/platforms/base.py
backend/posting/platforms/craigslist.py
backend/posting/platforms/ebay.py
backend/posting/platforms/facebook_marketplace.py
backend/posting/platforms/mercari.py
backend/posting/platforms/registry.py
backend/posting/queue/__init__.py
backend/posting/queue/job_processor.py
backend/posting/queue/worker.py
backend/posting/schemas/__init__.py
backend/posting/schemas/job.py
backend/posting/schemas/listing.py
backend/posting/storage/__init__.py
backend/posting/storage/images.py
backend/posting/uploads/.gitkeep
backend/pyproject.toml
backend/setup_facebook_login.py
backend/uv.lock
DEVPOST.md
frontend/.gitignore
frontend/eslint.config.mjs
frontend/next.config.ts
frontend/package.json
frontend/postcss.config.mjs
frontend/README.md
frontend/src/app/dashboard/page.tsx
frontend/src/app/globals.css
frontend/src/app/home/page.tsx
frontend/src/app/layout.tsx
frontend/src/app/listings/[id]/page.tsx
frontend/src/app/listings/new/page.tsx
frontend/src/app/page.tsx
frontend/src/app/payment/cancel/page.tsx
frontend/src/app/payment/success/page.tsx
frontend/src/app/transactions/page.tsx
frontend/src/components/layout/Header.tsx
frontend/src/components/layout/LayoutShell.tsx
frontend/src/components/listings/JobLogs.tsx
frontend/src/components/listings/ListingCard.tsx
frontend/src/components/listings/ListingForm.tsx
frontend/src/components/listings/ListingGrid.tsx
frontend/src/components/ui/AnimatedCounter.tsx
frontend/src/components/ui/Button.tsx
frontend/src/components/ui/Card.tsx
frontend/src/components/ui/Input.tsx
frontend/src/components/ui/Marquee.tsx
frontend/src/components/ui/StatusBadge.tsx
frontend/src/lib/api.ts
frontend/src/lib/types.ts
frontend/tsconfig.json
IMPLEMENTATION.md
pitch-deck.html
```

### Dependencies

- backend/pyproject.toml: aiofiles@>=23.0, aiosqlite@>=0.20.0, browserbase@>=0.3.0, fastapi@>=0.129.0, greenlet@>=3.0, openai@>=1.60.0, pillow@>=10.0, playwright@>=1.58.0, pydantic-settings@>=2.7.0, python-dotenv@>=1.0.0, python-multipart@>=0.0.6, sqlalchemy[asyncio]@>=2.0.0, stagehand@>=3.5.0, stripe@>=11.0.0, uvicorn@>=0.40.0
- frontend/package.json: @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@16.1.6, framer-motion@^12.34.0, next@16.1.6, react@19.2.3, react-dom@19.2.3, react-dropzone@^14.2.0, recharts@^3.7.0, swr@^2.2.0, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- remaining commits
- sd
- added DEVPOST.md
- updated server concurrence
- .
- db seed
- Update frontend pages
- changed website main file
- fixed rendering on 2nd batch
- added ui
- confident 2 buyer-system works
- frontend updates + payment + transactions
- stripe buyer side completely works
- integrated stripe code
- stripe
- working ui
- messages work rly well with edge cases
- working version after facebook error
- updated message handling
- .

## Key source files (fetched from GitHub, selected and truncated for size)

### IMPLEMENTATION.md

```markdown
# Bye-Buy Implementation Status

## Overview
Multi-platform listing automation service that allows users to upload product listings, automatically post them to Facebook Marketplace, and auto-respond to buyer messages on Messenger using AI.

## Tech Stack
- **Frontend**: Next.js 16, React 19, Tailwind CSS, SWR, react-dropzone
- **Backend**: Two FastAPI services (Python 3.13+), SQLAlchemy, aiosqlite
- **Browser Automation**: Browserbase Stagehand (posting + message monitoring)
- **AI**: Google Gemini 2.0 Flash (free tier) for Stagehand automation
- **Database**: SQLite (shared, WAL mode)
- **Image Storage**: Local filesystem

---

## Architecture

Two independent FastAPI services sharing one SQLite database (`bye_buy.db` at backend root):

| Service | Port | Purpose |
|---|---|---|
| **Posting** | 8000 | Listing CRUD, image uploads, Facebook Marketplace posting via Stagehand |
| **Messaging** | 8001 | Monitors Messenger conversations, auto-responds to buyers via GPT |

Both services read from a single `.env` file at the backend root.

---

## Implementation Status

### Phase 1: Database & Models ✅ COMPLETE
- [x] Shared `database/` package with async SQLAlchemy setup
- [x] SQLAlchemy models:
  - `Listing` (shared) - core listing data
  - `ListingImage` (posting) - images with position ordering
  - `PostingJob` (posting) - platform posting jobs with status tracking
  - `Buyer` (messaging) - buyer profiles
  - `Conversation` (messaging) - conversation threads linked to listings
  - `Message` (messaging) - individual messages with role/delivery tracking
  - `BrowserSession` (messaging) - Stagehand session tracking
  - `ResponseConfig` (messaging) - AI response configuration
- [x] Pydantic schemas for API request/response validation (both services)

### Phase 2: Posting API Endpoints ✅ COMPLETE
- [x] `POST /api/listings` - Create listing with images (multipart form)
- [x] `GET /api/listings` - List all listings with images
- [x] `GET /api/listings/{id}` - Get single listing
- [x] `PUT /api/listings/{id}` - Update listing
- [x] `DELETE /api/listings/{id}` - Delete listing and images
- [x] `POST /api/listings/{id}/post` - Create posting job
- [x] `GET /api/jobs` - List jobs with filtering
- [x] `GET /api/jobs/{id}` - Get job with logs
- [x] `POST /api/jobs/{id}/retry` - Retry failed job
- [x] Image serving via `/uploads/` static mount
- [x] CORS configured for frontend

### Phase 3: Frontend UI ✅ COMPLETE
- [x] Layout with Header navigation
- [x] Dashboard page with listings grid
- [x] Status badges (pending=yellow, posting=blue, posted=green, failed=red)
- [x] Create listing form with drag-drop image upload
- [x] Listing detail page with:
  - Image gallery
  - Description display
  - Platform selector dropdown
  - Post button
  - Posting history with status
  - Retry button for failed jobs
  - Expandable job logs

### Phase 4: Stagehand Automation ✅ COMPLETE
- [x] `PlatformPoster` abstract base class
- [x] `PlatformRegistry` for platform registration
- [
[truncated — 11079 more characters]
```

### DEVPOST.md

```markdown
# Bye! Buy!

## Inspiration

We've all been there: you list your old AirPods on Facebook Marketplace, and within minutes you're drowning in "is this available?" messages, getting offers for half your asking price, and coordinating meetups with people who ghost you. The peer-to-peer resale market is worth $200 billion globally, yet selling is still a painful, manual process that wastes hours of your time and often results in accepting lowball offers just to be done with it.

We realized AI agents could fundamentally change this. Instead of you doing all the work (listing, messaging, negotiating, vetting buyers), what if a swarm of AI agents handled everything? That's Bye! Buy!: your personal selling team that does the work while you just show up and get paid.

## What it does

Bye! Buy! is an AI agent marketplace that sells your stuff for you. Here's how it works:

1. **You list once**: Upload a photo and basic details of what you're selling (AirPods, couch, bike, whatever)

2. **Agents take over**:
   - Listing agents automatically cross-post to Facebook Marketplace, Craigslist, OfferUp with optimized descriptions and competitive pricing
   - Negotiation agents handle all buyer messages, filter out scammers and lowballers, and negotiate upward to get you the best price
   - Verification agents screen buyers based on their messaging patterns and response quality
   - Coordinator agents schedule meetups at safe locations and times that work for you

3. **Secure payment**: When a buyer is ready, payment goes through Stripe escrow. Guaranteed funds before you hand over the item.

4. **You show up once**: Meet the verified buyer, hand over the item, and get paid instantly. The agents handled everything else.

Result: What used to take 2+ weeks and 50+ message exchanges now happens automatically while you do nothing.

## How we built it

### System Architecture

At a high level, Bye! Buy! is two cooperating FastAPI microservices — a **Posting Service** and a **Messaging Service** — sharing a single SQLite database, with a Next.js frontend on top. Cloud browser sessions via Browserbase + Stagehand give our agents real browser access to Facebook Marketplace, and GPT models power the negotiation intelligence.

```
┌─────────────────────────────────────────────────────────────────────┐
│                    FRONTEND (Next.js 16 + React 19)                 │
│   ┌──────────────┐  ┌──────────────────┐  ┌─────────────────────┐  │
│   │ Dashboard UI  │  │  Listing Form    │  │ Transactions &      │  │
│   │ (SWR polling) │  │  (react-dropzone)│  │ Analytics (Recharts)│  │
│   └──────┬───────┘  └────────┬─────────┘  └──────────┬──────────┘  │
└──────────┼───────────────────┼───────────────────────┼──────────────┘
           │ SWR fetch         │ POST /listings         │ SWR fetch
           ▼                   ▼                        ▼
┌─────────────────────────┐          ┌──────────────────────────────┐
│  POSTING SERVICE (:8000)│          │  MESSAGING SERVICE
[truncated — 32454 more characters]
```

### backend/pyproject.toml

```
[project]
name = "backend"
version = "0.1.0"
description = "Bye-Buy multi-platform listing automation service"
requires-python = ">=3.13"
dependencies = [
    "fastapi>=0.129.0",
    "uvicorn>=0.40.0",
    "stagehand>=3.5.0",
    "openai>=1.60.0",
    "sqlalchemy[asyncio]>=2.0.0",
    "aiosqlite>=0.20.0",
    "pydantic-settings>=2.7.0",
    "python-dotenv>=1.0.0",
    "python-multipart>=0.0.6",
    "aiofiles>=23.0",
    "pillow>=10.0",
    "browserbase>=0.3.0",
    "greenlet>=3.0",
    "playwright>=1.58.0",
    "stripe>=11.0.0",
]

```

### frontend/package.json

```
{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "framer-motion": "^12.34.0",
    "next": "16.1.6",
    "react": "19.2.3",
    "react-dom": "19.2.3",
    "react-dropzone": "^14.2.0",
    "recharts": "^3.7.0",
    "swr": "^2.2.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.1.6",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### backend/messaging/main.py

```python
import logging
from contextlib import asynccontextmanager

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from database.connection import Base, engine
from database.seed import seed_default_listings, seed_default_conversations
from .api.router import router
from .browser.monitor import monitor
from .services.payment_worker import payment_worker

# Import models so they register with Base
from . import models  # noqa: F401
from posting import models as _posting_models  # noqa: F401

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: initialize database tables
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    await seed_default_listings()
    await seed_default_conversations()
    await monitor.start()
    await payment_worker.start()
    logger.info("Messaging service started")
    yield

    # Shutdown: stop worker and monitor, cleanup
    if payment_worker._running:
        await payment_worker.stop()
    if monitor.running:
        await monitor.stop()
    logger.info("Messaging service stopped")
    await engine.dispose()


app = FastAPI(
    title="Bye-Buy Messaging Service",
    description="Facebook Marketplace auto-responder",
    version="0.1.0",
    lifespan=lifespan,
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(router)


@app.get("/")
def read_root():
    return {"service": "messaging", "status": "ok"}


@app.get("/health")
def health_check():
    return {"status": "healthy"}

```

### backend/posting/main.py

```python
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles

from .api.router import router
from database.connection import Base, engine
from database.seed import seed_default_listings
from .config import settings
from .queue.worker import worker

# Import platform posters to register them
from . import platforms  # noqa: F401

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: initialize database
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    # Seed default data
    await seed_default_listings()

    # Ensure upload directory exists
    settings.upload_dir.mkdir(parents=True, exist_ok=True)

    # Start background worker
    await worker.start()
    logger.info("Background worker started")

    yield

    # Shutdown: stop worker and cleanup
    await worker.stop()
    logger.info("Background worker stopped")
    await engine.dispose()


app = FastAPI(
    title="Bye-Buy Posting Service",
    description="Multi-platform listing automation service",
    version="0.1.0",
    lifespan=lifespan,
)

# CORS middleware for frontend
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Mount uploads directory for serving images
app.mount("/uploads", StaticFiles(directory=str(settings.upload_dir)), name="uploads")

# Include API router
app.include_router(router)


@app.get("/")
def read_root():
    return {"service": "posting", "status": "ok"}


@app.get("/health")
def health_check():
    return {"status": "healthy"}

```

### frontend/src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Space_Grotesk, Pacifico } from "next/font/google";
import { LayoutShell } from "@/components/layout/LayoutShell";
import "./globals.css";

const spaceGrotesk = Space_Grotesk({
  variable: "--font-space-grotesk",
  subsets: ["latin"],
  weight: ["400", "500", "600", "700"],
});

const pacifico = Pacifico({
  variable: "--font-wordmark",
  subsets: ["latin"],
  weight: "400",
});


export const metadata: Metadata = {
  title: "bye! buy! — sell anything, automatically",
  description:
    "Our AI agents post your item across every major platform, filter out scammers, negotiate the best price, and guarantee instant payment. You just deliver.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={`${spaceGrotesk.variable} ${pacifico.variable} font-sans antialiased bg-cream min-h-screen`}>
        <LayoutShell>{children}</LayoutShell>
      </body>
    </html>
  );
}

```

### frontend/src/app/dashboard/page.tsx

```typescript
import { redirect } from "next/navigation";

export default function DashboardRedirect() {
  redirect("/home");
}

```

### frontend/src/app/page.tsx

```typescript
"use client";

import { useRef, useState, useEffect, useCallback } from "react";
import Image from "next/image";
import Link from "next/link";
import {
  motion,
  AnimatePresence,
  useMotionValue,
  useSpring,
} from "framer-motion";

/* ── Magnetic Button ───────────────────────────────── */
function MagneticButton({
  children,
  href,
}: {
  children: React.ReactNode;
  href: string;
}) {
  const ref = useRef<HTMLAnchorElement>(null);
  const x = useMotionValue(0);
  const y = useMotionValue(0);
  const springX = useSpring(x, { stiffness: 200, damping: 20 });
  const springY = useSpring(y, { stiffness: 200, damping: 20 });

  function handleMouse(e: React.MouseEvent) {
    const el = ref.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const cx = rect.left + rect.width / 2;
    const cy = rect.top + rect.height / 2;
    const dx = e.clientX - cx;
    const dy = e.clientY - cy;
    if (Math.sqrt(dx * dx + dy * dy) < 150) {
      x.set(dx * 0.15);
      y.set(dy * 0.15);
    }
  }

  return (
    <motion.div
      onMouseMove={handleMouse}
      onMouseLeave={() => {
        x.set(0);
        y.set(0);
      }}
      className="inline-block"
    >
      <motion.div style={{ x: springX, y: springY }}>
        <Link
          ref={ref}
          href={href}
          className="inline-flex items-center px-10 py-4 text-lg font-bold bg-primary text-white neo-border neo-shadow neo-hover"
        >
          {children}
        </Link>
      </motion.div>
    </motion.div>
  );
}

/* ── Cross-Post Orbit (Step 2) ─────────────────────── */
function CrossPostOrbit() {
  const platforms = [
    "Facebook",
    "eBay",
    "Craigslist",
    "Mercari",
    "OfferUp",
    "Poshmark",
    "Depop",
    "Nextdoor",
    "Swappa",
    "Whatnot",
  ];

  const R = 155;
  const CX = 210;
  const CY = 210;
  const VB = 420;

  return (
    <svg
      viewBox={`0 0 ${VB} ${VB}`}
      className="w-[336px] h-[336px] mx-auto overflow-visible"
    >
      {/* Orbit rings */}
      <circle
        cx={CX}
        cy={CY}
        r={R}
        fill="none"
        stroke="#FF5484"
        strokeWidth="1.5"
        strokeDasharray="6 6"
        opacity="0.25"
      >
        <animateTransform
          attributeName="transform"
          type="rotate"
          from={`0 ${CX} ${CY}`}
          to={`360 ${CX} ${CY}`}
          dur="30s"
          repeatCount="indefinite"
        />
      </circle>
      <circle
        cx={CX}
        cy={CY}
        r={R - 14}
        fill="none"
        stroke="#5B4CFF"
        strokeWidth="1"
        strokeDasharray="4 8"
        opacity="0.12"
      >
        <animateTransform
          attributeName="transform"
          type="rotate"
          from={`360 ${CX} ${CY}`}
          to={`0 ${CX} ${CY}`}
          dur="25s"
          repeatCount="indefinite"
        />
      </circle>

      {/* Center AI node */}
      <rect
        x={CX - 32}
        y={CY - 32}
        width="64"
        height="64"
        fill="#5B4CFF"
        stroke="#1A1A2E"
        strokeWidth="2.5"
      >
        <animate
          attributeName="opacity"
          values="1;0.8;1"
          dur="2s"
          repeatCount="indefinite"
        />
      </rect>
      <text
        x={CX}
        y={CY + 8}
        textAnchor="middle"
        fill="white"
        fontSize="22"
        fontWeight="800"
        fontFamily="var(--font-space-grotesk), sans-serif"
      >
        AI
      </text>

      {/* Platform nodes */}
      {platforms.map((name, i) => {
        const angle = (i / platforms.length) * Math.PI * 2 - Math.PI / 2;
        const px = CX + Math.cos(angle) * R;
        const py = CY + Math.sin(angle) * R;

        return (
          <g key={name}>
            <line
              x1={CX}
              y1={CY}
              x2={px}
              y2={py}
              stroke="#FF5484"
              strokeWidth="1.5"
              strokeDasharray="4 4"
              opacity="0.2"
            />

            <motion.g
              initial={{ opacity: 0, scale: 0 }}
              animate={{ opacity: 1, scale: 1 }}
              transition={{
                delay: 0.2 + i * 0.08,
                type: "spring",
                stiffness: 300,
                damping: 18,
              }}
            >
              <rect
                x={px - 38}
                y={py - 14}
                width="76"
                height="28"
                rx="0"
                fill="white"
                stroke="#1A1A2E"
                strokeWidth="2"
              />
              <text
                x={px}
                y={py + 5}
                textAnchor="middle"
                fill="#1A1A2E"
                fontSize="12"
                fontWeight="700"
                fontFamily="var(--font-space-grotesk), sans-serif"
              >
                {name}
              </text>
            </motion.g>

            <motion.g
              initial={{ opacity: 0, scale: 0 }}
              animate={{ opacity: 1, scale: 1 }}
              transition={{
                delay: 1.5 + i * 0.2,
                type: "spring",
                stiffness: 400,
                damping: 12,
              }}
            >
              <circle
                cx={px + 32}
                cy={py - 10}
                r="8"
                fill="#FF5484"
                stroke="#1A1A2E"
                strokeWidth="1.5"
              />
              <text
                x={px + 32}
                y={py - 6}
                textAnchor="middle"
                fill="white"
                fontSize="10"
                fontWeight="800"
              >
                ✓
              </text>
            </motion.g>
          </g>
        );
      })}
    </svg>
  );
}

/* ── Process Animation ─────────────────────────────── */
const STEP_DURATION = 4500;
const STEP_LABELS = ["Sell it", "Cross-post", "Negotiate", "Deal", "Get paid"];

function ProcessAnimation() {
  const [step, setS
[truncated — 14587 more characters]
```

### frontend/src/app/home/page.tsx

```typescript
"use client";

import { useState } from "react";
import Link from "next/link";
import useSWR from "swr";
import { api } from "@/lib/api";
import { ListingGrid } from "@/components/listings/ListingGrid";
import { ListingForm } from "@/components/listings/ListingForm";
import { Button } from "@/components/ui/Button";
import { Card } from "@/components/ui/Card";
import { StatusBadge } from "@/components/ui/StatusBadge";

/* ── helpers ──────────────────────────────────────── */
function centsToUsd(c: number) { return c / 100; }
function timeAgo(dateStr: string) {
  const diff = Date.now() - new Date(dateStr).getTime();
  const m = Math.floor(diff / 60000);
  if (m < 60) return `${m}m ago`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h}h ago`;
  return `${Math.floor(h / 24)}d ago`;
}

/* ── Dummy data for empty states ──────────────────── */
const DUMMY_TRANSACTIONS = [
  { id: 901, amount_cents: 42000, status: "paid_out" as const, created_at: "2025-01-15T10:00:00Z", listing_id: 1, buyer_id: 1, conversation_id: 1, tracking_number: "1Z999AA10123456784", paid_at: "2025-01-14T10:00:00Z", shipped_at: "2025-01-14T22:00:00Z", delivered_at: "2025-01-15T10:00:00Z", paid_out_at: "2025-01-15T22:00:00Z", refunded_at: null, stripe_checkout_session_id: null, stripe_payment_intent_id: null, stripe_transfer_id: null, checkout_url: null, updated_at: "2025-01-16T00:00:00Z" },
  { id: 902, amount_cents: 120000, status: "shipped" as const, created_at: "2025-01-16T14:00:00Z", listing_id: 2, buyer_id: 2, conversation_id: 2, tracking_number: "9400111899223100001", paid_at: "2025-01-15T22:00:00Z", shipped_at: "2025-01-16T02:00:00Z", delivered_at: null, paid_out_at: null, refunded_at: null, stripe_checkout_session_id: null, stripe_payment_intent_id: null, stripe_transfer_id: null, checkout_url: null, updated_at: "2025-01-17T00:00:00Z" },
  { id: 903, amount_cents: 8500, status: "payment_held" as const, created_at: "2025-01-17T08:00:00Z", listing_id: 3, buyer_id: 3, conversation_id: 3, tracking_number: null, paid_at: "2025-01-17T09:00:00Z", shipped_at: null, delivered_at: null, paid_out_at: null, refunded_at: null, stripe_checkout_session_id: null, stripe_payment_intent_id: null, stripe_transfer_id: null, checkout_url: null, updated_at: "2025-01-17T10:00:00Z" },
  { id: 904, amount_cents: 35000, status: "delivered" as const, created_at: "2025-01-12T09:00:00Z", listing_id: 4, buyer_id: 4, conversation_id: 4, tracking_number: "1Z999AA10123456789", paid_at: "2025-01-11T09:00:00Z", shipped_at: "2025-01-11T21:00:00Z", delivered_at: "2025-01-12T09:00:00Z", paid_out_at: null, refunded_at: null, stripe_checkout_session_id: null, stripe_payment_intent_id: null, stripe_transfer_id: null, checkout_url: null, updated_at: "2025-01-12T12:00:00Z" },
  { id: 905, amount_cents: 22500, status: "paid_out" as const, created_at: "2025-01-09T11:00:00Z", listing_id: 5, buyer_id: 5, conversation_id: 5, tracking_number: "1Z888BB20234567890", paid_at: "2025-01-08T11:00:00Z", shipped_at: "2025-01-08T23:00:00Z", delivered_at: "2025-01-09T11:00:00Z", paid_out_at: "2025-01-10T11:00:00Z", refunded_at: null, stripe_checkout_session_id: null, stripe_payment_intent_id: null, stripe_transfer_id: null, checkout_url: null, updated_at: "2025-01-10T12:00:00Z" },
  { id: 906, amount_cents: 67500, status: "paid_out" as const, created_at: "2025-01-05T15:00:00Z", listing_id: 6, buyer_id: 6, conversation_id: 6, tracking_number: "9400222899334200002", paid_at: "2025-01-04T15:00:00Z", shipped_at: "2025-01-05T03:00:00Z", delivered_at: "2025-01-05T15:00:00Z", paid_out_at: "2025-01-06T15:00:00Z", refunded_at: null, stripe_checkout_session_id: null, stripe_payment_intent_id: null, stripe_transfer_id: null, checkout_url: null, updated_at: "2025-01-06T16:00:00Z" },
  { id: 907, amount_cents: 15000, status: "pending" as const, created_at: "2025-01-17T12:00:00Z", listing_id: 7, buyer_id: 7, conversation_id: 7, tracking_number: null, paid_at: null, shipped_at: null, delivered_at: null, paid_out_at: null, refunded_at: null, stripe_checkout_session_id: null, stripe_payment_intent_id: null, stripe_transfer_id: null, checkout_url: "https://checkout.stripe.com/example", updated_at: "2025-01-17T12:00:00Z" },
];

export default function HomePage() {
  const [newListingOpen, setNewListingOpen] = useState(false);
  const { data: listings } = useSWR("/api/listings", () => api.listings.list().catch(() => []), { fallbackData: [], revalidateOnFocus: false, shouldRetryOnError: false, refreshInterval: 10000 });
  const { data: jobs } = useSWR("/api/jobs", () => api.jobs.list().catch(() => []), { fallbackData: [], revalidateOnFocus: false, shouldRetryOnError: false, refreshInterval: 10000 });
  const { data: transactions } = useSWR("/payments/transactions", () => api.payments.listTransactions().catch(() => []), { fallbackData: [], revalidateOnFocus: false, shouldRetryOnError: false, refreshInterval: 10000 });

  const txns = [...(transactions || []), ...DUMMY_TRANSACTIONS].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
  const allListings = listings || [];

  return (
    <div className="space-y-6">
      {/* ── Your Listings ─────────────────────────── */}
      <div>
        <div className="flex justify-between items-center mb-3">
          <h2 className="text-xl font-bold">Your Listings</h2>
          <button
            type="button"
            onClick={() => setNewListingOpen(true)}
            className="px-4 py-1.5 bg-primary text-white text-sm font-bold border-2 border-ink neo-shadow-sm neo-hover"
          >
            + New Listing
          </button>
        </div>
        <ListingGrid listings={allListings} jobs={jobs || []} />
      </div>

      {/* ── Recent Transactions ───────────────────── */}
      <div>
        <div className="flex justify-between items-center mb-3">
          <h2 className="text-xl font-bold">Recent Transactions</h2>
          <Link href="/transactions">
            <Button size="sm">View All</But
[truncated — 1396 more characters]
```

[87 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]