Agency Processor Skill
You process a single real estate agency: extract details and generate its demo page.
CRITICAL: Progress Updates
You MUST write progress updates to the absolute progressFilePath provided in the prompt.
The UI displays these updates in real-time. Users see your progress as it happens.
CRITICAL: Activity Streaming
You MUST append brief activity messages to the absolute activityFilePath provided in the prompt.
This powers the subagent streaming feed in the UI.
Format:
{
"sessionId": "...",
"agencyId": "...",
"agencyName": "...",
"messages": [
{
"id": "msg-{timestamp}",
"type": "thinking|fetch|identified|warning|tool|agent",
"text": "Short human-readable update",
"detail": "Optional detail",
"source": "Subagent",
"timestamp": "ISO timestamp"
}
]
}
Append a message at each milestone and before each tool call (WebFetch/WebSearch):
- Start processing agency
- Before each WebFetch/WebSearch (type="tool" with detail)
- After homepage fetch
- After extracting logo/colors/contact
- After metrics + pain score
- After HTML generation
- On any error
CRITICAL: Step Reporting
You MUST update the steps array in the progress file at each milestone.
Initial State (when file is first written)
{
"steps": [
{ "id": "website", "label": "Found website", "status": "pending" },
{ "id": "details", "label": "Extracted details", "status": "pending" },
{ "id": "generating", "label": "Generating demo page", "status": "pending" },
{ "id": "complete", "label": "Ready", "status": "pending" }
]
}
After fetching website successfully
Update: steps[0].status = "complete", steps[1].status = "in_progress"
After extracting all details
Update: steps[1].status = "complete", steps[2].status = "in_progress"
After HTML generation complete
Update: steps[2].status = "complete", steps[3].status = "complete"
On any error
Set the failed step to status = "error"
Input
You receive:
- agencyId: URL-safe slug
- sessionId: Pipeline session ID
- website: Agency website URL
- name: Agency name (from initial search)
- progressFilePath: Absolute path to agency progress JSON
- activityFilePath: Absolute path to agency activity JSON
- demoHtmlPath: Absolute path to /public/demo/{agencyId}.html
- agencyDataPath: Absolute path to /data/agencies/{agencyId}.json
Process
Phase 1: Extract Data
1.1 Fetch Homepage
Use WebFetch to get the agency homepage. Update progress:
- status = "extracting"
- steps[0].status = "in_progress"
After successful fetch:
- steps[0].status = "complete"
- steps[1].status = "in_progress"
1.2 Extract Logo
Look for logo in:
- tags with "logo" in src/alt/class
- og:image meta tag
- favicon as fallback
Update progress: logoUrl = "{url}"
1.3 Extract Colors
Analyze the page content for brand colors:
- Look for CSS color patterns in style attributes
- Common header/button colors
- If WebFetch returns HTML, parse inline styles
- Look for brand color in meta tags or CSS variables
Common Australian real estate brand colors:
- Ray White: #ffe512 (yellow)
- LJ Hooker: #e4002b (red)
- McGrath: #e31937 (red)
- Raine & Horne: #002b5c (navy blue)
- Century 21: #846b36 (gold)
- PRD: #c8102e (red)
- Belle Property: #1a1a1a (black)
Update progress: primaryColor = "#xxx", secondaryColor = "#xxx"
1.4 Extract Contact Info
Find:
- Phone number (Australian format: +61 or 02/03/07/08)
- Address
Update progress: phone = "...", address = "..."
1.5 Extract Metrics
Use WebFetch on team/about pages:
- Count team members
- Find listing counts
Use WebSearch if needed:
- "{agency name} listings"
- "{agency name} team"
Update progress: teamSize = N, listingCount = N
1.6 Enhanced Data Extraction (NEW)
Extract these additional fields from EXISTING page fetches (NO extra searches):
Sold Properties Count:
- Look for links/sections: "Sold", "Recently Sold", "Past Sales", "Sold Properties"
- WebFetch that page if found
- Count properties or extract displayed total
- Update progress: soldCount = N
Price Range: From listings already fetched:
- Collect all visible prices (for sale properties)
- Parse to numbers, find min and max
- Format as currency: "$600,000", "$2,100,000"
- Update progress: priceRangeMin, priceRangeMax
Rental/PM Count:
- Look for "Rentals", "For Rent", "Property Management", "Leasing"
- If section exists, count listings or extract total
- Update progress: forRentCount = N
IMPORTANT: If any data not found, set to null. Do NOT perform additional Google searches.
1.7 Calculate Pain Score
Apply scoring formula:
- 30+ listings: +20
- 20-29 listings: +15
- 10-19 listings: +10
- Has Property Management (forRentCount > 0): +25
- <5 agents + 20+ listings: +20
- <3 agents + 10+ listings: +15
- No after-hours number: +15
- No chat widget: +10
- No online booking: +5
After extraction complete:
- steps[1].status = "complete"
- steps[2].status = "in_progress"
- status = "generating"
Update progress: painScore = N
Phase 2: Generate Demo Page
2.1 Start Generation
Update progress: htmlProgress = 10
2.2 Build HTML
Generate complete HTML file with:
- Tailwind CSS via CDN
- Agency branding (logo, colors)
- Pain points section based on calculated pain reasons
- ROI calculator with real numbers
- Call demo button with phone number from env
- Embedded agency data for JS functionality
Use the demo-page-builder skill guidelines for design:
- Modern, clean, mobile-first design
- Smooth animations
- Clear CTA focus
- Agency brand colors throughout
Update progress: htmlProgress = 50
2.3 Write HTML File
Save to: demoHtmlPath (absolute path from prompt)
The HTML must include:
- Header with agency logo + Voqo co-branding
- Hero section with CTA
- Pain points cards (3 cards based on agency data)
- ROI calculator section
- How it works section
- Final CTA section
- Recent calls section (fetches from /api/agency-calls)
- JavaScript for call registration and polling
After HTML written:
- steps[2].status = "complete"
- steps[3].status = "complete"
- htmlProgress = 100
- status = "complete"
- demoUrl = "/demo/{agencyId}"
Phase 3: Save Agency Data
Write complete agency data to: agencyDataPath (absolute path from prompt) (This is separate from progress file - it's the permanent record)
Format:
{
"id": "agency-slug",
"name": "Agency Name",
"website": "https://...",
"phone": "+61...",
"address": "...",
"branding": {
"logoUrl": "...",
"primaryColor": "#...",
"secondaryColor": "#..."
},
"metrics": {
"teamSize": 5,
"listingCount": 30,
"soldCount": 12,
"forRentCount": 28,
"priceRangeMin": "$600,000",
"priceRangeMax": "$2,100,000",
"hasPropertyManagement": true,
"hasAfterHoursNumber": false,
"hasChatWidget": false,
"painScore": 75
},
"painReasons": [
"30 active listings generating high call volume",
"No after-hours contact solution"
],
"extractedAt": "ISO timestamp"
}
Progress File Format
Always write valid JSON to progressFilePath (absolute path from prompt):
{
"agencyId": "...",
"sessionId": "...",
"status": "skeleton|extracting|generating|complete|error",
"updatedAt": "ISO timestamp",
"name": "...",
"website": "...",
"phone": "..." or null,
"address": "..." or null,
"logoUrl": "..." or null,
"primaryColor": "..." or null,
"secondaryColor": "..." or null,
"teamSize": N or null,
"listingCount": N or null,
"painScore": N or null,
"soldCount": N or null,
"priceRangeMin": "..." or null,
"priceRangeMax": "..." or null,
"forRentCount": N or null,
"htmlProgress": 0-100,
"demoUrl": "..." or null,
"steps": [
{ "id": "website", "label": "Found website", "status": "pending|in_progress|complete|error" },
{ "id": "details", "label": "Extracted details", "status": "pending|in_progress|complete|error" },
{ "id": "generating", "label": "Generating demo page", "status": "pending|in_progress|complete|error" },
{ "id": "complete", "label": "Ready", "status": "pending|in_progress|complete|error" }
],
"error": "..." (only if status = "error")
}
Error Handling
If extraction fails:
- Set status = "error"
- Set the failed step's status = "error"
- Set error = "reason"
- The UI will remove this card
Do NOT retry - move on. The main agent handles retries if needed.
Tool Restrictions
- DO NOT use Chrome, Playwright, or any browser automation tools
- Use ONLY: WebSearch, WebFetch, Read, Write, Glob
Demo Phone Number
Use environment variable NEXT_PUBLIC_DEMO_PHONE for the call demo button. If not available, use the placeholder: +61 XXX XXX XXX