From f9f0c9d964175189fe8f00dbd1de5c53ed4db879 Mon Sep 17 00:00:00 2001 From: Chris Read Date: Sat, 15 Nov 2025 00:36:53 -0800 Subject: [PATCH 1/6] add VibeCheck --- typescript/vibe-check/.env.example | 9 + typescript/vibe-check/README.md | 65 +++++++ typescript/vibe-check/index.ts | 255 ++++++++++++++++++++++++++++ typescript/vibe-check/package.json | 16 ++ typescript/vibe-check/tsconfig.json | 13 ++ 5 files changed, 358 insertions(+) create mode 100644 typescript/vibe-check/.env.example create mode 100644 typescript/vibe-check/README.md create mode 100644 typescript/vibe-check/index.ts create mode 100644 typescript/vibe-check/package.json create mode 100644 typescript/vibe-check/tsconfig.json diff --git a/typescript/vibe-check/.env.example b/typescript/vibe-check/.env.example new file mode 100644 index 0000000..b1124e5 --- /dev/null +++ b/typescript/vibe-check/.env.example @@ -0,0 +1,9 @@ +# Set your API keys +BROWSERBASE_PROJECT_ID="YOUR_BROWSERBASE_PROJECT_ID" +BROWSERBASE_API_KEY="YOUR_BROWSERBASE_API_KEY" +GEMINI_API_KEY="YOUR_GOOGLE_API_KEY" + +# Or choose your preferred model provider - just update the stagehand constructor too +# OPENAI_API_KEY="YOUR_OPENAI_API_KEY" +# ANTHROPIC_API_KEY="YOUR_ANTHROPIC_API_KEY" +# See all supported model providers here: https://docs.stagehand.dev/v3/configuration/models diff --git a/typescript/vibe-check/README.md b/typescript/vibe-check/README.md new file mode 100644 index 0000000..c369c56 --- /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). + +You can build your own web agent using: `npx create-browser-app`! + +## 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 provider you're using. For example, to use a google model you'll need a GEMINI_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..b7e2148 --- /dev/null +++ b/typescript/vibe-check/index.ts @@ -0,0 +1,255 @@ +// 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: "google/gemini-2.5-flash", + // auto-loads GEMINI_API_KEY from environment + 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({ + model: "google/gemini-2.5-flash", + // auto-loads GEMINI_API_KEY from environment + }); + + 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); +}); diff --git a/typescript/vibe-check/package.json b/typescript/vibe-check/package.json new file mode 100644 index 0000000..97a24df --- /dev/null +++ b/typescript/vibe-check/package.json @@ -0,0 +1,16 @@ +{ + "name": "vibe-check", + "type": "module", + "scripts": { + "build": "tsc", + "start": "tsx index.ts" + }, + "dependencies": { + "@browserbasehq/stagehand": "^3.0.2", + "dotenv": "^16.4.7" + }, + "devDependencies": { + "tsx": "^4.19.2", + "typescript": "^5.0.0" + } +} diff --git a/typescript/vibe-check/tsconfig.json b/typescript/vibe-check/tsconfig.json new file mode 100644 index 0000000..e809afb --- /dev/null +++ b/typescript/vibe-check/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "outDir": "./dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["*.ts"], + "exclude": ["dist"] +} From 033fa954a206d6cde3a3af4102825377d54cf869 Mon Sep 17 00:00:00 2001 From: Jay Sahnan Date: Tue, 18 Nov 2025 15:55:14 -0800 Subject: [PATCH 2/6] removed unessary files --- typescript/vibe-check/.env.example | 9 --------- typescript/vibe-check/package.json | 16 ---------------- typescript/vibe-check/tsconfig.json | 13 ------------- 3 files changed, 38 deletions(-) delete mode 100644 typescript/vibe-check/.env.example delete mode 100644 typescript/vibe-check/package.json delete mode 100644 typescript/vibe-check/tsconfig.json diff --git a/typescript/vibe-check/.env.example b/typescript/vibe-check/.env.example deleted file mode 100644 index b1124e5..0000000 --- a/typescript/vibe-check/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -# Set your API keys -BROWSERBASE_PROJECT_ID="YOUR_BROWSERBASE_PROJECT_ID" -BROWSERBASE_API_KEY="YOUR_BROWSERBASE_API_KEY" -GEMINI_API_KEY="YOUR_GOOGLE_API_KEY" - -# Or choose your preferred model provider - just update the stagehand constructor too -# OPENAI_API_KEY="YOUR_OPENAI_API_KEY" -# ANTHROPIC_API_KEY="YOUR_ANTHROPIC_API_KEY" -# See all supported model providers here: https://docs.stagehand.dev/v3/configuration/models diff --git a/typescript/vibe-check/package.json b/typescript/vibe-check/package.json deleted file mode 100644 index 97a24df..0000000 --- a/typescript/vibe-check/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "vibe-check", - "type": "module", - "scripts": { - "build": "tsc", - "start": "tsx index.ts" - }, - "dependencies": { - "@browserbasehq/stagehand": "^3.0.2", - "dotenv": "^16.4.7" - }, - "devDependencies": { - "tsx": "^4.19.2", - "typescript": "^5.0.0" - } -} diff --git a/typescript/vibe-check/tsconfig.json b/typescript/vibe-check/tsconfig.json deleted file mode 100644 index e809afb..0000000 --- a/typescript/vibe-check/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "outDir": "./dist", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true - }, - "include": ["*.ts"], - "exclude": ["dist"] -} From 17fd2f6f123d249966a1ef14b906cf2988a13452 Mon Sep 17 00:00:00 2001 From: Chris Read Date: Sat, 22 Nov 2025 02:55:37 -0800 Subject: [PATCH 3/6] add python version --- python/vibe-check/README.md | 65 +++++++++ python/vibe-check/main.py | 278 ++++++++++++++++++++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 python/vibe-check/README.md create mode 100644 python/vibe-check/main.py diff --git a/python/vibe-check/README.md b/python/vibe-check/README.md new file mode 100644 index 0000000..9799db7 --- /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). + +You can build your own web agent using: `https://docs.stagehand.dev/v2/first-steps/installation`! + +## 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 provider you're using. For example, to use a google model you'll need a GEMINI_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..3987408 --- /dev/null +++ b/python/vibe-check/main.py @@ -0,0 +1,278 @@ +# 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 +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") + stagehand = Stagehand( + env="BROWSERBASE", + api_key=os.environ["BROWSERBASE_API_KEY"], + project_id=os.environ["BROWSERBASE_PROJECT_ID"], + model_name="google/gemini-2.5-flash", + model_api_key=os.environ["GEMINI_API_KEY"], + browserbase_session_create_params={ + "project_id": os.environ["BROWSERBASE_PROJECT_ID"], + "browser_settings": { + # Viewport optimized for AI models + "viewport": { + "width": 1288, + "height": 711, + }, + }, + }, + ) + + 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) From 3114f13c813ec2c29f8a8413eee28b7aef10c2e8 Mon Sep 17 00:00:00 2001 From: Chris Read Date: Sat, 22 Nov 2025 03:20:45 -0800 Subject: [PATCH 4/6] update readmes --- python/vibe-check/README.md | 2 +- typescript/vibe-check/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vibe-check/README.md b/python/vibe-check/README.md index 9799db7..f2e23ac 100644 --- a/python/vibe-check/README.md +++ b/python/vibe-check/README.md @@ -2,7 +2,7 @@ Hey! This is a project built with [Stagehand](https://github.com/browserbase/stagehand). -You can build your own web agent using: `https://docs.stagehand.dev/v2/first-steps/installation`! +Check out our other Stagehand templates [here!](https://www.browserbase.com/templates) ## What is VibeCheck? diff --git a/typescript/vibe-check/README.md b/typescript/vibe-check/README.md index c369c56..1a38d18 100644 --- a/typescript/vibe-check/README.md +++ b/typescript/vibe-check/README.md @@ -2,7 +2,7 @@ Hey! This is a project built with [Stagehand](https://github.com/browserbase/stagehand). -You can build your own web agent using: `npx create-browser-app`! +Check out our other Stagehand templates [here!](https://www.browserbase.com/templates) ## What is VibeCheck? From db99d23ada6ea02d50ea62641dfb9981f9f5bcc8 Mon Sep 17 00:00:00 2001 From: Chris Read Date: Wed, 26 Nov 2025 19:26:01 -0600 Subject: [PATCH 5/6] final updates --- python/vibe-check/main.py | 10 ++++++---- typescript/vibe-check/index.ts | 13 +++++++++---- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/python/vibe-check/main.py b/python/vibe-check/main.py index 3987408..19ba501 100644 --- a/python/vibe-check/main.py +++ b/python/vibe-check/main.py @@ -5,7 +5,7 @@ import json import asyncio from dotenv import load_dotenv -from stagehand import Stagehand +from stagehand import Stagehand, StagehandConfig from pydantic import BaseModel, Field # Load environment variables @@ -69,12 +69,13 @@ async def main() -> None: # ============= INITIALIZE STAGEHAND ============= print("\nπŸ…±οΈ Initializing Stagehand...\n") - stagehand = Stagehand( + config = StagehandConfig( env="BROWSERBASE", api_key=os.environ["BROWSERBASE_API_KEY"], project_id=os.environ["BROWSERBASE_PROJECT_ID"], - model_name="google/gemini-2.5-flash", - model_api_key=os.environ["GEMINI_API_KEY"], + 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": { @@ -87,6 +88,7 @@ async def main() -> None: }, ) + stagehand = Stagehand(config) await stagehand.init() print("\nπŸ…±οΈ Browser initialized...\n") diff --git a/typescript/vibe-check/index.ts b/typescript/vibe-check/index.ts index b7e2148..dbaf9a2 100644 --- a/typescript/vibe-check/index.ts +++ b/typescript/vibe-check/index.ts @@ -47,8 +47,9 @@ async function main(): Promise { console.log("\nπŸ…±οΈ Initializing Stagehand...\n"); const stagehand = new Stagehand({ env: "BROWSERBASE", - model: "google/gemini-2.5-flash", - // auto-loads GEMINI_API_KEY from environment + model: "openai/gpt-4.1", + // auto-loads OPENAI_API_KEY from environment + useAPI: false, browserbaseSessionCreateParams: { projectId: process.env.BROWSERBASE_PROJECT_ID!, browserSettings: { @@ -123,8 +124,12 @@ async function main(): Promise { console.log("\nπŸ…±οΈ Using agent to score venues...\n"); const agent = stagehand.agent({ - model: "google/gemini-2.5-flash", - // auto-loads GEMINI_API_KEY from environment + cua: true, + // @ts-ignore + model: { + modelName: "google/gemini-2.5-computer-use-preview-10-2025", + apiKey: process.env.GEMINI_API_KEY, + }, }); const scoringInstruction = ` From 75717957be7844f47fcf1fd3057815d10db2611c Mon Sep 17 00:00:00 2001 From: Chris Read Date: Wed, 26 Nov 2025 20:11:51 -0600 Subject: [PATCH 6/6] update readmes --- python/vibe-check/README.md | 2 +- typescript/vibe-check/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/vibe-check/README.md b/python/vibe-check/README.md index f2e23ac..5ba12cb 100644 --- a/python/vibe-check/README.md +++ b/python/vibe-check/README.md @@ -24,7 +24,7 @@ Your required API keys/environment variables are in the `.env.example` file. Cop 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 provider you're using. For example, to use a google model you'll need a GEMINI_API_KEY. +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 diff --git a/typescript/vibe-check/README.md b/typescript/vibe-check/README.md index 1a38d18..f852dc4 100644 --- a/typescript/vibe-check/README.md +++ b/typescript/vibe-check/README.md @@ -24,7 +24,7 @@ Your required API keys/environment variables are in the `.env.example` file. Cop 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 provider you're using. For example, to use a google model you'll need a GEMINI_API_KEY. +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