diff --git a/python/vibe-check/README.md b/python/vibe-check/README.md new file mode 100644 index 0000000..5ba12cb --- /dev/null +++ b/python/vibe-check/README.md @@ -0,0 +1,65 @@ +# 🀘 Welcome to VibeCheck! + +Hey! This is a project built with [Stagehand](https://github.com/browserbase/stagehand). + +Check out our other Stagehand templates [here!](https://www.browserbase.com/templates) + +## What is VibeCheck? + +VibeCheck uses Stagehand to search Google Maps and score venues based on your vibe preferences. Tell it what atmosphere you're looking for, and it'll search your area. + +> πŸ’‘ This is just a quick vibe check for inspo - do your own research before heading out! + +## Setting the Stage + +Stagehand is an SDK for automating browsers. It provides a higher-level API for better debugging and AI fail-safes. + +## Curtain Call + +### Add your API keys + +Your required API keys/environment variables are in the `.env.example` file. Copy it to `.env` and add your keys. + +```bash +cp .env.example .env && nano .env # Add your API keys to .env +``` + +Quick note - in addition to your Browserbase keys, you'll only need the API key of the model providers you're using. For example, to use a google model and an open ai model you'll need a GEMINI_API_KEY and an OPENAI_API_KEY. + +### Run your vibe check + +Get ready for a show-stopping development experience. Just run: + +```bash +python main.py "fall sunset" "san francisco" +``` + +To do a quick vibe check of the area. + +> 🎯 Tip: Always check the latest info yourself! + +## What's Next? + +### Run on Local + +To run on a local browser, change `env: "BROWSERBASE"` to `env: "LOCAL"` in the stagehand constructor. + +## πŸŽ₯ Watch Your Session Live + +When running with Browserbase, you can watch your session live in the Browserbase session inspector. The live view URL will be printed to the console when your script starts. + +## πŸš€ Future Enhancements + +Ideas for extending VibeCheck: + +- **Photo Analysis**: Process and analyze venue images for better vibe matching +- **Deeper Reviews**: Pull and analyze more individual reviews for richer sentiment analysis +- **Data Export**: Export results to CSV/JSON files for processing with other systems +- **Web Display**: Add an API route to display results in a web app +- **Dashboard Data**: Build a dashboard to track and compare vibe searches over time +- **Social Sharing**: Use the Twitter/X API to auto-post your vibe discoveries with venue details and scores + +## πŸ“š Resources & Support + +- **Questions?** Reach out via support@browserbase.com +- **Documentation:** Check out our docs at [docs.stagehand.dev](https://docs.stagehand.dev) diff --git a/python/vibe-check/main.py b/python/vibe-check/main.py new file mode 100644 index 0000000..19ba501 --- /dev/null +++ b/python/vibe-check/main.py @@ -0,0 +1,280 @@ +# VibeCheck - Stagehand-Powered Vibe Search on Google Maps + +import sys +import os +import json +import asyncio +from dotenv import load_dotenv +from stagehand import Stagehand, StagehandConfig +from pydantic import BaseModel, Field + +# Load environment variables +load_dotenv() + +# ============= CLI PARSING ============= + +vibe = sys.argv[1] if len(sys.argv) > 1 else None +location = sys.argv[2] if len(sys.argv) > 2 else "San Francisco" +venue_type = sys.argv[3] if len(sys.argv) > 3 else None + +if not vibe: + print('Usage: python main.py "" "[location]" "[venue type]"') + print('Example: python main.py "sunset vibes" "San Francisco" "rooftop bar"') + print("\nLocation defaults to San Francisco if not provided.") + print("Venue type is fully optional (e.g., bar, restaurant, cafe, club, etc.)") + sys.exit(1) + +# ============= PYDANTIC MODELS ============= + + +class Venue(BaseModel): + name: str = Field(description="venue name") + address: str = Field(description="full address") + description: str = Field(description="venue description") + star_rating: str = Field(description="Google Maps star rating (e.g., '4.5')") + reviews: str = Field(description="Available individual reviews for the venue") + + +class VenuesData(BaseModel): + venues: list[Venue] = Field(description="list of venues from search results") + + +class VibeScore(BaseModel): + vibe_score: int + name: str + address: str + star_rating: str + review_summary: str + + +# ============= MAIN ============= + + +async def main() -> None: + # ============= ASCII ART ============= + + print("\n") + print("\x1b[33mβ–ˆβ–ˆβ•—β–‘β–‘β–‘β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—") + print("β–ˆβ–ˆβ•‘β–‘β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•") + print("β•šβ–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•¦β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–‘") + print("β–‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β–‘β–‘") + print("β–‘β–‘β•šβ–ˆβ–ˆβ•”β•β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•¦β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—") + print("β–‘β–‘β–‘β•šβ•β•β–‘β–‘β–‘β•šβ•β•β•šβ•β•β•β•β•β•β–‘β•šβ•β•β•β•β•β•β•\x1b[0m") + print("\n > Vibe........: " + vibe) + print(" > Location....: " + location) + if venue_type: + print(" > Venue Type..: " + venue_type) + print() + + # ============= INITIALIZE STAGEHAND ============= + + print("\nπŸ…±οΈ Initializing Stagehand...\n") + config = StagehandConfig( + env="BROWSERBASE", + api_key=os.environ["BROWSERBASE_API_KEY"], + project_id=os.environ["BROWSERBASE_PROJECT_ID"], + model_name="openai/gpt-4.1", + model_api_key=os.environ["OPENAI_API_KEY"], + disable_api=True, + browserbase_session_create_params={ + "project_id": os.environ["BROWSERBASE_PROJECT_ID"], + "browser_settings": { + # Viewport optimized for AI models + "viewport": { + "width": 1288, + "height": 711, + }, + }, + }, + ) + + stagehand = Stagehand(config) + await stagehand.init() + + print("\nπŸ…±οΈ Browser initialized...\n") + + # Get session URL for debugging + session_id = stagehand.session_id + if session_id: + live_view_url = f"https://www.browserbase.com/sessions/{session_id}" + print(f"\nπŸ…±οΈ Live View: {live_view_url}\n") + + # ============= STAGEHAND: OBSERVE - ACT - EXTRACT ============= + + # Get a page + page = stagehand.page + + # Navigate to Google Maps + print("\nπŸ…±οΈ Navigating to Google Maps...\n") + await page.goto("https://www.google.com/maps") + + # Build search query + search_query = ( + f"{vibe} {venue_type + 's' if venue_type else 'venues'} in {location}" + ) + print(f"\nπŸ…±οΈ Searching for: {search_query}\n") + + # Search for venues - be very explicit and break down actions atomically + await page.act("click on the search box") + await page.act(f'type "{search_query}" into the search box') + await page.act("press Enter or click the search button") + await page.observe("make sure we can see the map search results") + + print("\nπŸ…±οΈ Extracting venue data...\n") + + # Extract venues with vibe scoring (note: extraction uses a11y tree, text only - no images) + venues_data = await page.extract( + "Extract all the venues you can from the search results.", schema=VenuesData + ) + + print("\nπŸ…±οΈ Venue data extracted:\n") + print(json.dumps(venues_data.model_dump(), indent=2)) + + # ============= STAGEHAND AGENT ============= + + print("\nπŸ…±οΈ Using agent to score venues...\n") + agent = stagehand.agent( + provider="google", + model="gemini-2.5-computer-use-preview-10-2025", + options={ + "api_key": os.getenv("GEMINI_API_KEY"), + }, + ) + + venue_type_filter = ( + f"- Category/venue type (should be a {venue_type})" if venue_type else "" + ) + + scoring_instruction = f""" +For each venue, determine a vibe score of how well it matches the vibe "{vibe}" of either 1, 2, 3, 4, or 5. +Take into account: +- Venue name (does the name suggest this vibe?) +{venue_type_filter} +- Description text (does it mention relevant atmosphere or theme?) +- Review snippets (any keywords about ambiance/mood that fit the vibe?) +- Star rating (higher rated venues are better) +- Keywords (look for vibe-related words in any visible text) + +Be generous with scoring. Do not search again. Use the data provided. + +Data: +{json.dumps(venues_data.model_dump(), indent=2)} + +Output as a JSON array, where each item has: +- vibe_score (number) +- name (string) +- address (string) +- star_rating (string) +- review_summary (string) + +// Example: +[ + {{ + "vibe_score": 5, + "name": "Awesome Venue", + "address": "123 Sunset Blvd, San Francisco, CA", + "star_rating": "4.6", + "review_summary": "Beautiful views and great music." + }}, + ... +] +""" + + result = await agent.execute(instruction=scoring_instruction, max_steps=30) + + print("\nπŸ…±οΈ Closing browser...\n") + await stagehand.close() + + # ============= PROCESS OUTPUT ============= + + # Extract JSON from the agent's response + output = result.message.strip() + + # Find JSON between ```json and ``` markers + if "```json" in output: + start = output.find("```json") + 7 + end = output.find("```", start) + output = output[start:end].strip() + # Or just find the JSON array + elif "[" in output and "]" in output: + start = output.find("[") + end = output.rfind("]") + 1 + output = output[start:end] + + vibe_scores_data = json.loads(output) + + # Convert to VibeScore objects + vibe_scores = [VibeScore(**item) for item in vibe_scores_data] + + print("\nπŸ…±οΈ Vibe scores:\n") + print(json.dumps([v.model_dump() for v in vibe_scores], indent=2)) + + # Sort by vibe score and take top 3 + top_venues = sorted(vibe_scores, key=lambda x: x.vibe_score, reverse=True)[:3] + + # ============= DISPLAY RESULTS ============= + + if top_venues and len(top_venues) > 0: + print("\n") + print(" ╦ ╦╦╔╗ ╔═╗") + print(" β•šβ•—β•”β•β•‘β• β•©β•—β•‘β•£") + print(" β•šβ• β•©β•šβ•β•β•šβ•β•") + print(" ══════════════════════") + print(f" >> {len(venues_data.venues)} VIBES ANALYZED") + print(f" >> TOP 3 VIBES FOUND") + print(f" >> FOR VIBE: {vibe} in {location}") + print() + + rank_labels = ["β—†β—†β—† PRIME VIBE", "β—†β—† GOOD VIBE", "β—† VIBES"] + + for index, venue in enumerate(top_venues): + rank = index + 1 + + print(f"\n β”Œβ”€ [{rank}] {rank_labels[index]}") + print(f" β”‚") + print(f" β”‚ \x1b[1m\x1b[36m{venue.name}\x1b[0m") + print(f" β”‚ {venue.address}") + print(f" β”‚") + + # Visual bars + vibe_blocks = "β–“" * round(venue.vibe_score * 2) + "β–‘" * ( + 10 - round(venue.vibe_score * 2) + ) + rating_num = ( + float(venue.star_rating) + if venue.star_rating and venue.star_rating.strip() not in ["", "null"] + else 0 + ) + rating_blocks = "β–“" * round(rating_num * 2) + "β–‘" * ( + 10 - round(rating_num * 2) + ) + display_rating = "n/a" if rating_num == 0 else rating_num + + print(f" β”‚ VIBEΒ·Β·Β·Β· [{vibe_blocks}] {venue.vibe_score}") + print(f" β”‚ RATINGΒ·Β· [{rating_blocks}] {display_rating}") + print(f" β”‚") + print(f' β”‚ "{venue.review_summary}"') + print(f" └─") + + print("\n β€» Quick vibe check only - please vibe responsibly β€»\n") + print(" β–ˆ THANKS FOR VIBING! β–ˆ") + else: + print("\n") + print(" ╦ ╦╦╔╗ ╔═╗") + print(" β•šβ•—β•”β•β•‘β• β•©β•—β•‘β•£") + print(" β•šβ• β•©β•šβ•β•β•šβ•β•") + print(" ══════════════════════") + print("\n ╔══════════════════════") + print(" β•‘ NO VIBES FOUND β•‘") + print(" β•‘ TRY DIFFERENT VIBE β•‘") + print(" β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n") + + +# ============= ERROR HANDLING ============= + +if __name__ == "__main__": + try: + asyncio.run(main()) + except Exception as err: + print(f"\nπŸ…±οΈ Error: {err}") + sys.exit(1) diff --git a/typescript/vibe-check/README.md b/typescript/vibe-check/README.md new file mode 100644 index 0000000..f852dc4 --- /dev/null +++ b/typescript/vibe-check/README.md @@ -0,0 +1,65 @@ +# 🀘 Welcome to VibeCheck! + +Hey! This is a project built with [Stagehand](https://github.com/browserbase/stagehand). + +Check out our other Stagehand templates [here!](https://www.browserbase.com/templates) + +## What is VibeCheck? + +VibeCheck uses Stagehand to search Google Maps and score venues based on your vibe preferences. Tell it what atmosphere you're looking for, and it'll search your area. + +> πŸ’‘ This is just a quick vibe check for inspo - do your own research before heading out! + +## Setting the Stage + +Stagehand is an SDK for automating browsers. It provides a higher-level API for better debugging and AI fail-safes. + +## Curtain Call + +### Add your API keys + +Your required API keys/environment variables are in the `.env.example` file. Copy it to `.env` and add your keys. + +```bash +cp .env.example .env && nano .env # Add your API keys to .env +``` + +Quick note - in addition to your Browserbase keys, you'll only need the API key of the model providers you're using. For example, to use a google model and an open ai model you'll need a GEMINI_API_KEY and an OPENAI_API_KEY. + +### Run your vibe check + +Get ready for a show-stopping development experience. Just run: + +```bash +npm start "fall sunset" "san francisco" +``` + +To do a quick vibe check of the area. + +> 🎯 Tip: Always check the latest info yourself! + +## What's Next? + +### Run on Local + +To run on a local browser, change `env: "BROWSERBASE"` to `env: "LOCAL"` in the stagehand constructor. + +## πŸŽ₯ Watch Your Session Live + +When running with Browserbase, you can watch your session live in the Browserbase session inspector. The live view URL will be printed to the console when your script starts. + +## πŸš€ Future Enhancements + +Ideas for extending VibeCheck: + +- **Photo Analysis**: Incorporate Vercel AI SDK to process and analyze venue images for better vibe matching +- **Deeper Reviews**: Pull and analyze more individual reviews for richer sentiment analysis +- **Data Export**: Export results to CSV/JSON files for processing with other systems +- **Web Display**: Add a Next.js API route to display results in a web app +- **Dashboard Data**: Build a dashboard to track and compare vibe searches over time +- **Social Sharing**: Use the Twitter/X API to auto-post your vibe discoveries with venue details and scores + +## πŸ“š Resources & Support + +- **Questions?** Reach out via support@browserbase.com +- **Documentation:** Check out our docs at [docs.stagehand.dev](https://docs.stagehand.dev) diff --git a/typescript/vibe-check/index.ts b/typescript/vibe-check/index.ts new file mode 100644 index 0000000..dbaf9a2 --- /dev/null +++ b/typescript/vibe-check/index.ts @@ -0,0 +1,260 @@ +// VibeCheck - Stagehand-Powered Vibe Search on Google Maps + +import { Stagehand } from "@browserbasehq/stagehand"; +import { z } from "zod"; +import "dotenv/config"; + +// ============= CLI PARSING ============= + +const vibe = process.argv[2]; +const location = process.argv[3] || "San Francisco"; +const venueType = process.argv[4]; // optional venue type filter + +if (!vibe) { + console.log('Usage: npm start "" "[location]" "[venue type]"'); + console.log( + 'Example: npm start "sunset vibes" "San Francisco" "rooftop bar"' + ); + console.log("\nLocation defaults to San Francisco if not provided."); + console.log( + "Venue type is fully optional (e.g., bar, restaurant, cafe, club, etc.)" + ); + process.exit(1); +} + +// ============= MAIN ============= + +async function main(): Promise { + // ============= ASCII ART ============= + + console.log("\n"); + console.log("\x1b[33mβ–ˆβ–ˆβ•—β–‘β–‘β–‘β–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—"); + console.log("β–ˆβ–ˆβ•‘β–‘β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•"); + console.log("β•šβ–ˆβ–ˆβ•—β–‘β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•¦β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–‘β–‘"); + console.log("β–‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β–‘β–‘"); + console.log("β–‘β–‘β•šβ–ˆβ–ˆβ•”β•β–‘β–‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•¦β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—"); + console.log("β–‘β–‘β–‘β•šβ•β•β–‘β–‘β–‘β•šβ•β•β•šβ•β•β•β•β•β•β–‘β•šβ•β•β•β•β•β•β•\x1b[0m"); + console.log("\n > Vibe........: " + vibe); + console.log(" > Location....: " + location); + if (venueType) { + console.log(" > Venue Type..: " + venueType); + } + console.log(); + + // ============= INITIALIZE STAGEHAND ============= + + // Initialize Stagehand + console.log("\nπŸ…±οΈ Initializing Stagehand...\n"); + const stagehand = new Stagehand({ + env: "BROWSERBASE", + model: "openai/gpt-4.1", + // auto-loads OPENAI_API_KEY from environment + useAPI: false, + browserbaseSessionCreateParams: { + projectId: process.env.BROWSERBASE_PROJECT_ID!, + browserSettings: { + // Viewport optimized for AI models + viewport: { + width: 1288, + height: 711, + }, + }, + }, + }); + + await stagehand.init(); + console.log("\nπŸ…±οΈ Browser initialized...\n"); + + // Get session URL for debugging + const sessionId = stagehand.browserbaseSessionId; + if (sessionId) { + const liveViewUrl = `https://www.browserbase.com/sessions/${sessionId}`; + console.log(`\nπŸ…±οΈ Live View: ${liveViewUrl}\n`); + } + + // ============= STAGEHAND: OBSERVE - ACT - EXTRACT ============= + + // Get a page + const page = stagehand.context.pages()[0]; + + // Navigate to Google Maps + console.log("\nπŸ…±οΈ Navigating to Google Maps...\n"); + await page.goto("https://www.google.com/maps"); + + // Build search query + const searchQuery = `${vibe} ${ + venueType ? venueType + "s" : "venues" + } in ${location}`; + console.log(`\nπŸ…±οΈ Searching for: ${searchQuery}\n`); + + // Search for venues - be very explicit and break down actions atomically + await stagehand.act(`click on the search box`); + await stagehand.act(`type "${searchQuery}" into the search box`); + await stagehand.act(`press Enter or click the search button`); + await stagehand.observe(`make sure we can see the map search results`); + + console.log("\nπŸ…±οΈ Extracting venue data...\n"); + + // Extract venues with vibe scoring (note: extraction uses a11y tree, text only - no images) + const venuesData = await stagehand.extract( + "Extract all the venues you can from the search results.", + z.object({ + venues: z + .array( + z.object({ + name: z.string().describe("venue name"), + address: z.string().describe("full address"), + description: z.string().describe("venue description"), + starRating: z + .string() + .describe("Google Maps star rating (e.g., '4.5')"), + reviews: z + .string() + .describe("Available individual reviews for the venue"), + }) + ) + .describe("list of venues from search results"), + }) + ); + + console.log("\nπŸ…±οΈ Venue data extracted:\n"); + console.log(JSON.stringify(venuesData, null, 2)); + + // ============= STAGEHAND AGENT ============= + + console.log("\nπŸ…±οΈ Using agent to score venues...\n"); + const agent = stagehand.agent({ + cua: true, + // @ts-ignore + model: { + modelName: "google/gemini-2.5-computer-use-preview-10-2025", + apiKey: process.env.GEMINI_API_KEY, + }, + }); + + const scoringInstruction = ` +For each venue, determine a vibe score of how well it matches the vibe "${vibe}" of either 1, 2, 3, 4, or 5. +Take into account: +- Venue name (does the name suggest this vibe?) +${venueType ? `- Category/venue type (should be a ${venueType})` : ""} +- Description text (does it mention relevant atmosphere or theme?) +- Review snippets (any keywords about ambiance/mood that fit the vibe?) +- Star rating (higher rated venues are better) +- Keywords (look for vibe-related words in any visible text) + +Be generous with scoring. Do not search again. Use the data provided. + +Data: +${JSON.stringify(venuesData, null, 2)} + +Output as a JSON array, where each item has: +- vibeScore (number) +- name (string) +- address (string) +- starRating (string) +- reviewSummary (string) + +// Example: +[ + { + "vibeScore": 5, + "name": "Awesome Venue", + "address": "123 Sunset Blvd, San Francisco, CA", + "starRating": "4.6", + "reviewSummary": "Beautiful views and great music." + }, + ... +] +`; + + const result = await agent.execute(scoringInstruction); + + console.log("\nπŸ…±οΈ Closing browser...\n"); + await stagehand.close(); + + // ============= PROCESS OUTPUT ============= + + // Strip the JSON formatting + const output = result.message + .trim() + .replace(/^```json\n?/, "") + .replace(/\n?```$/, ""); + + type VibeScore = { + vibeScore: number; + name: string; + address: string; + starRating: string; + reviewSummary: string; + }; + + const vibeScores = JSON.parse(output) as VibeScore[]; + + console.log("\nπŸ…±οΈ Vibe scores:\n"); + console.log(JSON.stringify(vibeScores, null, 2)); + + // Sort by vibe score and take top 3 + const topVenues = vibeScores + .sort((a, b) => b.vibeScore - a.vibeScore) + .slice(0, 3); + + // ============= DISPLAY RESULTS ============= + + if (topVenues && topVenues.length > 0) { + console.log("\n"); + console.log(" ╦ ╦╦╔╗ ╔═╗"); + console.log(" β•šβ•—β•”β•β•‘β• β•©β•—β•‘β•£"); + console.log(" β•šβ• β•©β•šβ•β•β•šβ•β•"); + console.log(" ══════════════════════"); + console.log(` >> ${venuesData.venues.length} VIBES ANALYZED`); + console.log(` >> TOP 3 VIBES FOUND`); + console.log(` >> FOR VIBE: ${vibe} in ${location}`); + console.log(); + + topVenues.forEach((venue, index) => { + const rank = index + 1; + const rankLabels = ["β—†β—†β—† PRIME VIBE", "β—†β—† GOOD VIBE", "β—† VIBES"]; + + console.log(`\n β”Œβ”€ [${rank}] ${rankLabels[index]}`); + console.log(` β”‚`); + console.log(` β”‚ \x1b[1m\x1b[36m${venue.name}\x1b[0m`); + console.log(` β”‚ ${venue.address}`); + console.log(` β”‚`); + + // Visual bars + const vibeBlocks = + "β–“".repeat(Math.round(venue.vibeScore * 2)) + + "β–‘".repeat(10 - Math.round(venue.vibeScore * 2)); + const ratingNum = parseFloat(venue.starRating) || 0; + const ratingBlocks = + "β–“".repeat(Math.round(ratingNum * 2)) + + "β–‘".repeat(10 - Math.round(ratingNum * 2)); + + console.log(` β”‚ VIBEΒ·Β·Β·Β· [${vibeBlocks}] ${venue.vibeScore}`); + console.log(` β”‚ RATINGΒ·Β· [${ratingBlocks}] ${venue.starRating}`); + console.log(` β”‚`); + console.log(` β”‚ "${venue.reviewSummary}"`); + console.log(` └─`); + }); + + console.log("\n β€» Quick vibe check only - please vibe responsibly β€»\n"); + console.log(" β–ˆ THANKS FOR VIBING! β–ˆ"); + } else { + console.log("\n"); + console.log(" ╦ ╦╦╔╗ ╔═╗"); + console.log(" β•šβ•—β•”β•β•‘β• β•©β•—β•‘β•£"); + console.log(" β•šβ• β•©β•šβ•β•β•šβ•β•"); + console.log(" ══════════════════════"); + console.log("\n ╔══════════════════════"); + console.log(" β•‘ NO VIBES FOUND β•‘"); + console.log(" β•‘ TRY DIFFERENT VIBE β•‘"); + console.log(" β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n"); + } +} + +// ============= ERROR HANDLING ============= + +main().catch((err) => { + console.error("\nπŸ…±οΈ Error:", err); + process.exit(1); +});