diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4db8dec6..4c28f42f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,4 +5,6 @@ /scripts/validate-playground-templates.mjs @browserbase/dashboard /scripts/playground-ci.mjs @browserbase/dashboard /scripts/lib/playground-checks.mjs @browserbase/dashboard +/.github/workflows/playground.yml @browserbase/dashboard /.github/workflows/playground-production.yml @browserbase/dashboard +/.github/workflows/playground-test-production.yml @browserbase/dashboard diff --git a/.github/workflows/playground-production.yml b/.github/workflows/playground-production.yml index f8cd6d51..48666ec6 100644 --- a/.github/workflows/playground-production.yml +++ b/.github/workflows/playground-production.yml @@ -1,9 +1,5 @@ -# Validates TypeScript → JavaScript builds for templates exposed by the public -# templates API (playgroundRunnable). Intended for the `production` branch workflow -# described in https://github.com/browserbase/templates (branch protection: require -# this check + Dashboard team review before merge). -# -# Set TEMPLATES_API_URL to override the default public endpoint (e.g. staging). +# Triggers the shared playground workflow for the `production` branch. +# All CI steps live in playground.yml — edit there to change behavior. name: Playground templates (production) @@ -21,40 +17,6 @@ permissions: jobs: playground-templates: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Clean javascript output directory - run: rm -rf javascript && mkdir -p javascript - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build and validate playground TypeScript templates - env: - TEMPLATES_API_URL: ${{ vars.TEMPLATES_API_URL }} - run: pnpm run ci:playground - - - name: Commit and push playground javascript (push to production only) - if: github.event_name == 'push' - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A javascript/ - if git diff --staged --quiet; then - echo "No javascript changes to commit." - else - git commit -m "chore: regenerate playground javascript templates" - git push - fi + uses: ./.github/workflows/playground.yml + with: + branch: production diff --git a/.github/workflows/playground-test-production.yml b/.github/workflows/playground-test-production.yml index e23bf2e2..39b76f45 100644 --- a/.github/workflows/playground-test-production.yml +++ b/.github/workflows/playground-test-production.yml @@ -1,9 +1,5 @@ -# Validates TypeScript → JavaScript builds for templates exposed by the public -# templates API (playgroundRunnable). Intended for the `test-production` branch workflow -# described in https://github.com/browserbase/templates (branch protection: require -# this check + Dashboard team review before merge). -# -# Set TEMPLATES_API_URL to override the default public endpoint (e.g. staging). +# Triggers the shared playground workflow for the `test-production` branch. +# All CI steps live in playground.yml — edit there to change behavior. name: Playground templates (test production) @@ -21,40 +17,6 @@ permissions: jobs: playground-templates: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Clean javascript output directory - run: rm -rf javascript && mkdir -p javascript - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build and validate playground TypeScript templates - env: - TEMPLATES_API_URL: ${{ vars.TEMPLATES_API_URL }} - run: pnpm run ci:playground - - - name: Commit and push playground javascript (push to test-production only) - if: github.event_name == 'push' - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A javascript/ - if git diff --staged --quiet; then - echo "No javascript changes to commit." - else - git commit -m "chore: regenerate playground javascript templates" - git push - fi + uses: ./.github/workflows/playground.yml + with: + branch: test-production diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml new file mode 100644 index 00000000..5fb80900 --- /dev/null +++ b/.github/workflows/playground.yml @@ -0,0 +1,58 @@ +# Reusable workflow: validates TypeScript → JavaScript builds for playground- +# runnable templates and optionally commits the generated javascript/ tree. +# +# Called by playground-production.yml and playground-test-production.yml so that +# CI steps are defined in exactly one place. + +name: Playground templates + +on: + workflow_call: + inputs: + branch: + description: Target branch name (used only in log messages) + required: true + type: string + +permissions: + contents: write + +jobs: + playground-templates: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Clean javascript output directory + run: rm -rf javascript && mkdir -p javascript + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build and validate playground TypeScript templates + env: + TEMPLATES_API_URL: ${{ vars.TEMPLATES_API_URL }} + run: pnpm run ci:playground + + - name: Commit and push playground javascript (push only) + if: github.event_name == 'push' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A javascript/ + if git diff --staged --quiet; then + echo "No javascript changes to commit." + else + git commit -m "chore: regenerate playground javascript templates" + git push + fi diff --git a/javascript/amazon-global-price-comparison/.env.example b/javascript/amazon-global-price-comparison/.env.example deleted file mode 100644 index f1797331..00000000 --- a/javascript/amazon-global-price-comparison/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -# Browserbase credentials - get these from https://www.browserbase.com/settings -BROWSERBASE_API_KEY=your_browserbase_api_key diff --git a/javascript/amazon-global-price-comparison/README.md b/javascript/amazon-global-price-comparison/README.md deleted file mode 100644 index e7f8a99d..00000000 --- a/javascript/amazon-global-price-comparison/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Amazon Global Price Comparison - -## AT A GLANCE - -- Goal: compare Amazon product prices across multiple countries using geolocation proxies. -- Uses Browserbase's managed proxy infrastructure to route traffic through different geographic locations (US, UK, Germany, France, Italy, Spain). -- Extracts structured product data (name, price, rating, reviews) using Stagehand's extraction capabilities with Zod schema validation. -- Sequential processing shows how different proxy locations return different pricing from the same Amazon search. -- Docs → https://docs.browserbase.com/features/proxies - -## GLOSSARY - -- geolocation proxies: route traffic through specific geographic locations (city, country) to access location-specific content and pricing - Docs → https://docs.browserbase.com/features/proxies#set-proxy-geolocation -- extract: extract structured data from web pages using natural language instructions and Zod schemas - Docs → https://docs.stagehand.dev/basics/extract -- proxies: Browserbase's managed proxy infrastructure supporting 201+ countries for geolocation-based routing - Docs → https://docs.browserbase.com/features/proxies - -## QUICKSTART - -1. cd amazon-global-price-comparison -2. pnpm install -3. cp .env.example .env -4. Add your Browserbase API key to .env -5. pnpm start - -## EXPECTED OUTPUT - -- Creates Browserbase sessions with geolocation proxies for each country (US, UK, DE, FR, IT, ES) -- Navigates to Amazon search results through location-specific proxies -- Extracts product name, price, rating, and review count for each location -- Displays formatted comparison table showing price differences across countries -- Outputs JSON results for programmatic use - -## COMMON PITFALLS - -- Browserbase Developer plan or higher is required to use proxies -- "Cannot find module": ensure all dependencies are installed (`pnpm install`) -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Geolocation fields are case-insensitive (city, country can be any case) -- Amazon may show different products in different regions - comparison works best for globally available products -- ERR_TUNNEL_CONNECTION_FAILED: indicates either a temporary proxy hiccup or a site unsupported by built-in proxies - -## USE CASES - -• Price arbitrage: Find the best country to purchase products from for international shipping -• Market research: Compare pricing strategies across different Amazon regions -• Competitive analysis: Monitor how competitors price products globally -• Travel shopping: Check prices before international trips to plan purchases - -## NEXT STEPS - -• Add more countries: Extend the COUNTRIES array with additional regions (Japan, Australia, Canada, etc.) -• Currency conversion: Add real-time currency conversion to normalize prices for comparison -• Price tracking: Store results over time to track price changes across regions -• Email alerts: Send notifications when price drops below a threshold in any country - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/amazon-global-price-comparison/index.js b/javascript/amazon-global-price-comparison/index.js deleted file mode 100644 index f3e4d465..00000000 --- a/javascript/amazon-global-price-comparison/index.js +++ /dev/null @@ -1,199 +0,0 @@ -// Amazon Global Price Comparison - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// Schema for a single product with structured extraction fields -const ProductSchema = z.object({ - name: z.string().describe("The full product title/name"), - price: z - .string() - .describe("The product price including currency symbol (e.g., '$29.99', '29,99 EUR', '29.99 GBP'). If no price is visible, return 'N/A'"), - rating: z.string().describe("The star rating (e.g., '4.5 out of 5 stars')"), - reviews_count: z.string().describe("The number of customer reviews (e.g., '1,234')"), - product_url: z - .string() - .describe("The full href URL link to the product detail page (starting with https:// or /dp/)"), -}); -// Schema for extracting multiple products from search results -const ProductsSchema = z.object({ - products: z.array(ProductSchema).describe("Array of products from search results"), -}); -// Supported countries for price comparison -// Add or remove countries as needed - see https://docs.browserbase.com/features/proxies for available geolocations -const COUNTRIES = [ - { name: "United States", code: "US", city: undefined, currency: "USD" }, - { name: "United Kingdom", code: "GB", city: "LONDON", currency: "GBP" }, - { name: "Germany", code: "DE", city: "BERLIN", currency: "EUR" }, - { name: "France", code: "FR", city: "PARIS", currency: "EUR" }, - { name: "Italy", code: "IT", city: "ROME", currency: "EUR" }, - { name: "Spain", code: "ES", city: "MADRID", currency: "EUR" }, -]; -/** - * Fetches products from Amazon for a specific country using geolocation proxy - * Uses Browserbase's managed proxy infrastructure to route traffic through the target country - * This ensures Amazon shows location-specific pricing and availability - */ -async function getProductsForCountry(searchQuery, country, resultsCount = 3) { - console.log(`\n=== Searching Amazon for "${searchQuery}" in ${country.name} ===`); - // Build geolocation config for proxy routing - const geolocation = { - country: country.code, - }; - if (country.city) { - geolocation.city = country.city; - } - // Initialize Stagehand with geolocation proxy configuration - // This ensures all browser traffic routes through the specified geographic location - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - browserbaseSessionCreateParams: { - proxies: [ - { - type: "browserbase", // Use Browserbase's managed proxy infrastructure for reliable geolocation routing - geolocation, - }, - ], - }, - }); - try { - console.log(`Initializing browser session with ${country.name} proxy...`); - await stagehand.init(); - const page = stagehand.context.pages()[0]; - // Alternative: Skip the search bar and go straight to results by building the search URL. - // Uncomment below to use direct navigation instead of stagehand.act() typing + clicking. - // const searchUrl = `https://www.amazon.com/s?k=${encodeURIComponent(searchQuery)}`; - // console.log(`Navigating to: ${searchUrl}`); - // await page.goto(searchUrl, { waitUntil: "domcontentloaded", timeout: 60000 }); - // Navigate to Amazon homepage to begin search - console.log(`[${country.name}] Navigating to Amazon...`); - await page.goto("https://www.amazon.com", { - waitUntil: "domcontentloaded", - timeout: 60000, - }); - // Perform search using natural language actions - console.log(`[${country.name}] Searching for: ${searchQuery}`); - await stagehand.act(`Type "${searchQuery}" into the search bar`); - await stagehand.act("Click the search button"); - // Extract products from search results using Stagehand's structured extraction - console.log(`[${country.name}] Extracting top ${resultsCount} products...`); - const extractionResult = await stagehand.extract(`Extract the first ${resultsCount} product search results from this Amazon page. For each product, extract: - 1. name: the full product title - 2. price: the displayed price WITH currency symbol (like $599.99 or 599,99 EUR). If no price shown, use "N/A" - 3. rating: the star rating text (like "4.5 out of 5 stars") - 4. reviews_count: the number of reviews (like "2,508") - 5. product_url: the href link to the product page (starts with /dp/ or https://) - - Only extract actual product listings, skip sponsored ads or recommendations.`, ProductsSchema); - // Clean up products - ensure price is never null and URLs are absolute - const cleanedProducts = extractionResult.products.map((p) => ({ - ...p, - price: p.price || "N/A", - product_url: p.product_url?.startsWith("http") - ? p.product_url - : p.product_url?.startsWith("/") - ? `https://www.amazon.com${p.product_url}` - : p.product_url || "N/A", - })); - console.log(`Found ${cleanedProducts.length} products in ${country.name}`); - await stagehand.close(); - return { - country: country.name, - countryCode: country.code, - currency: country.currency, - products: cleanedProducts.slice(0, resultsCount), - }; - } - catch (error) { - console.error(`Error fetching products from ${country.name}:`, error); - await stagehand.close(); - return { - country: country.name, - countryCode: country.code, - currency: country.currency, - products: [], - error: error instanceof Error ? error.message : String(error), - }; - } -} -/** - * Displays results in a formatted comparison table - * Shows product name, price, rating, and review count for each country - */ -function displayComparisonTable(results) { - console.log("\n" + "=".repeat(100)); - console.log("PRICE COMPARISON ACROSS COUNTRIES"); - console.log("=".repeat(100)); - // Find the first successful result to get product count - const successfulResult = results.find((r) => r.products.length > 0); - if (!successfulResult) { - console.log("No products found in any country."); - return; - } - // Display results for each product position - const maxProducts = Math.max(...results.map((r) => r.products.length)); - for (let i = 0; i < maxProducts; i++) { - console.log(`\n--- Product ${i + 1} ---`); - // Find the first available product name for this position - const productName = results.find((r) => r.products[i])?.products[i]?.name; - if (productName) { - const truncatedName = productName.length > 80 ? productName.slice(0, 77) + "..." : productName; - console.log(`Product: ${truncatedName}`); - } - console.log("\nPrices by Country:"); - console.log("-".repeat(70)); - for (const result of results) { - const countryPad = result.country.padEnd(20); - if (result.error) { - console.log(` ${countryPad} | Error: ${result.error}`); - } - else if (result.products[i]) { - const product = result.products[i]; - const price = product.price || "N/A"; - const pricePad = price.padEnd(18); - const ratingShort = product.rating?.split(" out")[0] || "N/A"; - const ratingPad = ratingShort.padEnd(6); - const reviews = product.reviews_count || "N/A"; - console.log(` ${countryPad} | ${pricePad} | ${ratingPad} stars | ${reviews} reviews`); - } - else { - console.log(` ${countryPad} | Not available in this country`); - } - } - } - console.log("\n" + "=".repeat(100)); -} -async function main() { - // Configure search parameters - const searchQuery = "iPhone 15 Pro Max 256GB"; - const resultsCount = 3; - console.log("=".repeat(60)); - console.log("AMAZON PRICE COMPARISON - GEOLOCATION PROXY DEMO"); - console.log("=".repeat(60)); - console.log(`Search Query: ${searchQuery}`); - console.log(`Results per country: ${resultsCount}`); - console.log(`Countries: ${COUNTRIES.map((c) => c.code).join(", ")}`); - console.log("=".repeat(60)); - // Process all countries concurrently for faster execution - // Each country uses its own browser session, so they can run in parallel - console.log(`\nFetching prices from ${COUNTRIES.length} countries concurrently...`); - const results = await Promise.all(COUNTRIES.map((country) => getProductsForCountry(searchQuery, country, resultsCount))); - // Display formatted comparison table - displayComparisonTable(results); - // Output JSON results for programmatic use - console.log("\n--- JSON OUTPUT ---"); - console.log(JSON.stringify(results, null, 2)); - console.log("\n=== Price comparison completed ==="); -} -main().catch((err) => { - console.error("Application error:", err); - console.error("\nCommon issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify geolocation proxy locations are valid (see https://docs.browserbase.com/features/proxies)"); - console.error(" - Ensure you have sufficient Browserbase credits"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/amazon-global-price-comparison/package.json b/javascript/amazon-global-price-comparison/package.json deleted file mode 100644 index e9519e6a..00000000 --- a/javascript/amazon-global-price-comparison/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "amazon-global-price-comparison-template", - "type": "module", - "scripts": { - "start": "node index.js" - }, - "dependencies": { - "@browserbasehq/stagehand": "latest", - "dotenv": "^16.4.7", - "zod": "latest" - } -} diff --git a/javascript/amazon-product-scraping/.env.example b/javascript/amazon-product-scraping/.env.example deleted file mode 100644 index 8c3f7702..00000000 --- a/javascript/amazon-product-scraping/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -# Browserbase Configuration -BROWSERBASE_API_KEY=your_browserbase_api_key diff --git a/javascript/amazon-product-scraping/README.md b/javascript/amazon-product-scraping/README.md deleted file mode 100644 index 58a75270..00000000 --- a/javascript/amazon-product-scraping/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Stagehand + Browserbase: Amazon Product Scraping - -## AT A GLANCE - -- Goal: scrape the first 3 Amazon search results for a given query and return structured product data. -- AI-Powered Search: uses Stagehand `act` to type in the search bar and click search (or optionally navigate directly to the search URL). -- Structured Extraction: uses `extract` with a Zod schema to get product name, price, rating, review count, and product URL. -- Model: uses `google/gemini-2.5-flash` for fast, cost-effective automation. - Docs → https://docs.stagehand.dev - -## GLOSSARY - -- act: perform UI actions from a prompt (type in search bar, click search) - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from pages using schemas - Docs → https://docs.stagehand.dev/basics/extract - -## QUICKSTART - -1. cd typescript/amazon-product-scraping -2. npm install -3. cp .env.example .env (or create .env with required keys) -4. Add BROWSERBASE_API_KEY to .env -5. Optionally edit SEARCH_QUERY in index.ts -6. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Displays live session link for monitoring -- Navigates to Amazon and performs search (or direct URL navigation if uncommented) -- Extracts the first 3 products with name, price, rating, reviews count, and product URL -- Outputs JSON to console -- Closes session cleanly - -## COMMON PITFALLS - -- "Cannot find module": ensure npm install completed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Amazon layout changes: extraction may need prompt/schema updates if Amazon changes their search results UI -- Find more information on your Browserbase dashboard → https://www.browserbase.com/sign-in - -## USE CASES - -• Price monitoring: Scrape top results for a product query to track prices and availability over time. -• Competitor research: Extract product titles, ratings, and review counts for comparison. -• Catalog building: Pull structured product data for feeds, dashboards, or internal tools. - -## NEXT STEPS - -• Switch to direct URL: Uncomment the URL-based search block in index.ts for faster runs without LLM search actions. -• Parameterize query: Accept SEARCH_QUERY from CLI or env for different products without editing code. -• Paginate: Extend extraction to multiple pages or increase the number of products per run. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/amazon-product-scraping/index.js b/javascript/amazon-product-scraping/index.js deleted file mode 100644 index 8209f4f1..00000000 --- a/javascript/amazon-product-scraping/index.js +++ /dev/null @@ -1,73 +0,0 @@ -// Stagehand + Browserbase: Amazon Product Scraping - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// ============= CONFIGURATION ============= -// Update this value to search for different products -const SEARCH_QUERY = "Seiko 5"; -// ========================================= -// Schema for a single product with structured extraction fields -const ProductSchema = z.object({ - name: z.string().describe("The full product title/name"), - price: z.string().describe("The product price including currency symbol (e.g., '$29.99')"), - rating: z.string().describe("The star rating (e.g., '4.5 out of 5 stars')"), - reviews_count: z.string().describe("The number of customer reviews (e.g., '1,234')"), - product_url: z.string().url().describe("The URL link to the product detail page on Amazon"), -}); -// Schema for extracting multiple products from search results -const ProductsSchema = z.object({ - products: z.array(ProductSchema).describe("Array of the first 3 products from search results"), -}); -async function main() { - console.log("Starting Amazon Product Scraping..."); - // Initialize Stagehand with Browserbase for cloud-based browser automation. - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - model: "google/gemini-2.5-flash", - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Alternative: skip the search bar and go straight to results by building the search URL. - // Uncomment below to use direct navigation instead of stagehand.act() typing + clicking. - // // Build search URL - // const encodedQuery = encodeURIComponent(query).replace(/%20/g, "+"); - // const searchUrl = `https://www.amazon.com/s?k=${encodedQuery}`; - // console.log(`Navigating to: ${searchUrl}`); - // await page.goto(searchUrl, { - // waitUntil: "domcontentloaded", - // }); - // Navigate to Amazon homepage to begin search. - console.log("Navigating to Amazon..."); - await page.goto("https://www.amazon.com"); - // Perform search using natural language actions. - console.log(`Searching for: ${SEARCH_QUERY}`); - await stagehand.act(`Type ${SEARCH_QUERY} into the search bar`); - await stagehand.act("Click the search button"); - // Extract structured product data using Zod schema for type safety. - console.log("Extracting product data..."); - const products = await stagehand.extract("Extract the details of the FIRST 3 products in the search results. Get the product name, price, star rating, number of reviews, and the URL link to the product page.", ProductsSchema); - console.log("Products found:"); - console.log(JSON.stringify(products, null, 2)); - } - catch (error) { - console.error("Error during product scraping:", error); - } - finally { - // Always close session to release resources and clean up. - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in Amazon product scraping:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify network connectivity"); - console.error("Docs: https://docs.stagehand.dev"); - process.exit(1); -}); diff --git a/javascript/amazon-product-scraping/package.json b/javascript/amazon-product-scraping/package.json deleted file mode 100644 index 83780e3b..00000000 --- a/javascript/amazon-product-scraping/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "amazon-product-scraping", - "version": "1.0.0", - "description": "Stagehand + Browserbase: Amazon Product Scraping", - "type": "module", - "main": "index.js", - "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" - }, - "dependencies": { - "@browserbasehq/stagehand": "latest", - "dotenv": "^16.4.5", - "zod": "^3.23.8" - } -} diff --git a/javascript/basic-caching/README.md b/javascript/basic-caching/README.md deleted file mode 100644 index 2999a4f5..00000000 --- a/javascript/basic-caching/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# Stagehand + Browserbase: Basic Caching - -## AT A GLANCE - -- Goal: Demonstrate how Stagehand's caching feature dramatically reduces cost and latency by reusing previously computed actions instead of calling the LLM every time. -- Shows side-by-side comparison of workflows with and without caching enabled. -- Demonstrates massive cost savings for repeated workflows (99.9% reduction in LLM calls). -- Docs → https://docs.stagehand.dev/v3/best-practices/caching#caching-actions - -## GLOSSARY - -- caching: Stagehand can cache action results based on instruction text and page context, eliminating redundant LLM calls - Docs → https://docs.stagehand.dev/v3/best-practices/caching#caching-actions -- act: execute actions on web pages using natural language instructions - Docs → https://docs.stagehand.dev/basics/act - -## QUICKSTART - -1. pnpm install -2. cp .env.example .env -3. Add your Browserbase API key to .env -4. pnpm start (run twice to see cache benefits!) - -## EXPECTED OUTPUT - -- First run: Executes workflow without cache, then with cache enabled (populates cache) -- Subsequent runs: Uses cached actions for instant execution with zero LLM calls -- Displays timing comparison, cost savings, and cache statistics -- Shows cache location and file structure - -## HOW CACHING WORKS - -**Cache Key Generation:** - -- Based on instruction text -- Based on page context -- Automatically computed - -**When Cache is Used:** - -- ✅ Same instruction -- ✅ Same page structure -- ✅ Cache file exists -- ❌ Different instruction -- ❌ Page structure changed significantly - -**Cache Storage:** - -- Location: `.cache/stagehand-demo` -- Format: JSON files (one per cached action) -- Persistent across runs - -## BENEFITS FOR REPEATED WORKFLOWS - -**Example Scenario: 1,000 customers × 10 portals = 10,000 payment flows** - -**Without caching:** - -- 10,000 workflows × 5 actions = 50,000 LLM calls -- Cost: ~$500-2,500 -- Latency: 2-3s per action × 5 = 10-15s per payment - -**With caching:** - -- First payment per portal: 5 LLM calls (populate cache) -- Next 999 payments: 0 LLM calls (use cache) -- Total: 10 portals × 5 actions = 50 LLM calls -- Cost: ~$0.50-2.50 (99.9% savings!) -- Latency: <100ms per action × 5 = <0.5s per payment - -**Key Insight:** -Payment portals rarely change → Cache actions once → Reuse for thousands of payments → Massive cost + latency reduction - -## COMMON PITFALLS - -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Cache not working: ensure cacheDir path is writable and check that instruction text matches exactly -- First run slower: expected behavior - cache is populated on first run, subsequent runs will be instant -- Find more information on your Browserbase dashboard -> https://www.browserbase.com/sign-in - -## USE CASES - -• Payment processing: Cache form-filling actions for payment portals that don't change frequently, processing thousands of payments with minimal LLM calls. -• Data entry automation: Reuse actions for repetitive data entry tasks across similar forms or interfaces. -• Testing workflows: Cache test actions to speed up regression testing and reduce API costs during development. - -## BEST PRACTICES - -- ✅ Enable caching in production for repeated workflows -- ✅ One cache per portal/interface type -- ✅ Invalidate cache when page structure changes significantly -- ✅ Monitor cache hit rate to optimize cache effectiveness -- ✅ Warm cache with test runs before production deployment - -## NEXT STEPS - -• Customize cache directory: Modify cacheDir to organize caches by workflow type or environment. -• Add cache invalidation: Implement logic to clear cache when page structure changes or after a certain time period. -• Monitor cache performance: Track cache hit rates and cost savings to measure effectiveness. - -## TRY IT YOURSELF - -1. Run this script again: `pnpm start` - → Second run will be MUCH faster (cache hits) - -2. Clear cache and run again: - `rm -rf .cache/stagehand-demo && pnpm start` - → Back to first-run behavior - -3. Check cache contents: - `ls -la .cache/stagehand-demo` - → See cached action files - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/basic-caching/index.js b/javascript/basic-caching/index.js deleted file mode 100644 index 39047c0b..00000000 --- a/javascript/basic-caching/index.js +++ /dev/null @@ -1,131 +0,0 @@ -// Stagehand + Browserbase: Basic Caching - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import fs from "fs"; -import path from "path"; -const CACHE_DIR = path.join(process.cwd(), ".cache", "stagehand-demo"); -async function runWithoutCache() { - console.log("RUN 1: WITHOUT CACHING"); - const startTime = Date.now(); - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "google/gemini-2.5-flash", - enableCaching: false, - }); - await stagehand.init(); - const page = stagehand.context.pages()[0]; - try { - console.log("Navigating to Stripe checkout..."); - await page.goto("https://checkout.stripe.dev/preview", { - waitUntil: "domcontentloaded", - }); - await stagehand.act("Click on the View Demo button"); - await stagehand.act("Type 'test@example.com' into the email field"); - await stagehand.act("Type '4242424242424242' into the card number field"); - await stagehand.act("Type '12/34' into the expiration date field"); - const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); - console.log(`Total time: ${elapsed}s`); - console.log("Cost: ~$0.01-0.05 (4 LLM calls)"); - console.log("API calls: 4 (one per action)\n"); - await stagehand.close(); - return { elapsed, llmCalls: 4 }; - } - catch (error) { - console.error("Error:", error.message); - await stagehand.close(); - throw error; - } -} -async function runWithCache() { - console.log("RUN 2: WITH CACHING"); - const startTime = Date.now(); - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "google/gemini-2.5-flash", - enableCaching: true, - cacheDir: CACHE_DIR, - }); - await stagehand.init(); - const page = stagehand.context.pages()[0]; - try { - console.log("Navigating to Stripe checkout..."); - await page.goto("https://checkout.stripe.dev/preview", { - waitUntil: "domcontentloaded", - }); - await stagehand.act("Click on the View Demo button"); - await stagehand.act("Type 'test@example.com' into the email field"); - await stagehand.act("Type '4242424242424242' into the card number field"); - await stagehand.act("Type '12/34' into the expiration date field"); - const elapsed = ((Date.now() - startTime) / 1000).toFixed(2); - const cacheExists = fs.existsSync(CACHE_DIR); - const cacheFiles = cacheExists ? fs.readdirSync(CACHE_DIR).length : 0; - console.log(`Total time: ${elapsed}s`); - if (cacheFiles > 0) { - console.log("Cost: $0.00 (cache hits, no LLM calls)"); - console.log("API calls: 0 (all from cache)"); - console.log(`Cache entries: ${cacheFiles}`); - } - else { - console.log("💰Cost: ~$0.01-0.05 (first run, populated cache)"); - console.log("📡API calls: 4 (saved to cache for next run)"); - console.log("📂Cache created"); - } - console.log(); - await stagehand.close(); - return { elapsed, llmCalls: cacheFiles > 0 ? 0 : 4 }; - } - catch (error) { - console.error("Error:", error.message); - await stagehand.close(); - throw error; - } -} -async function main() { - console.log("\n╔═══════════════════════════════════════════════════════════╗"); - console.log("║ Caching Demo - Run This Script TWICE! ║"); - console.log("╚═══════════════════════════════════════════════════════════╝\n"); - console.log("This demo shows caching impact by running the same workflow twice:\n"); - console.log("First run:"); - console.log(" 1. WITHOUT cache (baseline)"); - console.log(" 2. WITH cache enabled (populates cache)\n"); - console.log("Second run:"); - console.log(" - WITH cache (instant, $0 cost)\n"); - console.log("Run 'pnpm start' twice to see the difference!\n"); - // Check if cache exists - const cacheExists = fs.existsSync(CACHE_DIR); - if (cacheExists) { - const cacheFiles = fs.readdirSync(CACHE_DIR); - console.log(`📂 Cache found: ${cacheFiles.length} entries`); - console.log(" This is a SUBSEQUENT run - cache will be used!\n"); - } - else { - console.log("No cache found - first run will populate cache"); - } - console.log("\nRunning comparison: without cache vs with cache...\n"); - const withoutCache = await runWithoutCache(); - const withCache = await runWithCache(); - console.log("\n=== Comparison ==="); - console.log(`Without caching: ${withoutCache.elapsed}s, ${withoutCache.llmCalls} LLM calls`); - console.log(`With caching: ${withCache.elapsed}s, ${withCache.llmCalls} LLM calls`); - if (withCache.llmCalls === 0) { - const speedup = (parseFloat(withoutCache.elapsed) / parseFloat(withCache.elapsed)).toFixed(1); - console.log(`\nSpeedup: ${speedup}x faster with cache`); - console.log("Cost savings: 100% (no LLM calls)"); - } - console.log("\nRun again to see cache benefits on subsequent runs!"); -} -main().catch((err) => { - console.error("Error in caching demo:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/basic-caching/package.json b/javascript/basic-caching/package.json deleted file mode 100644 index fc30695b..00000000 --- a/javascript/basic-caching/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "basic-caching", - "version": "1.0.0", - "description": "Stagehand + Browserbase: Basic Caching Demo", - "type": "module", - "main": "index.js", - "scripts": { - "start": "node index.js" - }, - "dependencies": { - "@browserbasehq/stagehand": "latest", - "dotenv": "latest" - }, - "packageManager": "pnpm@9.0.0" -} diff --git a/javascript/basic-recaptcha/README.md b/javascript/basic-recaptcha/README.md deleted file mode 100644 index ab5b87c9..00000000 --- a/javascript/basic-recaptcha/README.md +++ /dev/null @@ -1,113 +0,0 @@ -# Stagehand + Browserbase: Basic reCAPTCHA Solving - -## AT A GLANCE - -- Goal: Demonstrate automatic reCAPTCHA solving using Browserbase's built-in captcha solving capabilities. -- Automated Solving: Browserbase automatically detects and solves CAPTCHAs in the background. CAPTCHA solving is **enabled by default** - you don't need to set `solveCaptchas: true` unless you want to explicitly enable it (or set it to `false` to disable). -- Solving Time: CAPTCHA solving typically takes between 5-30 seconds depending on CAPTCHA type and complexity. -- Progress Monitoring: Listen for console messages (`browserbase-solving-started`, `browserbase-solving-finished`) to track captcha solving progress in real-time. -- Proxies Recommended: Enable proxies for higher CAPTCHA solving success rates. -- Verification: Extracts page content to verify successful captcha solving and form submission. -- Docs → https://docs.browserbase.com/features/stealth-mode#captcha-solving - -## GLOSSARY - -- solveCaptchas: Browserbase browser setting that enables automatic captcha solving for reCAPTCHA, hCaptcha, and other captcha types. Enabled by default for Basic and Advanced Stealth Mode. - Docs → https://docs.browserbase.com/features/stealth-mode#captcha-solving -- CAPTCHA solving: When a CAPTCHA is detected, Browserbase attempts to solve it automatically in the background, allowing your automation to continue without manual intervention. -- console messages: browser console events that indicate captcha solving status: - - `browserbase-solving-started`: emitted when CAPTCHA detection begins - - `browserbase-solving-finished`: emitted when CAPTCHA solving completes -- custom CAPTCHA solving: For non-standard or custom captcha providers, you can specify CSS selectors for the captcha image and input field using `captchaImageSelector` and `captchaInputSelector` in browserSettings. -- act: perform UI actions from a prompt (type, click, fill forms) - Docs → https://docs.stagehand.dev/basics/act -- extract: pull data from web pages using natural language instructions - Docs → https://docs.stagehand.dev/basics/extract - -## STAGEHAND VS PLAYWRIGHT - -| Feature | Stagehand (this template) | Playwright | -| --------------- | ------------------------------------ | ----------------------------- | -| Actions | `stagehand.act("Click the button")` | `page.click()`, `page.goto()` | -| Data Extraction | `stagehand.extract("Get the price")` | Manual DOM queries | - -## CAPTCHA SOLVING DETAILS - -### How CAPTCHA Solving Works - -Browserbase provides integrated CAPTCHA solving to handle challenges automatically: - -- **Automatic Detection**: When a CAPTCHA is detected on a page, Browserbase attempts to solve it in the background -- **Solving Time**: CAPTCHA solving typically takes between 5-30 seconds, depending on the CAPTCHA type and complexity -- **Default Behavior**: CAPTCHA solving is enabled by default for Basic and Advanced Stealth Mode -- **Proxies**: It's recommended to enable proxies when using CAPTCHA solving for higher success rates -- **Multiple Types**: Browserbase supports reCAPTCHA, hCaptcha, and other common captcha providers automatically - -### Custom CAPTCHA Solving - -For non-standard or custom captcha providers, you can specify CSS selectors to guide the solution process: - -```typescript -browserSettings: { - solveCaptchas: true, - captchaImageSelector: "#custom-captcha-image-id", - captchaInputSelector: "#custom-captcha-input-id" -} -``` - -To find the selectors: - -1. Right-click on the captcha image and select "Inspect" to get the image selector -2. Right-click on the input field and select "Inspect" to get the input selector -3. Use the element's `id` or a CSS selector that uniquely identifies it - -### Disabling CAPTCHA Solving - -If you want to disable automatic captcha solving, set `solveCaptchas: false` in browserSettings: - -```typescript -browserSettings: { - solveCaptchas: false; -} -``` - -## QUICKSTART - -1. cd basic-recaptcha -2. pnpm install -3. cp .env.example .env -4. Add your Browserbase API key to .env -5. pnpm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Displays live session link for monitoring -- Navigates to Google reCAPTCHA demo page -- Clicks submit button to trigger reCAPTCHA challenge -- Waits for Browserbase to automatically solve the captcha -- Logs captcha solving progress messages -- Clicks submit again after captcha is solved -- Extracts and displays page content -- Verifies successful captcha solving by checking for success message -- Closes session cleanly - -## COMMON PITFALLS - -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Captcha solving not enabled: ensure `solveCaptchas: true` is set in browserSettings (enabled by default) -- Solving timeout: allow up to 30 seconds for CAPTCHA solving to complete before timing out -- Proxies not enabled: enable proxies in browserSettings for higher CAPTCHA solving success rates -- Demo page inaccessible: verify the reCAPTCHA demo page URL is accessible and hasn't changed -- Console message timing: ensure console event listeners are set up before triggering the captcha -- Verification failure: success message check may fail if page structure changes; check extracted text manually -- Custom captcha selectors: for non-standard CAPTCHAs, verify that `captchaImageSelector` and `captchaInputSelector` are correctly defined - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/basic-recaptcha/index.js b/javascript/basic-recaptcha/index.js deleted file mode 100644 index a1924c66..00000000 --- a/javascript/basic-recaptcha/index.js +++ /dev/null @@ -1,81 +0,0 @@ -// Basic reCAPTCHA Solving with Browserbase - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -async function main() { - // Initialize Stagehand with Browserbase for cloud-based browser automation. - // Enable captcha solving in browser settings for automatic reCAPTCHA handling. - const solveCaptchas = true; // Set to false to disable automatic captcha solving (true by default) - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - browserbaseSessionCreateParams: { - browserSettings: { - solveCaptchas: solveCaptchas, - }, - }, - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to Google reCAPTCHA demo page to test captcha solving. - console.log("Navigating to reCAPTCHA demo page..."); - await page.goto("https://google.com/recaptcha/api2/demo"); - // Wait for Browserbase to solve the captcha automatically. - // Listen for console messages indicating captcha solving progress. - if (solveCaptchas) { - console.log("Waiting for captcha to be solved..."); - await new Promise((resolve) => { - page.on("console", (msg) => { - if (msg.text() === "browserbase-solving-started") { - console.log("Captcha solving in progress..."); - } - else if (msg.text() === "browserbase-solving-finished") { - console.log("Captcha solving completed!"); - resolve(); - } - }); - }); - } - else { - console.log("Captcha solving is disabled. Skipping wait..."); - } - // Click submit again after captcha is solved to complete the form submission. - console.log("Clicking submit button after captcha is solved..."); - await stagehand.act("Click the Submit button"); - // Extract and display the page content to verify successful submission. - console.log("Extracting page content..."); - const text = await stagehand.extract("Extract all the text on this page"); - console.log("Page content:"); - console.log(text); - // Check if captcha was successfully solved by looking for success message. - if (text.extraction.includes("Verification Success... Hooray!")) { - console.log("reCAPTCHA successfully solved!"); - } - else { - console.log("Could not verify captcha success from page content"); - } - } - catch (error) { - console.error("Error during reCAPTCHA solving:", error); - } - finally { - // Always close session to release resources and clean up. - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in reCAPTCHA solving example:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify solveCaptchas is enabled in browserSettings"); - console.error(" - Ensure the demo page is accessible"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/business-lookup/README.md b/javascript/business-lookup/README.md deleted file mode 100644 index 08c01a77..00000000 --- a/javascript/business-lookup/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Stagehand + Browserbase: Business Lookup with Agent - -## AT A GLANCE - -- Goal: Automate business registry searches using an autonomous AI agent with computer-use capabilities. -- Uses Stagehand Agent in CUA mode to navigate complex UI elements, apply filters, and extract structured business data. -- Demonstrates extraction with Zod schema validation for consistent data retrieval. -- Docs → https://docs.stagehand.dev/basics/agent - -## GLOSSARY - -- agent: create an autonomous AI agent that can execute complex multi-step tasks - Docs → https://docs.stagehand.dev/basics/agent#what-is-agent -- extract: extract structured data from web pages using natural language instructions - Docs → https://docs.stagehand.dev/basics/extract - -## QUICKSTART - -1. npm install -2. cp .env.example .env -3. Add required API keys/IDs to .env -4. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Displays live session link for monitoring -- Navigates to SF Business Registry search page -- Agent searches for business using DBA Name filter -- Agent completes search and opens business details -- Extracts structured business information (DBA Name, Account Number, NAICS Code, etc.) -- Outputs extracted data as JSON -- Closes session cleanly - -## COMMON PITFALLS - -- Dependency install errors: ensure npm install completed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY and GOOGLE_API_KEY -- Google API access: ensure you have access to Google's gemini-2.5-computer-use-preview-10-2025 model -- Agent failures: check that the business name exists in the registry and that maxSteps is sufficient for complex searches -- Find more information on your Browserbase dashboard -> https://www.browserbase.com/sign-in - -## USE CASES - -• Business verification: Automate registration status checks, license validation, and compliance verification for multiple businesses. -• Data enrichment: Collect structured business metadata (NAICS codes, addresses, ownership) for research or CRM updates. -• Due diligence: Streamline background checks by autonomously searching and extracting business registration details from public registries. - -## NEXT STEPS - -• Parameterize search: Accept business names as command-line arguments or from a CSV file for batch processing. -• Expand extraction: Add support for additional fields like tax status, licenses, or historical registration changes. -• Multi-registry support: Extend agent to search across multiple city or state business registries with routing logic. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/business-lookup/index.js b/javascript/business-lookup/index.js deleted file mode 100644 index 79bcae1d..00000000 --- a/javascript/business-lookup/index.js +++ /dev/null @@ -1,73 +0,0 @@ -// Business Lookup with Agent - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// Business search variables -const businessName = "Jalebi Street"; -async function main() { - // Initialize Stagehand with Browserbase for cloud-based browser automation. - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - model: "openai/gpt-4.1", - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to SF Business Registry search page. - console.log(`Navigating to SF Business Registry...`); - await page.goto("https://data.sfgov.org/stories/s/Registered-Business-Lookup/k6sk-2y6w/"); - // Create agent with computer use capabilities for autonomous business search. - const agent = stagehand.agent({ - cua: true, // Enable Computer Use Agent mode - model: { - modelName: "google/gemini-2.5-computer-use-preview-10-2025", - apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY, - }, - systemPrompt: "You are a helpful assistant that can use a web browser to search for business information.", - }); - console.log(`Searching for business: ${businessName}`); - const result = await agent.execute({ - instruction: `Find and look up the business "${businessName}" in the SF Business Registry. Use the DBA Name filter to search for "${businessName}", apply the filter, and click on the business row to view detailed information. Scroll towards the right to see the NAICS code.`, - maxSteps: 30, - }); - if (!result.success) { - throw new Error("Agent failed to complete the search"); - } - // Extract comprehensive business information after agent completes the search. - console.log("Extracting business information..."); - const businessInfo = await stagehand.extract("Extract all visible business information including DBA Name, Ownership Name, Business Account Number, Location Id, Street Address, Business Start Date, Business End Date, Neighborhood, NAICS Code, and NAICS Code Description", z.object({ - dbaName: z.string(), - ownershipName: z.string().optional(), - businessAccountNumber: z.string(), - locationId: z.string().optional(), - streetAddress: z.string().optional(), - businessStartDate: z.string().optional(), - businessEndDate: z.string().optional(), - neighborhood: z.string().optional(), - naicsCode: z.string(), - naicsCodeDescription: z.string().optional(), - }), { page }); - console.log("Business Information:"); - console.log(JSON.stringify(businessInfo, null, 2)); - } - catch (error) { - console.error("Error during business lookup:", error); - } - finally { - // Always close session to release resources and clean up. - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in business lookup:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify GOOGLE_API_KEY is set for the agent"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/company-address-finder/README.md b/javascript/company-address-finder/README.md deleted file mode 100644 index f60b4fe5..00000000 --- a/javascript/company-address-finder/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Stagehand + Browserbase: Company Address Finder - -## AT A GLANCE - -- Goal: Automate discovery of company legal information and physical addresses from Terms of Service and Privacy Policy pages. -- CUA Agent: Uses autonomous computer-use agent to search for company homepages via Google and navigate to legal documents. -- Data Extraction: Extracts structured data including homepage URLs, ToS/Privacy Policy links, and physical mailing addresses. -- Fallback Strategy: Intelligently falls back from Terms of Service to Privacy Policy if address is not found. -- Retry Logic: Built-in exponential backoff for reliability against network failures. -- Scalable: Supports both sequential and concurrent processing (concurrent requires Startup/Developer plan or higher). - -## GLOSSARY - -- agent: autonomous AI agent with computer-use capabilities that can navigate websites like a human - Docs → https://docs.stagehand.dev/basics/agent -- extract: pull structured data from web pages using natural language instructions and Zod schemas - Docs → https://docs.stagehand.dev/basics/extract -- CUA (Computer Use Agent): agent mode that enables full browser interaction (search, click, scroll, type) - Docs → https://docs.stagehand.dev/basics/agent#what-is-cua-mode -- concurrent sessions: run multiple browser sessions simultaneously for faster batch processing - Docs → https://docs.browserbase.com/guides/concurrency-rate-limits -- exponential backoff: retry strategy that increases wait time between attempts for reliability - -## QUICKSTART - -1. cd company-address-finder -2. npm install -3. cp .env.example .env -4. Add your Browserbase API key and Google Generative AI API key to .env -5. Edit COMPANY_NAMES array in index.ts to specify which companies to process -6. npm start - -## EXPECTED OUTPUT - -- Initializes browser session for each company with live view link -- Agent navigates to Google and searches for company homepage -- Extracts Terms of Service and Privacy Policy links from homepage -- Navigates to Terms of Service and extracts physical address -- Falls back to Privacy Policy if address not found in ToS -- Outputs comprehensive JSON with all extracted data for each company -- Displays processing status and session closure for each company - -## COMMON PITFALLS - -- Missing credentials: verify .env contains BROWSERBASE_API_KEY and GOOGLE_GENERATIVE_AI_API_KEY -- Google API access: ensure you have access to google/gemini-2.5-computer-use-preview-10-2025 model -- Concurrent processing: MAX_CONCURRENT > 1 requires Browserbase Startup or Developer plan or higher (default is 1 for sequential) -- Company not found: agent may fail if company name is ambiguous or doesn't have a clear web presence -- Address extraction: some companies may not list physical addresses in their legal documents -- Session timeouts: long-running batches may hit 900s timeout (adjust browserbaseSessionCreateParams if needed) - -## USE CASES - -• Legal compliance research: Collect company addresses and legal document URLs for due diligence, vendor verification, or compliance audits. -• Business intelligence: Build datasets of company locations and legal information for market research or competitive analysis. -• Contact data enrichment: Augment CRM or database records with verified physical addresses extracted from official company documents. -• Multi-company batch processing: Process lists of companies (investors, partners, clients) to gather standardized location data at scale. - -## NEXT STEPS - -• Parameterize inputs: Accept company names from CSV files, command-line arguments, or API endpoints for dynamic batch processing. -• Expand extraction: Add support for additional fields like contact emails, phone numbers, business registration numbers, or founding dates. -• Multi-source validation: Cross-reference addresses from multiple pages (About, Contact, Footer) to improve accuracy and confidence. -• Export formats: Add CSV, Excel, or database export options with configurable field mappings for downstream integrations. -• Error handling: Implement more granular error categorization (not found vs. no address vs. extraction failure) for better reporting. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/company-address-finder/index.js b/javascript/company-address-finder/index.js deleted file mode 100644 index bf01f196..00000000 --- a/javascript/company-address-finder/index.js +++ /dev/null @@ -1,233 +0,0 @@ -// Stagehand + Browserbase: Company Address Finder - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// Companies to process (modify this array to add/remove companies) -const COMPANY_NAMES = ["Browserbase", "Mintlify", "Wordware", "Reducto"]; -// Maximum number of companies to process concurrently. -// Default: 1 (sequential processing - works on all plans) -// Set to > 1 for concurrent processing (requires Startup or Developer plan or higher) -const MAX_CONCURRENT = 1; -// Retries an async function with exponential backoff -// Handles transient network/page load failures for reliability -async function withRetry(fn, description, maxRetries = 3, delayMs = 2000) { - let lastError = null; - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - return await fn(); - } - catch (error) { - lastError = error instanceof Error ? error : new Error(String(error)); - if (attempt < maxRetries) { - console.log(`${description} - Attempt ${attempt} failed, retrying in ${delayMs}ms...`); - await new Promise((resolve) => setTimeout(resolve, delayMs)); - } - } - } - throw new Error(`${description} - Failed after ${maxRetries} attempts: ${lastError?.message}`); -} -// Processes a single company: finds homepage, extracts ToS/Privacy links, and extracts physical address -// Uses CUA agent to navigate and Stagehand extract() for structured data extraction -// Falls back to Privacy Policy if address not found in Terms of Service -async function processCompany(companyName) { - console.log(`\nProcessing: ${companyName}`); - let stagehand = null; - try { - // Initialize Stagehand with Browserbase - stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - browserbaseSessionCreateParams: { - region: "us-east-1", - timeout: 900, - browserSettings: { - viewport: { - width: 1920, - height: 1080, - }, - }, - }, - }); - console.log(`[${companyName}] Initializing browser session...`); - await stagehand.init(); - const sessionId = stagehand.browserbaseSessionId; - if (!sessionId) { - throw new Error(`Failed to initialize browser session for ${companyName}`); - } - console.log(`[${companyName}] Live View Link: https://browserbase.com/sessions/${sessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to Google as starting point for CUA agent to search and find company homepage - console.log(`[${companyName}] Navigating to Google...`); - await withRetry(async () => { - await page.goto("https://www.google.com/", { - waitUntil: "domcontentloaded", - }); - }, `[${companyName}] Initial navigation to Google`); - // Create CUA agent for autonomous navigation - // Agent can interact with the browser like a human: search, click, scroll, and navigate - const agent = stagehand.agent({ - cua: true, - model: { - modelName: "google/gemini-2.5-computer-use-preview-10-2025", - apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY, - }, - systemPrompt: `You are a helpful assistant that can use a web browser. - You are currently on the following page: ${page.url()}. - Do not ask follow up questions, the user will trust your judgement.`, - }); - console.log(`[${companyName}] Finding company homepage using CUA agent...`); - await withRetry(async () => { - await agent.execute({ - instruction: `Navigate to the ${companyName} website`, - maxSteps: 5, - highlightCursor: true, - }); - }, `[${companyName}] Navigation to website`); - const homepageUrl = page.url(); - console.log(`[${companyName}] Homepage found: ${homepageUrl}`); - // Extract both legal document links in parallel for speed (independent operations) - console.log(`[${companyName}] Finding Terms of Service & Privacy Policy links...`); - const [termsResult, privacyResult] = await Promise.allSettled([ - stagehand.extract("extract the link to the Terms of Service page (may also be labeled as Terms of Use, Terms and Conditions, or similar equivalent names)", z.object({ - termsOfServiceLink: z.string().url(), - })), - stagehand.extract("extract the link to the Privacy Policy page (may also be labeled as Privacy Notice, Privacy Statement, or similar equivalent names)", z.object({ - privacyPolicyLink: z.string().url(), - })), - ]); - let termsOfServiceLink = ""; - let privacyPolicyLink = ""; - if (termsResult.status === "fulfilled" && termsResult.value) { - termsOfServiceLink = termsResult.value.termsOfServiceLink || ""; - console.log(`[${companyName}] Terms of Service: ${termsOfServiceLink}`); - } - if (privacyResult.status === "fulfilled" && privacyResult.value) { - privacyPolicyLink = privacyResult.value.privacyPolicyLink || ""; - console.log(`[${companyName}] Privacy Policy: ${privacyPolicyLink}`); - } - let address = ""; - // Try Terms of Service first - most likely to contain physical address for legal/contact purposes - if (termsOfServiceLink) { - console.log(`[${companyName}] Extracting address from Terms of Service...`); - await withRetry(async () => { - await page.goto(termsOfServiceLink); - }, `[${companyName}] Navigate to Terms of Service`); - try { - const addressResult = await stagehand.extract("Extract the physical company mailing address (street, city, state, postal code, and country if present) from the Terms of Service page. Ignore phone numbers or email addresses.", z.object({ - companyAddress: z.string(), - })); - const companyAddress = addressResult.companyAddress || ""; - if (companyAddress && companyAddress.trim().length > 0) { - address = companyAddress.trim(); - console.log(`[${companyName}] Address found in Terms of Service: ${address}`); - } - } - catch (error) { - console.log(`[${companyName}] Could not extract address from Terms of Service page: ${error}`); - } - } - // Fallback: check Privacy Policy if address not found in Terms of Service - if (!address && privacyPolicyLink) { - console.log(`[${companyName}] Address not found in Terms of Service, trying Privacy Policy...`); - await withRetry(async () => { - await page.goto(privacyPolicyLink); - }, `[${companyName}] Navigate to Privacy Policy`); - try { - const addressResult = await stagehand.extract("Extract the physical company mailing address(street, city, state, postal code, and country if present) from the Privacy Policy page. Ignore phone numbers or email addresses.", z.object({ - companyAddress: z.string(), - })); - const companyAddress = addressResult.companyAddress || ""; - if (companyAddress && companyAddress.trim().length > 0) { - address = companyAddress.trim(); - console.log(`[${companyName}] Address found in Privacy Policy: ${address}`); - } - } - catch (error) { - console.log(`[${companyName}] Could not extract address from Privacy Policy page: ${error}`); - } - } - if (!address) { - address = "Address not found in Terms of Service or Privacy Policy pages"; - console.log(`[${companyName}] ${address}`); - } - const result = { - companyName, - homepageUrl, - termsOfServiceLink, - privacyPolicyLink, - address, - }; - console.log(`[${companyName}] Successfully processed`); - return result; - } - catch (error) { - console.error(`[${companyName}] Error:`, error); - return { - companyName, - homepageUrl: "", - termsOfServiceLink: "", - privacyPolicyLink: "", - address: `Error: ${error instanceof Error ? error.message : "Failed to process"}`, - }; - } - finally { - if (stagehand) { - try { - await stagehand.close(); - console.log(`[${companyName}] Session closed successfully`); - } - catch (closeError) { - console.error(`[${companyName}] Error closing browser:`, closeError); - } - } - } -} -// Main orchestration function: processes companies sequentially or in batches based on MAX_CONCURRENT -// Collects results and outputs final JSON summary -async function main() { - console.log("Starting Company Address Finder..."); - const companyNames = COMPANY_NAMES; - const maxConcurrent = Math.max(1, MAX_CONCURRENT || 1); - const companyCount = companyNames.length; - const isSequential = maxConcurrent === 1; - console.log(`\nProcessing ${companyCount} ${companyCount === 1 ? "company" : "companies"} ${isSequential ? "sequentially" : `concurrently (batch size: ${maxConcurrent})`}...`); - const allResults = []; - if (isSequential) { - for (let i = 0; i < companyNames.length; i++) { - const companyName = companyNames[i]; - console.log(`[${i + 1}/${companyNames.length}] ${companyName}`); - const result = await processCompany(companyName); - allResults.push(result); - } - } - else { - for (let i = 0; i < companyNames.length; i += maxConcurrent) { - const batch = companyNames.slice(i, i + maxConcurrent); - const batchNumber = Math.floor(i / maxConcurrent) + 1; - const totalBatches = Math.ceil(companyNames.length / maxConcurrent); - console.log(`\nBatch ${batchNumber}/${totalBatches}: ${batch.join(", ")}`); - const batchPromises = batch.map((companyName) => processCompany(companyName)); - const batchResults = await Promise.all(batchPromises); - allResults.push(...batchResults); - console.log(`Batch ${batchNumber}/${totalBatches} completed: ${batchResults.length} companies processed`); - } - } - console.log("\n" + "=".repeat(80)); - console.log("RESULTS (JSON):"); - console.log("=".repeat(80)); - console.log(JSON.stringify(allResults, null, 2)); - console.log("=".repeat(80)); - console.log(`\nComplete: processed ${allResults.length}/${companyNames.length} companies`); -} -main().catch((err) => { - console.error("Application error:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify GOOGLE_GENERATIVE_AI_API_KEY is set"); - console.error(" - Ensure COMPANY_NAMES is configured in the config section"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/company-value-prop-generator/README.md b/javascript/company-value-prop-generator/README.md deleted file mode 100644 index e14876b0..00000000 --- a/javascript/company-value-prop-generator/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Stagehand + Browserbase: Value Prop One-Liner Generator - -## AT A GLANCE - -- Goal: Automatically extract and format website value propositions into concise one-liners for email personalization -- Demonstrates Stagehand's `extract` method with Zod schemas to pull structured data from landing pages -- Shows direct LLM API usage via `stagehand.llmClient` to transform extracted content with custom prompts -- Includes placeholder page detection and validation logic to filter out non-functional sites -- Docs → https://docs.stagehand.dev/v3/basics/extract - -## GLOSSARY - -- Extract: Stagehand method that uses AI to pull structured data from pages using natural language instructions - Docs → https://docs.stagehand.dev/v3/basics/extract -- Value Proposition: The core benefit or unique selling point a company communicates to customers - -## QUICKSTART - -1. cd typescript/company-value-prop-generator -2. npm install -3. cp .env.example .env -4. Add your Browserbase API key to .env -5. npm start - -## EXPECTED OUTPUT - -- Stagehand initializes and creates a Browserbase session -- Displays live session link for monitoring -- Navigates to target domain and waits for page load -- Checks for placeholder pages via meta tag inspection -- Extracts value proposition from landing page using AI -- Validates extracted content against placeholder patterns -- Generates formatted one-liner via LLM (constraints: 9 words max, starts with "your") -- Prints generated one-liner to console -- Closes browser session - -## COMMON PITFALLS - -- Dependency install errors: ensure npm install completed -- Missing credentials: - - BROWSERBASE_API_KEY (required for browser automation) -- Placeholder pages: Template includes detection logic, but some custom placeholder pages may still pass validation -- Slow-loading sites: 5-minute timeout configured, but extremely slow sites may still timeout - -## USE CASES - -• Generate personalized email openers by extracting value props from prospect domains -• Build prospecting tools that automatically understand what companies do from their websites -• Create dynamic messaging systems that adapt content based on extracted company information - -## NEXT STEPS - -• Batch process multiple domains by iterating over a list and aggregating results -• Extract additional metadata like company description, industry tags, or key features alongside value prop -• Add caching layer to avoid re-extracting value props for previously analyzed domains - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/company-value-prop-generator/index.js b/javascript/company-value-prop-generator/index.js deleted file mode 100644 index ebbdf984..00000000 --- a/javascript/company-value-prop-generator/index.js +++ /dev/null @@ -1,119 +0,0 @@ -// Stagehand + Browserbase: Value Prop One-Liner Generator - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// Domain to analyze - change this to target a different website -const targetDomain = "www.browserbase.com"; // Or extract from email: email.split("@")[1] -/** - * Analyzes a website's landing page to generate a concise one-liner value proposition. - * Extracts the value prop using Stagehand, then uses an LLM to format it into a short phrase starting with "your". - */ -async function generateOneLiner(domain) { - const stagehand = new Stagehand({ - env: "BROWSERBASE", - model: "openai/gpt-4.1", - verbose: 0, // 0 = errors only, 1 = info, 2 = debug - }); - try { - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to domain - console.log(`🌐 Navigating to https://${domain}...`); - // 5min timeout to handle slow-loading sites or network issues - await page.goto(`https://${domain}/`, { - waitUntil: "domcontentloaded", - timeoutMs: 300000, - }); - console.log(`✅ Successfully loaded ${domain}`); - // Extract value proposition from landing page - console.log(`📝 Extracting value proposition for ${domain}...`); - const valueProp = await stagehand.extract("extract the value proposition from the landing page", z.object({ - value_prop: z.string(), - })); - console.log(`📊 Extracted value prop for ${domain}:`, valueProp.value_prop); - // Validate extraction returned meaningful content - if (!valueProp.value_prop || - valueProp.value_prop.toLowerCase() === "null" || - valueProp.value_prop.toLowerCase() === "undefined") { - console.error(`⚠️ Value prop extraction returned empty or invalid result`); - throw new Error(`No value prop found for ${domain}`); - } - // Generate one-liner using OpenAI - // Prompt uses few-shot examples to guide LLM toward concise, "your X" format - // System prompt enforces constraints (9 words max, no quotes, must start with "your") - console.log(`🤖 Generating email one-liner for ${domain}...`); - const response = await stagehand.llmClient.createChatCompletion({ - logger: () => { }, // Suppress verbose LLM logs - options: { - messages: [ - { - role: "system", - content: "You are an expert at generating concise, unique descriptions of companies. Generate ONLY a concise description (no greetings or extra text). Don't use generic adjectives like 'comprehensive', 'innovative', or 'powerful'. Keep it short and concise, no more than 9 words. DO NOT USE QUOTES. Only use English. You MUST start the response with 'your'.", - }, - { - role: "user", - content: `The response will be inserted into this template: "{response}" - -Examples: -Value prop: "Supercharge your investment team with AI-powered research" -Response: "your AI-powered investment research platform" - -Value prop: "The video-first food delivery app" -Response: "your video-first approach to food delivery" - -Value prop: "${valueProp.value_prop}" -Response:`, - }, - ], - }, - }); - const oneLiner = String(response.choices?.[0]?.message?.content || "").trim(); - // Validate LLM response is usable (not empty, not generic placeholder) - console.log(`🔍 Validating generated one-liner...`); - if (!oneLiner || - oneLiner.toLowerCase() === "null" || - oneLiner.toLowerCase() === "undefined" || - oneLiner.toLowerCase() === "your company") { - console.error(`⚠️ LLM generated invalid or placeholder response: "${oneLiner}"`); - throw new Error(`No valid one-liner generated for ${domain}. AI response: "${oneLiner}"`); - } - console.log(`✨ Generated one-liner for ${domain}:`, oneLiner); - return oneLiner; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error(`❌ Generation failed for ${domain}: ${errorMessage}`); - throw error; - } - finally { - await stagehand.close(); - console.log("Session closed successfully"); - } -} -/** - * Main entry point: generates a one-liner value proposition for the target domain. - */ -async function main() { - console.log("Starting One-Liner Generator..."); - try { - const oneLiner = await generateOneLiner(targetDomain); - console.log("\n✅ Success!"); - console.log(`One-liner: ${oneLiner}`); - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error(`\n❌ Error: ${errorMessage}`); - console.error("\nCommon issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY set (required for browser automation)"); - console.error(" - Ensure the domain is accessible and not a placeholder/maintenance page"); - console.error(" - Verify internet connectivity and that the target site is reachable"); - console.error("Docs: https://docs.browserbase.com/stagehand"); - process.exit(1); - } -} -main().catch((err) => { - console.error("Fatal error:", err); - process.exit(1); -}); diff --git a/javascript/context/README.md b/javascript/context/README.md deleted file mode 100644 index 47286cb1..00000000 --- a/javascript/context/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Stagehand + Browserbase: Context Authentication Example - -## AT A GLANCE - -- Goal: demonstrate persistent authentication using Browserbase **contexts** that survive across sessions. -- Flow: create context → log in once → persist cookies/tokens → reuse context in a new session → extract data → clean up. -- Benefits: skip re-auth on subsequent runs, reduce MFA prompts, speed up protected flows, and keep state stable across retries. - Docs → https://docs.browserbase.com/features/contexts - -## GLOSSARY - -- context: a persistent browser state (cookies, localStorage, cache) stored server-side and reusable by new sessions. - Docs → https://docs.browserbase.com/features/contexts -- persist: when true, any state changes during a session are written back to the context for future reuse. -- act: perform UI actions from a prompt (click, type, navigate). - Docs → https://docs.stagehand.dev/basics/act - -## QUICKSTART - -1. cd context-template -2. npm install -3. npm install axios -4. cp .env.example .env -5. Add your Browserbase API key, Project ID, and SF Rec Park credentials to .env -6. npm start - -## EXPECTED OUTPUT - -- Creates context, performs login, saves auth state -- Reuses context in new session to access authenticated pages -- Extracts user data using structured schemas -- Cleans up context after completion - -## COMMON PITFALLS - -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains all required variables -- Context persistence: ensure persist: true is set to save login state - -## USE CASES - -• Persistent login sessions: Automate workflows that require authentication without re-logging in every run. -• Access to gated content: Crawl or extract data from behind login walls (e.g., booking portals, dashboards, intranets). -• Multi-step workflows: Maintain cookies/tokens across different automation steps or scheduled jobs. - -## NEXT STEPS - -• Extend to multiple apps: Reuse the same context across different authenticated websites within one session. -• Add session validation: Extract and verify account info (e.g., username, profile details) to confirm successful auth. -• Secure lifecycle: Rotate, refresh, and delete contexts programmatically to enforce security policies. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/context/index.js b/javascript/context/index.js deleted file mode 100644 index 87acaf59..00000000 --- a/javascript/context/index.js +++ /dev/null @@ -1,128 +0,0 @@ -// Stagehand + Browserbase: Context Authentication Example - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { Browserbase } from "@browserbasehq/sdk"; -import { z } from "zod"; -import axios from "axios"; -async function createSessionContextID() { - console.log("Creating new Browserbase context..."); - // First create a context using Browserbase SDK to get a context ID. - const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY }); - const context = await bb.contexts.create(); - console.log("Created context ID:", context.id); - // Create a single session using the context ID to perform initial login. - console.log("Creating session for initial login..."); - const session = await bb.sessions.create({ - browserSettings: { - context: { - id: context.id, - persist: true, // Save authentication state to context - }, - }, - }); - console.log("Live view: https://browserbase.com/sessions/" + session.id); - // Connect Stagehand to the existing session (no new session created). - console.log("Connecting Stagehand to session..."); - const stagehand = new Stagehand({ - env: "BROWSERBASE", - model: "openai/gpt-4.1", - verbose: 1, - browserbaseSessionID: session.id, - }); - await stagehand.init(); // Connect to existing session for login process. - const page = stagehand.context.pages()[0]; - const email = process.env.SF_REC_PARK_EMAIL; - const password = process.env.SF_REC_PARK_PASSWORD; - // Navigate to login page with extended timeout for slow-loading sites. - console.log("Navigating to SF Rec & Park login page..."); - await page.goto("https://www.rec.us/organizations/san-francisco-rec-park", { - waitUntil: "domcontentloaded", - timeout: 60000, - }); - // Perform login sequence: each step is atomic to handle dynamic page changes. - console.log("Starting login sequence..."); - await page.act("Click the Login button"); - await page.act(`Fill in the email or username field with "${email}"`); - await page.act("Click the next, continue, or submit button to proceed"); - await page.act(`Fill in the password field with "${password}"`); - await page.act("Click the login, sign in, or submit button"); - console.log("Login sequence completed!"); - await stagehand.close(); - console.log("Authentication state saved to context"); - // Return the context ID for reuse in future sessions. - return { id: context.id }; -} -async function deleteContext(contextId) { - try { - console.log("Cleaning up context:", contextId); - // Delete context via Browserbase API to clean up stored authentication data. - // This prevents accumulation of unused contexts and ensures security cleanup. - const response = await axios.delete(`https://api.browserbase.com/v1/contexts/${contextId}`, { - headers: { - "X-BB-API-Key": process.env.BROWSERBASE_API_KEY, - }, - }); - console.log("Context deleted successfully (status:", response.status + ")"); - } - catch (error) { - const err = error; - console.error("Error deleting context:", err.response?.data || err.message || error); - } -} -async function main() { - console.log("Starting Context Authentication Example..."); - // Create context with login state for reuse in authenticated sessions. - const contextId = await createSessionContextID(); - // Initialize new session using existing context to inherit authentication state. - // persist: true ensures any new changes (cookies, cache) are saved back to context. - const stagehand = new Stagehand({ - env: "BROWSERBASE", - model: "openai/gpt-4.1", - verbose: 1, - browserbaseSessionCreateParams: { - browserSettings: { - context: { - id: contextId.id, - persist: true, - }, - }, - }, - }); - await stagehand.init(); // Creates session with inherited login state from context. - console.log("Authenticated session ready!"); - console.log("Live view: https://browserbase.com/sessions/" + stagehand.browserbaseSessionID); - const page = stagehand.context.pages()[0]; - // Navigate to authenticated area - should skip login due to persisted cookies. - console.log("Navigating to authenticated area (should skip login)..."); - await page.goto("https://www.rec.us/organizations/san-francisco-rec-park", { - waitUntil: "domcontentloaded", - timeout: 60000, - }); - // Navigate to user-specific area to access personal data. - await page.act("Click on the reservations button"); - // Extract structured user data using Zod schema for type safety. - // Schema ensures consistent data format and validates extracted content. - console.log("Extracting user profile data..."); - const userData = await page.extract({ - instruction: "Extract the user's full name and address", - schema: z.object({ - fullName: z.string().describe("the user's full name"), - address: z.string().describe("the user's address"), - }), - }); - console.log("Extracted user data:", userData); - // Always close session to release resources and save any context changes. - await stagehand.close(); - console.log("Session closed successfully"); - // Clean up context to prevent accumulation and ensure security. - await deleteContext(contextId.id); -} -main().catch((err) => { - console.error("Error in context authentication example:", err); - console.error("Common issues:"); - console.error(" - Check .env file has SF_REC_PARK_EMAIL and SF_REC_PARK_PASSWORD"); - console.error(" - Verify BROWSERBASE_API_KEY is set"); - console.error(" - Ensure credentials are valid for SF Rec & Park"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/council-events/README.md b/javascript/council-events/README.md deleted file mode 100644 index 1393eb86..00000000 --- a/javascript/council-events/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Stagehand + Browserbase: Council Events Automation - -## AT A GLANCE - -- Goal: demonstrate how to automate event information extraction from Philadelphia Council. -- Navigation & Search: automate website navigation, calendar selection, and year filtering. -- Data Extraction: extract structured event data with validated output using Zod schemas. -- Practical Example: extract council events with name, date, and time information. - -## GLOSSARY - -- act: perform UI actions from a natural language prompt (type, click, navigate). - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from web pages into validated objects. - Docs → https://docs.stagehand.dev/basics/extract -- schema: a Zod definition that enforces data types, optional fields, and validation rules. - Docs → https://zod.dev/ -- council events automation: navigate to council website, select calendar, and extract event information. -- structured data extraction: convert unstructured web content into typed, validated objects. - -## QUICKSTART - -1. cd councilEvents -2. npm install -3. cp .env.example .env (or create .env with BROWSERBASE_API_KEY) -4. Add your Browserbase API key to .env -5. npm start - -## EXPECTED OUTPUT - -- Navigates to Philadelphia Council website -- Clicks calendar from the navigation menu -- Selects 2025 from the month dropdown -- Extracts structured event data including name, date, and time -- Returns typed object with event information - -## COMMON PITFALLS - -- "Cannot find module 'dotenv'": ensure npm install ran successfully -- Missing API key: verify .env is loaded and file is not committed -- Events not found: check if the website structure has changed or if no events exist for the selected period -- Schema validation errors: ensure extracted data matches Zod schema structure - -## USE CASES - -• Event tracking: automate monitoring of council meetings and public events. -• Calendar aggregation: collect event information for integration with calendar systems. -• Public information access: extract structured event data for citizens and researchers. -• Meeting scheduling: track upcoming council meetings for attendance planning. - -## NEXT STEPS - -• Date filtering: make the year or date selection configurable via environment variables or prompts. -• Multi-year extraction: extend the flow to extract events from multiple years in parallel. -• Calendar integration: add logic to export extracted events to calendar formats (iCal, Google Calendar). -• Event notifications: add logic to send alerts for upcoming meetings or important events. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/council-events/index.js b/javascript/council-events/index.js deleted file mode 100644 index 8cfd260b..00000000 --- a/javascript/council-events/index.js +++ /dev/null @@ -1,71 +0,0 @@ -// Stagehand + Browserbase: Philadelphia Council Events Scraper - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -/** - * Searches Philadelphia Council Events for 2025 and extracts event information. - * Uses AI-powered browser automation to navigate and interact with the site. - */ -async function main() { - console.log("Starting Philadelphia Council Events automation..."); - // Initialize Stagehand with Browserbase for cloud-based browser automation - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1", - }); - try { - // Initialize browser session - console.log("Initializing browser session..."); - await stagehand.init(); - console.log("Stagehand session started successfully"); - // Provide live session URL for debugging and monitoring - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Navigate to Philadelphia Council - console.log("Navigating to: https://phila.legistar.com/"); - await page.goto("https://phila.legistar.com/"); - console.log("Page loaded successfully"); - // Click calendar from the navigation menu - console.log("Clicking calendar from the navigation menu"); - await stagehand.act("click calendar from the navigation menu"); - // Select 2025 from the month dropdown - console.log("Selecting 2025 from the month dropdown"); - await stagehand.act("select 2025 from the month dropdown"); - // Extract event data using AI to parse the structured information - console.log("Extracting event information..."); - const results = await stagehand.extract("Extract the table with the name, date and time of the events", z.object({ - results: z.array(z.object({ - name: z.string(), - date: z.string(), - time: z.string(), - })), - })); - console.log(`Found ${results.results.length} events`); - console.log("Event data extracted successfully:"); - console.log(JSON.stringify(results, null, 2)); - } - catch (error) { - console.error("Error during event extraction:", error); - // Provide helpful troubleshooting information - console.error("\nCommon issues:"); - console.error("1. Check .env file has BROWSERBASE_API_KEY"); - console.error("2. Ensure internet access and https://phila.legistar.com is accessible"); - console.error("3. Verify Browserbase account has sufficient credits"); - console.error("4. Check if the calendar page structure has changed"); - throw error; - } - finally { - // Clean up browser session - console.log("Closing browser session..."); - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Application error:", err); - process.exit(1); -}); diff --git a/javascript/download-financial-statements/README.md b/javascript/download-financial-statements/README.md deleted file mode 100644 index e28b674b..00000000 --- a/javascript/download-financial-statements/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Stagehand + Browserbase: Download Apple's Quarterly Financial Statements - -## AT A GLANCE - -- Goal: automate downloading Apple's quarterly financial statements (PDFs) from their investor relations site. -- Download Handling: Browserbase automatically captures PDFs opened during the session and bundles them into a ZIP file. -- Retry Logic: polls Browserbase downloads API with configurable timeout to ensure files are ready before retrieval. -- Live Debugging: displays live view URL for real-time session monitoring. - -## GLOSSARY - -- act: perform UI actions from a prompt (click, scroll, navigate) - Docs → https://docs.stagehand.dev/basics/act -- downloads API: retrieve files downloaded during a Browserbase session as a ZIP archive - Docs → https://docs.browserbase.com/features/screenshots#pdfs -- live view: real-time browser debugging interface for monitoring automation - Docs → https://docs.browserbase.com/features/session-live-view - -## QUICKSTART - -1. cd download-financial-statements -2. npm install -3. cp .env.example .env -4. Add your Browserbase API key to .env -5. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Navigates to Apple.com → Investors section -- Locates Q1-Q4 2025 quarterly earnings reports -- Clicks each Financial Statements PDF link (triggers downloads) -- Polls Browserbase API until downloads are ready -- Saves all PDFs as `downloaded_files.zip` in current directory -- Displays session history and closes cleanly - -## COMMON PITFALLS - -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Download timeout: increase `retryForSeconds` parameter if downloads take longer than 45 seconds -- Empty ZIP file: ensure PDFs were actually triggered (check live view link to debug) -- Network issues: check internet connection and Apple website accessibility - -## USE CASES - -• Financial reporting automation: Download quarterly/annual reports from investor relations sites for analysis, archiving, or compliance. -• Document batch retrieval: Collect multiple PDFs (contracts, invoices, statements) from web portals without manual clicking. -• Scheduled data collection: Run on cron/Lambda to automatically fetch latest financial filings or regulatory documents. - -## NEXT STEPS - -• Generalize for other sites: Extract URL patterns, adapt act() prompts, and support multiple companies/document types. -• Parse downloaded PDFs: Unzip, OCR/parse text (PyPDF2/pdfplumber), and load into structured format (CSV/DB/JSON). -• Add validation: Check file count, sizes, naming conventions; alert on failures; retry missing quarters. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/download-financial-statements/index.js b/javascript/download-financial-statements/index.js deleted file mode 100644 index b337ac9e..00000000 --- a/javascript/download-financial-statements/index.js +++ /dev/null @@ -1,122 +0,0 @@ -// Stagehand + Browserbase: Download Apple's Quarterly Financial Statements - See README.md for full documentation -import { Browserbase } from "@browserbasehq/sdk"; -import { Stagehand } from "@browserbasehq/stagehand"; -import "dotenv/config"; -import fs from "fs"; -/** - * Polls Browserbase API for downloads with timeout handling. - * Retries every 2 seconds until downloads are ready or timeout is reached. - */ -async function saveDownloadsWithRetry(bb, sessionId, retryForSeconds = 30) { - return new Promise((resolve, reject) => { - console.log(`Waiting up to ${retryForSeconds} seconds for downloads to complete...`); - const intervals = { - poller: undefined, - timeout: undefined, - }; - async function fetchDownloads() { - try { - console.log("Checking for downloads..."); - const response = await bb.sessions.downloads.list(sessionId); - const downloadBuffer = await response.arrayBuffer(); - if (downloadBuffer.byteLength > 0) { - console.log(`Downloads ready! File size: ${downloadBuffer.byteLength} bytes`); - fs.writeFileSync("downloaded_files.zip", Buffer.from(downloadBuffer)); - console.log("Files saved as: downloaded_files.zip"); - if (intervals.poller) - clearInterval(intervals.poller); - if (intervals.timeout) - clearTimeout(intervals.timeout); - resolve(downloadBuffer.byteLength); - } - else { - console.log("Downloads not ready yet, retrying..."); - } - } - catch (e) { - console.error("Error fetching downloads:", e); - if (intervals.poller) - clearInterval(intervals.poller); - if (intervals.timeout) - clearTimeout(intervals.timeout); - reject(e); - } - } - // Set timeout to prevent infinite polling if downloads never complete - intervals.timeout = setTimeout(() => { - if (intervals.poller) { - clearInterval(intervals.poller); - } - reject(new Error("Download timeout exceeded")); - }, retryForSeconds * 1000); - // Poll every 2 seconds to check if downloads are ready - intervals.poller = setInterval(fetchDownloads, 2000); - }); -} -async function main() { - console.log("Starting Apple Financial Statements Download Automation..."); - console.log("Initializing Browserbase client..."); - const bb = new Browserbase({ - apiKey: process.env.BROWSERBASE_API_KEY, - }); - // Initialize Stagehand with Browserbase for cloud-based browser automation - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - logger: console.log, - disablePino: true, - }); - try { - // Initialize browser session to start automation - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - const context = stagehand.context; - const page = context.pages()[0]; - // Display live view URL for debugging and monitoring - const liveViewLinks = await bb.sessions.debug(stagehand.browserbaseSessionId); - console.log(`Live View Link: ${liveViewLinks.debuggerFullscreenUrl}`); - // Navigate to Apple homepage with extended timeout for slow-loading sites - console.log("Navigating to Apple.com..."); - await page.goto("https://www.apple.com/", { timeoutMs: 60000 }); - // Navigate to investor relations section - console.log("Navigating to Investors section..."); - await stagehand.act("Click the 'Investors' button at the bottom of the page'"); - await stagehand.act("Scroll down to the Financial Data section of the page"); - await stagehand.act("Under Quarterly Earnings Reports, click on '2025'"); - // Download all quarterly financial statements - // When a URL of a PDF is opened, Browserbase automatically downloads and stores the PDF - // See https://docs.browserbase.com/features/screenshots#pdfs for more info - console.log("Downloading quarterly financial statements..."); - await stagehand.act("Click the 'Financial Statements' link under Q4"); - await stagehand.act("Click the 'Financial Statements' link under Q3"); - await stagehand.act("Click the 'Financial Statements' link under Q2"); - await stagehand.act("Click the 'Financial Statements' link under Q1"); - // Retrieve all downloads triggered during this session from Browserbase API - console.log("Retrieving downloads from Browserbase..."); - await saveDownloadsWithRetry(bb, stagehand.browserbaseSessionId, 45); - console.log("All downloads completed successfully!"); - console.log("\nStagehand History:"); - console.log(stagehand.history); - } - catch (error) { - console.error("Error during automation:", error); - throw error; - } - finally { - // Always close session to release resources and clean up - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Application error:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify internet connection and Apple website accessibility"); - console.error(" - Ensure sufficient timeout for slow-loading pages"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/dynamic-form-filling/README.md b/javascript/dynamic-form-filling/README.md deleted file mode 100644 index 4d988fcb..00000000 --- a/javascript/dynamic-form-filling/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Stagehand + Browserbase: Dynamic Form Filling with Agent - -## AT A GLANCE - -- Goal: Automate intelligent form filling using an Stagehand AI agent that understands form context and uses semantic matching. -- Agent-Powered: Uses Stagehand Agent to autonomously fill forms by extracting information from natural language descriptions. -- Semantic Matching: Agent intelligently selects form options even when exact wording doesn't match, choosing the closest semantic match. -- Custom Instructions: Demonstrates how to configure agent behavior with system prompts for reliable form completion. -- Docs → https://docs.stagehand.dev/basics/agent - -## GLOSSARY - -- agent: create an autonomous AI agent that can execute complex multi-step tasks - Docs → https://docs.stagehand.dev/basics/agent#what-is-agent -- semantic matching: selecting form options based on meaning rather than exact text match -- system prompt: custom instructions that guide agent behavior and decision-making - Docs → https://docs.stagehand.dev/basics/agent#using-agent - -## QUICKSTART - -1. pnpm install -2. cp .env.example .env -3. Add your Browserbase API key to .env (BROWSERBASE_API_KEY) -4. Customize the `tripDetails` variable in index.ts with your own form data -5. Update the form URL if using a different form -6. pnpm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Displays live session link for monitoring -- Navigates to the target form -- Agent analyzes form structure and available fields -- Agent extracts relevant information from trip details -- Agent fills form fields using semantic matching for dropdowns/checkboxes -- Agent submits the form when complete -- Outputs success status and agent message -- Closes session cleanly - -## COMMON PITFALLS - -- Dependency install errors: ensure pnpm install completed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Agent stopping early: increase maxSteps (default 30) for complex forms with many fields -- Form not submitting: verify the form URL is accessible and form fields are visible -- Semantic matching issues: adjust system prompt to better guide agent's matching behavior -- Find more information on your Browserbase dashboard -> https://www.browserbase.com/sign-in - -## USE CASES - -• Dynamic form automation: Fill out forms with variable data from natural language descriptions without hardcoding field mappings. -• Survey and questionnaire automation: Automatically complete surveys, feedback forms, or registration forms with intelligent option selection. -• Multi-step form workflows: Handle complex multi-page forms where the agent navigates between steps and maintains context. -• Form testing and validation: Test form behavior with different data sets to ensure proper validation and error handling. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/dynamic-form-filling/index.js b/javascript/dynamic-form-filling/index.js deleted file mode 100644 index 89d5c40e..00000000 --- a/javascript/dynamic-form-filling/index.js +++ /dev/null @@ -1,74 +0,0 @@ -// Dynamic Form Filling with Agent - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -// Trip details to be used for form filling -const tripDetails = `I'm planning a Summer in Japan. We're going to Tokyo, Kyoto, and Osaka (Japan) for 14 days. There will be 2 of us, and our budget is around $3,500 USD. We have a couple of dietary needs: vegetarian, and no shellfish. For activities, we'd love food tours, historical sites and temples, nature/scenic walks, local markets, and generally an itinerary that's easy to do with public transit. For accommodation, we prefer mid-range hotels or a traditional ryokan. We like a relaxed pace, with maybe a few busier days mixed in. It's our first time in Japan, and we'd love help balancing must-see attractions with less touristy experiences, plus recommendations for vegetarian-friendly restaurants.`; -async function main() { - // Initialize Stagehand with Browserbase for cloud-based browser automation. - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log(`Stagehand Session Started`); - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to the trip example form. - console.log("Navigating to form..."); - await page.goto("https://forms.gle/DVX84XynAJwUWNu26"); - // Create agent with custom system prompt for intelligent form filling. - // The agent will use semantic matching to select appropriate form options. - const agent = stagehand.agent({ - cua: false, - model: "google/gemini-2.5-pro", // Routed through Model Gateway - systemPrompt: `You are filling out a trip planning form. - - When filling out fields, extract relevant information from the trip details provided - - For fields with options (radio buttons, dropdowns, checkboxes), always choose the closest matching option from the available choices - - Use semantic matching - look for options that convey similar meaning even if the exact wording differs - - Only select "Other" if no other option reasonably matches the trip details - - For checkbox fields, select all options that semantically match the trip details`, - }); - // Instruction for the agent to fill out the form with trip details. - const instruction = `Fill out this form with the following trip details: ${tripDetails} - -Make sure to: -- Fill in all required fields -- When an exact match isn't available, choose the closest matching option from the available choices -- Use semantic matching to find the best option - look for options that convey similar meaning even if the wording differs -- Only select "Other" if no other option reasonably matches the trip details -- Extract relevant information from the trip details (duration, accommodation preferences, activities, dietary needs, etc.) and map them to the form fields -- IMPORTANT: Once all fields are filled out, you must click the submit button to complete the form submission`; - // Execute agent to autonomously fill out the form based on details. - console.log("\nFilling out the form with agent..."); - const result = await agent.execute({ - instruction, - maxSteps: 30, - }); - if (result.success) { - console.log("Form filled successfully!"); - console.log("Agent message:", result.message); - } - else { - console.log("Form filling may be incomplete"); - console.log("Agent message:", result.message); - } - } - catch (error) { - console.error("Error during form filling:", error); - } - finally { - // Always close session to release resources and clean up. - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in dynamic form filling:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Ensure the form URL is accessible and form fields are available"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/form-filling/README.md b/javascript/form-filling/README.md deleted file mode 100644 index 3080163c..00000000 --- a/javascript/form-filling/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Stagehand + Browserbase: Form Filling Automation - -## AT A GLANCE - -- Goal: showcase how to automate form filling with Stagehand and Browserbase. -- Smart Form Automation: dynamically fill contact forms with variable-driven data. -- Field Detection: analyze page structure with `observe` before interacting with fields. -- AI-Powered Interaction: leverage Stagehand to map inputs to the right fields reliably. - Docs → https://docs.browserbase.com/fundamentals/create-browser-session - -## GLOSSARY - -- act: perform UI actions from a prompt (type, click, fill forms) - Docs → https://docs.stagehand.dev/basics/act -- observe: analyze a page and return selectors or action plans before executing - Docs → https://docs.stagehand.dev/basics/observe -- variable substitution: inject dynamic values into actions using `%variable%` syntax - -## QUICKSTART - -1. cd form-fill-template -2. npm install -3. cp .env.example .env -4. Add your Browserbase API key and Project ID to .env -5. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Navigates to contact form page -- Analyzes available form fields using observe -- Fills form with sample data using variable substitution -- Displays session recording link for monitoring -- Closes session cleanly - -## COMMON PITFALLS - -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains all required API keys -- Form detection: ensure target page has fillable form fields -- Variable mismatch: ensure variable names in action match variables object -- Network issues: check internet connection and website accessibility - -## USE CASES - -• Lead & intake automation: Auto-fill contact/quote/request forms from CRM or CSV to speed up inbound/outbound workflows. -• QA & regression testing: Validate form fields, required rules, and error states across releases/environments. -• Bulk registrations & surveys: Programmatically complete repeatable sign-ups or survey passes for pilots and internal ops. - -## NEXT STEPS - -• Wire in data sources: Load variables from CSV/JSON/CRM, map fields via observe, and support per-site field aliases. -• Submit & verify: Enable submit, capture success toasts/emails, take screenshots, and retry on validation errors. -• Handle complex widgets: Add file uploads, multi-step flows, dropdown/radio/datepickers, and basic anti-bot tactics (delays/proxies). - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/form-filling/index.js b/javascript/form-filling/index.js deleted file mode 100644 index 2a6b778d..00000000 --- a/javascript/form-filling/index.js +++ /dev/null @@ -1,84 +0,0 @@ -// Stagehand + Browserbase: Form Filling Automation - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -// Form data variables - using random/fake data for testing -// Set your own variables below to customize the form submission -const firstName = "Alex"; -const lastName = "Johnson"; -const company = "TechCorp Solutions"; -const jobTitle = "Software Developer"; -const email = "alex.johnson@techcorp.com"; -const message = "Hello, I'm interested in learning more about your services and would like to schedule a demo."; -async function main() { - console.log("Starting Form Filling Example..."); - // Initialize Stagehand with Browserbase for cloud-based browser automation. - const stagehand = new Stagehand({ - env: "BROWSERBASE", - model: "openai/gpt-4.1", - verbose: 1, - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Navigate to contact page with extended timeout for slow-loading sites. - console.log("Navigating to Browserbase contact page..."); - await page.goto("https://www.browserbase.com/contact", { - waitUntil: "domcontentloaded", // Wait for DOM to be ready before proceeding. - timeout: 60000, // Extended timeout for reliable page loading. - }); - // Single observe call to plan all form filling - const formFields = await stagehand.observe("Find form fields for: first name, last name, company, job title, email, message"); - // Execute all actions without LLM calls - for (const field of formFields) { - // Match field to data based on description - let value = ""; - const desc = field.description.toLowerCase(); - if (desc.includes("first name")) - value = firstName; - else if (desc.includes("last name")) - value = lastName; - else if (desc.includes("company")) - value = company; - else if (desc.includes("job title")) - value = jobTitle; - else if (desc.includes("email")) - value = email; - else if (desc.includes("message")) - value = message; - if (value) { - await stagehand.act({ - ...field, - arguments: [value], - }); - } - } - // Language choice in Stagehand act() is crucial for reliable automation. - // Use "click" for dropdown interactions rather than "select" - await stagehand.act("Click on the How Can we help? dropdown"); - await stagehand.act("Click on the first option from the dropdown"); - // await stagehand.act("Select the first option from the dropdown"); // Less reliable than "click" - // Uncomment the line below if you want to submit the form - // await stagehand.act("Click the submit button"); - console.log("Form filled successfully! Waiting 3 seconds..."); - await page.waitForTimeout(30000); - } - catch (error) { - console.error(`Error during form filling: ${error}`); - } - finally { - // Always close session to release resources and clean up. - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in form filling example:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Ensure form fields are available on the contact page"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/gemini-3-flash/README.md b/javascript/gemini-3-flash/README.md deleted file mode 100644 index 9bdd32ca..00000000 --- a/javascript/gemini-3-flash/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Stagehand + Browserbase: Gemini 3 Flash Agent Example - -## AT A GLANCE - -- Goal: demonstrate autonomous web browsing using Google's Gemini 3 Flash with Stagehand and Browserbase. -- Uses Stagehand Agent to automate complex workflows with AI powered browser agents. -- Leverages Gemini 3 Flash model for autonomous web interaction and decision-making. - -## GLOSSARY - -- agent: create an autonomous AI agent that can execute complex multi-step tasks - Docs → https://docs.stagehand.dev/basics/agent#what-is-agent - -## QUICKSTART - -1. npm install -2. cp .env.example .env -3. Add your Browserbase API key to .env -4. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Navigates to Google search engine -- Executes autonomous search and data extraction task -- Displays live session link for monitoring -- Returns structured results or completion status -- Closes session cleanly - -## COMMON PITFALLS - -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY - -## USE CASES - -• Autonomous research: Let AI agents independently research topics, gather information, and compile reports without manual intervention. -• Complex web workflows: Automate multi-step processes that require decision-making, form filling, and data extraction across multiple pages. -• Content discovery: Search for specific information, verify data accuracy, and cross-reference sources autonomously. - -## NEXT STEPS - -• Customize instructions: Modify the instruction variable to test different autonomous tasks and scenarios. -• Add error handling: Implement retry logic, fallback strategies, and better error recovery for failed agent actions. -• Extend capabilities: Add support for file downloads, form submissions, and more complex interaction patterns. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com diff --git a/javascript/gemini-3-flash/index.js b/javascript/gemini-3-flash/index.js deleted file mode 100644 index c5734e68..00000000 --- a/javascript/gemini-3-flash/index.js +++ /dev/null @@ -1,80 +0,0 @@ -// Stagehand + Browserbase: Gemini 3 Flash Example - See README.md for full documentation -import { Stagehand } from "@browserbasehq/stagehand"; -// ============================================================================ -// EXAMPLE INSTRUCTIONS - Choose one to test different scenarios -// ============================================================================ -// Example 1: Learning Plan Creation -// const instruction = `I want to learn more about Sourdough Bread Making. It's my first time learning about it, and want to get a good grasp by investing 1 hour a day for the next 2 months. Go find online courses/resources, create a plan cross-referencing the time I want to invest with the modules/timelines of the courses and return the plan`; -// Example 2: Flight Search -// const instruction = `Use flights.google.com to find the lowest fare from all eligible one-way flights for 1 adult from JFK to Heathrow in the next 30 days.`; -// Example 3: Solar Eclipse Research -const instruction = `Search for the next visible solar eclipse in North America and its expected date, and what about the one after that.`; -// Example 4: GitHub PR Verification -// const instruction = `Find the most recently opened non-draft PR on Github for Browserbase's Stagehand project and make sure the combination-evals in the PR validation passed.`; -// ============================================================================ -async function main() { - const stagehand = new Stagehand({ - env: "BROWSERBASE", - // model: "google/gemini-2.5-pro", // this is the model Stagehand uses for act, observe, extract (not agent) - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - browserbaseSessionCreateParams: { - proxies: true, // Using proxies will give the agent a better chance of success - requires Developer Plan or higher, comment out if you don't have access - region: "us-west-2", - browserSettings: { - blockAds: true, - viewport: { - width: 1288, - height: 711, - }, - }, - }, - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to search engine with extended timeout for slow-loading sites. - await page.goto("https://www.google.com/", { - waitUntil: "domcontentloaded", - }); - // Create agent with Gemini 3 Flash for autonomous web browsing. - const agent = stagehand.agent({ - model: "google/gemini-3-flash-preview", // Routed through Model Gateway - systemPrompt: `You are a helpful assistant that can use a web browser. - You are currently on the following page: ${page.url()}. - Do not ask follow up questions, the user will trust your judgement. If you are getting blocked on google, try another search engine.`, - }); - console.log("Executing instruction:", instruction); - const result = await agent.execute({ - instruction: instruction, - maxSteps: 30, - highlightCursor: true, - }); - if (result.success === true) { - console.log("Task completed successfully!"); - console.log("Result:", result); - } - else { - console.log("Task failed or was incomplete"); - } - } - catch (error) { - console.error("Error executing Gemini 3 Flash agent:", error); - } - finally { - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in Gemini 3 Flash agent example:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/gemini-cua/README.md b/javascript/gemini-cua/README.md deleted file mode 100644 index edec7b0c..00000000 --- a/javascript/gemini-cua/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Stagehand + Browserbase: Computer Use Agent (CUA) Example - -## AT A GLANCE - -- Goal: demonstrate autonomous web browsing using Google's Computer Use Agent with Stagehand and Browserbase. -- Uses Stagehand Agent to automate complex workflows with AI powered browser agents -- Leverages Google's computer-use-preview model for autonomous web interaction and decision-making. - -## GLOSSARY - -- agent: create an autonomous AI agent that can execute complex multi-step tasks - Docs → https://docs.stagehand.dev/basics/agent#what-is-agent - -## QUICKSTART - -1. npm install -2. cp .env.example .env -3. Add your Browserbase API key and Google API key to .env -4. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Navigates to Google search engine -- Executes autonomous search and data extraction task -- Displays live session link for monitoring -- Returns structured results or completion status -- Closes session cleanly - -## COMMON PITFALLS - -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY and GOOGLE_API_KEY -- Google API access: ensure you have access to Google's computer-use-preview model - -## USE CASES - -• Autonomous research: Let AI agents independently research topics, gather information, and compile reports without manual intervention. -• Complex web workflows: Automate multi-step processes that require decision-making, form filling, and data extraction across multiple pages. -• Content discovery: Search for specific information, verify data accuracy, and cross-reference sources autonomously. - -## NEXT STEPS - -• Customize instructions: Modify the instruction variable to test different autonomous tasks and scenarios. -• Add error handling: Implement retry logic, fallback strategies, and better error recovery for failed agent actions. -• Extend capabilities: Add support for file downloads, form submissions, and more complex interaction patterns. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/gemini-cua/index.js b/javascript/gemini-cua/index.js deleted file mode 100644 index dbc5977a..00000000 --- a/javascript/gemini-cua/index.js +++ /dev/null @@ -1,85 +0,0 @@ -// Stagehand + Browserbase: Computer Use Agent (CUA) Example - See README.md for full documentation -import { Stagehand } from "@browserbasehq/stagehand"; -// ============================================================================ -// EXAMPLE INSTRUCTIONS - Choose one to test different scenarios -// ============================================================================ -// Example 1: Learning Plan Creation -// const instruction = `I want to learn more about Sourdough Bread Making. It's my first time learning about it, and want to get a good grasp by investing 1 hour a day for the next 2 months. Go find online courses/resources, create a plan cross-referencing the time I want to invest with the modules/timelines of the courses and return the plan`; -// Example 2: Flight Search -// const instruction = `Use flights.google.com to find the lowest fare from all eligible one-way flights for 1 adult from JFK to Heathrow in the next 30 days.`; -// Example 3: Solar Eclipse Research -const instruction = `Search for the next visible solar eclipse in North America and its expected date, and what about the one after that.`; -// Example 4: GitHub PR Verification -// const instruction = `Find the most recently opened non-draft PR on Github for Browserbase's Stagehand project and make sure the combination-evals in the PR validation passed.`; -// ============================================================================ -async function main() { - const stagehand = new Stagehand({ - env: "BROWSERBASE", - // model: "google/gemini-2.5-pro", // this is the model stagehand uses in act, observe, extract (not agent) - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - browserbaseSessionCreateParams: { - proxies: true, // Using proxies will give the agent a better chance of success - requires Developer Plan or higher, comment out if you don't have access - region: "us-west-2", - browserSettings: { - blockAds: true, - viewport: { - width: 1288, - height: 711, - }, - }, - }, - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to search engine with extended timeout for slow-loading sites. - await page.goto("https://www.google.com/", { - waitUntil: "domcontentloaded", - }); - // Create agent with computer use capabilities for autonomous web browsing. - const agent = stagehand.agent({ - cua: true, - model: { - modelName: "google/gemini-2.5-computer-use-preview-10-2025", - apiKey: process.env.GOOGLE_API_KEY, - }, - systemPrompt: `You are a helpful assistant that can use a web browser. - You are currently on the following page: ${page.url()}. - Do not ask follow up questions, the user will trust your judgement. If you are getting blocked on google, try another search engine.`, - }); - console.log("Executing instruction:", instruction); - const result = await agent.execute({ - instruction: instruction, - maxSteps: 30, - highlightCursor: true, - }); - if (result.success === true) { - console.log("Task completed successfully!"); - console.log("Result:", result); - } - else { - console.log("Task failed or was incomplete"); - } - } - catch (error) { - console.error("Error executing computer use agent:", error); - } - finally { - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in computer use agent example:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify GOOGLE_API_KEY is set for the agent"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/gift-finder/README.md b/javascript/gift-finder/README.md deleted file mode 100644 index 9b572831..00000000 --- a/javascript/gift-finder/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Stagehand + Browserbase: AI-Powered Gift Finder - -## AT A GLANCE - -- Goal: find personalized gift recommendations using AI-generated search queries and intelligent product scoring. -- AI Integration: Stagehand for AI-generated search queries and score products based on recipient profile. -- Concurrent Sessions: runs multiple browser sessions simultaneously to search different queries in parallel. -- Proxies: uses Browserbase proxies with UK geolocation for European website access (Firebox.eu). - -## GLOSSARY - -- act: perform UI actions from a prompt (search, click, type) - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from pages using schemas - Docs → https://docs.stagehand.dev/basics/extract -- concurrent sessions: run multiple browser sessions simultaneously for faster searching - Docs → https://docs.browserbase.com/guides/concurrency-rate-limits -- proxies: use geolocation-based routing for European website access (Firebox.eu) - Docs → https://docs.browserbase.com/features/proxies - -## QUICKSTART - -1. cd gift-finder-template -2. npm install -3. npm install inquirer openai -4. cp .env.example .env -5. Add your Browserbase API key and Project ID to .env -6. npm start - -## EXPECTED OUTPUT - -- Prompts user for recipient and description -- Generates 3 search queries using OpenAI -- Runs concurrent browser sessions to search Firebox.eu -- Extracts product data using structured schemas -- AI-scores products based on recipient profile -- Displays top 3 personalized gift recommendations - -## COMMON PITFALLS - -- Browserbase Developer plan or higher is required to use proxies (they have been commented out in the code) -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains all required API keys -- Search failures: check internet connection and website accessibility - -## USE CASES - -• Multi-retailer product discovery: Generate smart queries, browse in parallel, and extract structured results across sites (with geo-specific proxies when needed). -• Personalized gifting/recommendations: Score items against a recipient profile for gift lists, concierge shopping, or corporate gifting portals. -• Assortment & market checks: Rapidly sample categories to compare price/availability/ratings across regions or competitors. - -## NEXT STEPS - -• Add site adapters: Plug in more retailers with per-site extract schemas, result normalization, and de-duplication (canonical URL matching). -• Upgrade ranking: Blend AI scores with signals (price, reviews, shipping, stock), and persist results to JSON/CSV/DB for re-scoring and audits. -• Scale & geo-test: Fan out more concurrent sessions and run a geo matrix via proxies (e.g., UK/EU/US) to compare localized inventory and pricing. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/gift-finder/index.js b/javascript/gift-finder/index.js deleted file mode 100644 index 2f0ba123..00000000 --- a/javascript/gift-finder/index.js +++ /dev/null @@ -1,298 +0,0 @@ -// Stagehand + Browserbase: AI-Powered Gift Finder - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import OpenAI from "openai"; -import { z } from "zod"; -// ============= CONFIGURATION ============= -// Update these values to customize your gift search -const CONFIG = { - recipient: "Friend", // Options: "Mum", "Dad", "Sister", "Brother", "Friend", "Boss" - description: "loves cooking and trying new recipes", // Describe their interests, hobbies, age, etc. -}; -const client = new OpenAI(); -async function generateSearchQueries(recipient, description) { - console.log(`Generating search queries for ${recipient}...`); - // Use AI to generate search terms based on recipient profile - // This avoids generic searches and focuses on thoughtful, complementary gifts - const response = await client.chat.completions.create({ - model: "gpt-4.1", - messages: [ - { - role: "user", - content: `Generate exactly 3 short gift search queries (1-2 words each) for finding gifts for a ${recipient.toLowerCase()} who is described as: "${description}". - -IMPORTANT: Assume they already have the basic necessities related to their interests. Focus on: -- Complementary items that enhance their hobbies -- Thoughtful accessories or upgrades -- Related but unexpected items -- Premium or unique versions of things they might not buy themselves - -AVOID obvious basics like "poker set" for poker players, "dumbbells" for fitness enthusiasts, etc. - -Examples for "loves cooking": -spice rack -chef knife -herb garden - -Return ONLY the search terms, one per line, no dashes, bullets, or numbers. Just the plain search terms:`, - }, - ], - max_completion_tokens: 1000, - }); - // Parse AI response and clean up formatting - const queries = response.choices[0]?.message?.content - ?.trim() - .split("\n") - .filter((q) => q.trim()) || []; - return queries.slice(0, 3); -} -async function scoreProducts(products, recipient, description) { - console.log("AI is analyzing gift options based on recipient profile..."); - // Flatten all products from multiple search sessions into single array - const allProducts = products.flat(); - if (allProducts.length === 0) { - console.log("No products to score"); - return []; - } - // Format products for AI analysis with index numbers for reference - const productList = allProducts - .map((product, index) => `${index + 1}. ${product.title} - ${product.price} - ${product.rating}`) - .join("\n"); - console.log(`Scoring ${allProducts.length} products...`); - const response = await client.chat.completions.create({ - model: "gpt-4.1", - messages: [ - { - role: "user", - content: `You are a gift recommendation expert. Score each product based on how well it matches the recipient profile. - -RECIPIENT: ${recipient} -DESCRIPTION: ${description} - -PRODUCTS TO SCORE: -${productList} - -For each product, provide a score from 1-10 (10 being perfect match) and a brief reason. Consider: -- How well it matches their interests/hobbies -- Appropriateness for the relationship (${recipient.toLowerCase()}) -- Value for money -- Uniqueness/thoughtfulness -- Practical usefulness - -Return ONLY a valid JSON array (no markdown, no code blocks) with this exact format: -[ - { - "productIndex": 1, - "score": 8, - "reason": "Perfect for poker enthusiasts, high quality chips enhance the gaming experience" - }, - { - "productIndex": 2, - "score": 6, - "reason": "Useful but basic, might already own similar item" - } -] - -IMPORTANT: -- Return raw JSON only, no code blocks -- Include all ${allProducts.length} products -- Keep reasons under 100 characters -- Use productIndex 1-${allProducts.length}`, - }, - ], - max_completion_tokens: 1000, - }); - try { - // Clean up AI response by removing markdown code blocks - let responseContent = response.choices[0]?.message?.content?.trim() || "[]"; - responseContent = responseContent.replace(/```json\n?/g, "").replace(/```\n?/g, ""); - // Parse JSON response from AI scoring - const scoresData = JSON.parse(responseContent); - // Map AI scores back to products using index matching - const scoredProducts = allProducts.map((product, index) => { - const scoreInfo = scoresData.find((s) => s.productIndex === index + 1); - return { - ...product, - aiScore: scoreInfo?.score || 0, - aiReason: scoreInfo?.reason || "No scoring available", - }; - }); - // Sort by AI score descending to show best matches first - return scoredProducts.sort((a, b) => (b.aiScore || 0) - (a.aiScore || 0)); - } - catch (error) { - console.error("Error parsing AI scores:", error); - console.log("Using fallback scoring (all products scored as 5)"); - // Fallback scoring ensures app continues working even if AI fails - // Neutral score of 5 allows products to still be ranked and displayed - return allProducts.map((product) => ({ - ...product, - aiScore: 5, - aiReason: "Scoring failed - using neutral score", - })); - } -} -async function getUserInput() { - console.log("Welcome to the Gift Finder App!"); - console.log("Find the perfect gift with intelligent web browsing"); - console.log(`\nSearching for gifts for: ${CONFIG.recipient}`); - console.log(`Profile: ${CONFIG.description}\n`); - // Validate description length - if (CONFIG.description.trim().length < 5) { - throw new Error("Description must be at least 5 characters long. Please update the CONFIG at the top of the file."); - } - return CONFIG; -} -async function main() { - console.log("Starting Gift Finder Application..."); - const { recipient, description } = await getUserInput(); - console.log(`User input received: ${recipient} - ${description}`); - console.log("\nGenerating intelligent search queries..."); - // Generate search queries with fallback for reliability - let searchQueries; - try { - searchQueries = await generateSearchQueries(recipient, description); - console.log("\nGenerated Search Queries:"); - searchQueries.forEach((query, index) => { - console.log(` ${index + 1}. ${query.replace(/['"]/g, "")}`); - }); - } - catch (error) { - console.error("Error generating search queries:", error); - // Fallback queries - searchQueries = ["gifts", "accessories", "items"]; - console.log("Using fallback search queries"); - } - console.log("\nStarting concurrent browser searches..."); - async function runSingleSearch(query, sessionIndex) { - console.log(`Starting search session ${sessionIndex + 1} for: "${query}"`); - // Create separate Stagehand instance for each search to run concurrently - // Each session searches independently to maximize speed - const sessionStagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1", - browserbaseSessionCreateParams: { - // Proxies require Developer Plan or higher - comment in if you have a Developer Plan or higher - // proxies: [ - // { - // "type": "browserbase", - // "geolocation": { - // "city": "LONDON", - // "country": "GB" - // } - // } - // ], - region: "us-east-1", - timeout: 900, - browserSettings: { - viewport: { - width: 1920, - height: 1080, - }, - }, - }, - }); - try { - await sessionStagehand.init(); - const sessionPage = sessionStagehand.context.pages()[0]; - // Display live view URL for debugging and monitoring - const sessionId = sessionStagehand.browserbaseSessionID; - if (sessionId) { - const liveViewUrl = `https://www.browserbase.com/sessions/${sessionId}`; - console.log(`Session ${sessionIndex + 1} Live View: ${liveViewUrl}`); - } - // Navigate to European gift site - proxies help with regional access - console.log(`Session ${sessionIndex + 1}: Navigating to Firebox.eu...`); - await sessionPage.goto("https://firebox.eu/"); - // Perform search using natural language actions - console.log(`Session ${sessionIndex + 1}: Searching for "${query}"...`); - await sessionStagehand.act(`Type ${query} into the search bar`); - await sessionStagehand.act("Click the search button"); - await sessionPage.waitForTimeout(1000); - // Extract structured product data using Zod schema for type safety - console.log(`Session ${sessionIndex + 1}: Extracting product data...`); - const productsData = await sessionStagehand.extract("Extract the first 3 products from the search results", z.object({ - products: z - .array(z.object({ - title: z.string().describe("the title/name of the product"), - url: z.string().url("the full URL link to the product page"), - price: z.string().describe("the price of the product (include currency symbol)"), - rating: z - .string() - .describe("the star rating or number of reviews (e.g., '4.5 stars' or '123 reviews')"), - })) - .max(3) - .describe("array of the first 3 products from search results"), - })); - console.log(`Session ${sessionIndex + 1}: Found ${productsData.products.length} products for "${query}"`); - await sessionStagehand.close(); - return { - query, - sessionIndex: sessionIndex + 1, - products: productsData.products, - }; - } - catch (error) { - console.error(`Session ${sessionIndex + 1} failed:`, error); - try { - await sessionStagehand.close(); - } - catch (closeError) { - console.error(`Error closing session ${sessionIndex + 1}:`, closeError); - } - return { - query, - sessionIndex: sessionIndex + 1, - products: [], - }; - } - } - const searchPromises = searchQueries.map((query, index) => runSingleSearch(query, index)); - console.log("\nBrowser Sessions Starting..."); - console.log("Live view links will appear as each session initializes"); - // Wait for all concurrent searches to complete - const allResults = await Promise.all(searchPromises); - // Calculate total products found across all search sessions - const totalProducts = allResults.reduce((sum, result) => sum + result.products.length, 0); - console.log(`\nTotal products found: ${totalProducts} across ${searchQueries.length} searches`); - // Flatten all products into single array for AI scoring - const allProductsFlat = allResults.flatMap((result) => result.products); - if (allProductsFlat.length > 0) { - try { - // AI scores all products and ranks them by relevance to recipient - const scoredProducts = await scoreProducts(allProductsFlat, recipient, description); - const top3Products = scoredProducts.slice(0, 3); - console.log("\nTOP 3 RECOMMENDED GIFTS:"); - // Display top 3 products with AI reasoning for transparency - top3Products.forEach((product, index) => { - const rank = `#${index + 1}`; - console.log(`\n${rank} - ${product.title}`); - console.log(`Price: ${product.price}`); - console.log(`Rating: ${product.rating}`); - console.log(`Score: ${product.aiScore}/10`); - console.log(`Why: ${product.aiReason}`); - console.log(`Link: ${product.url}`); - }); - console.log(`\nGift finding complete! Found ${totalProducts} products, analyzed ${scoredProducts.length} with AI.`); - } - catch (error) { - console.error("Error scoring products:", error); - console.log(`Target: ${recipient}`); - console.log(`Profile: ${description}`); - } - } - else { - // Handle case where no products were found across all searches - console.log("No products found to score"); - console.log("Try adjusting your recipient description or check if the website is accessible"); - } - console.log("\nThank you for using Gift Finder!"); -} -main().catch((err) => { - console.error("Application error:", err); - process.exit(1); -}); diff --git a/javascript/google-trends/.env.example b/javascript/google-trends/.env.example deleted file mode 100644 index 2e9ab652..00000000 --- a/javascript/google-trends/.env.example +++ /dev/null @@ -1,2 +0,0 @@ -# Browserbase credentials (required) -BROWSERBASE_API_KEY=your-browserbase-api-key diff --git a/javascript/google-trends/README.md b/javascript/google-trends/README.md deleted file mode 100644 index 897e40e2..00000000 --- a/javascript/google-trends/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Stagehand + Browserbase: Google Trends Keywords Extractor - -## AT A GLANCE - -- Goal: Extract trending search keywords from Google Trends for any country with structured JSON output. -- Configurable by country code (US, GB, IN, DE, etc.) and language preference. -- Uses Zod schema validation for consistent, typed data extraction. -- Docs → https://docs.stagehand.dev/basics/extract - -## GLOSSARY - -- extract: extract structured data from web pages using natural language instructions and Zod schemas - Docs → https://docs.stagehand.dev/basics/extract -- act: perform UI actions from a prompt (click, type, dismiss dialogs) - Docs → https://docs.stagehand.dev/basics/act - -## QUICKSTART - -1. pnpm install -2. cp .env.example .env -3. Add your Browserbase API key to .env -4. pnpm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Displays live session link for monitoring -- Navigates to Google Trends trending page with configured country/language -- Dismisses any consent dialogs if present -- Extracts trending keywords with rank positions -- Outputs structured JSON with country code, language, timestamp, and keyword list -- Closes session cleanly - -## COMMON PITFALLS - -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Invalid country code: ensure country code is a valid 2-letter ISO code (US, GB, IN, DE, FR, BR, etc.) -- Empty results: Google Trends may not have trending data for all country/language combinations -- Consent dialogs: script handles "Got it" dialogs automatically, but regional variations may require adjustment -- Find more information on your Browserbase dashboard -> https://www.browserbase.com/sign-in - -## USE CASES - -• Market research: Track trending topics across different regions to identify emerging interests and market opportunities. -• Content strategy: Discover popular search terms to inform blog posts, social media content, or SEO keyword targeting. -• Competitive intelligence: Monitor trending keywords in your industry to stay ahead of market shifts and consumer interests. - -## NEXT STEPS - -• Parameterize inputs: Accept country code and limit as command-line arguments or environment variables for flexible deployment. -• Historical tracking: Store extracted keywords with timestamps to build a trends database and analyze keyword momentum over time. -• Multi-region comparison: Extend to fetch trends from multiple countries in parallel and compare regional differences. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/google-trends/index.js b/javascript/google-trends/index.js deleted file mode 100644 index b550bfe5..00000000 --- a/javascript/google-trends/index.js +++ /dev/null @@ -1,94 +0,0 @@ -// Stagehand + Browserbase: Google Trends Keywords Extractor - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// Configuration variables -const countryCode = "US"; // Two-letter ISO code (US, GB, IN, DE, FR, BR) -const limit = 20; // Max keywords to return -const language = "en-US"; // Language code for results -// Define Zod schema for structured data extraction -const TrendingKeywordSchema = z.object({ - rank: z.number().describe("Position in the trending list (1, 2, 3, etc.)"), - keyword: z.string().describe("The main trending search term or keyword"), -}); -async function main() { - console.log("Starting Google Trends Keywords Extractor..."); - console.log(`Country Code: ${countryCode}`); - console.log(`Language: ${language}`); - console.log(`Limit: ${limit} keywords`); - // Initialize Stagehand with Browserbase for cloud-based browser automation. - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "google/gemini-2.5-flash", - }); - try { - // Initialize browser session to start data extraction process. - await stagehand.init(); - console.log("Stagehand initialized successfully"); - // Provide live session URL for debugging and monitoring extraction process. - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Build and navigate to Google Trends URL with country code and language. - const trendsUrl = `https://trends.google.com/trending?geo=${countryCode.toUpperCase()}&hl=${language}`; - console.log(`Navigating to: ${trendsUrl}`); - await page.goto(trendsUrl, { - waitUntil: "networkidle", - }); - console.log("Page loaded successfully"); - // Dismiss any consent/welcome dialogs that block content. - try { - console.log("Checking for consent dialogs..."); - await stagehand.act('Click the "Got it" button if visible', { timeout: 5000 }); - // Small delay to let the dialog close and content load. - await new Promise((resolve) => setTimeout(resolve, 1500)); - } - catch { - // No dialog present, continue. - console.log("No consent dialog found, continuing..."); - } - // Extract trending keywords using Stagehand's structured extraction with Zod schema. - console.log("Extracting trending keywords from table..."); - const extractResult = await stagehand.extract(`Extract the trending search keywords from the Google Trends table. Each row has a trending topic/keyword shown as a button (like "catherine ohara", "don lemon arrested", "fed chair", etc.). For each trend, extract the main keyword text and assign a rank starting from 1 for the first trend. Return up to ${limit} items.`, z.array(TrendingKeywordSchema)); - // Apply limit to results and build output structure. - const limitedKeywords = extractResult.slice(0, limit); - console.log(`Successfully extracted ${limitedKeywords.length} trending keywords`); - // Output formatted results with metadata. - const result = { - country_code: countryCode.toUpperCase(), - language: language, - extracted_at: new Date().toISOString(), - trending_keywords: limitedKeywords, - }; - console.log("\n=== Results ==="); - console.log(JSON.stringify(result, null, 2)); - console.log(`\nExtraction complete! Found ${limitedKeywords.length} trending keywords.`); - } - catch (error) { - console.error("Error extracting trending keywords:", error); - // Provide helpful troubleshooting information. - console.error("\nCommon issues:"); - console.error("1. Check .env file has BROWSERBASE_API_KEY"); - console.error("2. Ensure country code is a valid 2-letter ISO code (US, GB, IN, DE, etc.)"); - console.error("3. Verify Browserbase account has sufficient credits"); - console.error("4. Check if Google Trends page structure has changed"); - throw error; - } - finally { - // Always close session to release resources and clean up. - console.log("Closing browser session..."); - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Application error:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify country code is a valid 2-letter ISO code (US, GB, IN, DE, etc.)"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/google-trends/package.json b/javascript/google-trends/package.json deleted file mode 100644 index b1e3f9f5..00000000 --- a/javascript/google-trends/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "google-trends-template", - "version": "1.0.0", - "description": "Stagehand + Browserbase: Google Trends Keywords Extractor", - "main": "index.js", - "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" - }, - "keywords": [ - "stagehand", - "browserbase", - "automation", - "google-trends", - "keywords", - "seo", - "trending" - ], - "author": "", - "license": "MIT", - "dependencies": { - "@browserbasehq/stagehand": "latest", - "dotenv": "^16.0.0", - "zod": "^3.22.0" - } -} diff --git a/javascript/license-verification/README.md b/javascript/license-verification/README.md deleted file mode 100644 index a4340f92..00000000 --- a/javascript/license-verification/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Stagehand + Browserbase: Data Extraction with Structured Schemas - -## AT A GLANCE - -- Goal: show how to extract structured, validated data from websites using Stagehand + Zod. -- Data Extraction: automate navigation, form submission, and structured scraping in one flow. -- Schema Validation: enforce type safety and consistency with Zod schemas. -- Practical Example: verify California real estate license details with a typed output object. - -## GLOSSARY - -- act: perform UI actions from a prompt (type, click, navigate). - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from web pages into validated objects. - Docs → https://docs.stagehand.dev/basics/extract -- schema: a Zod definition that enforces data types, optional fields, and validation rules. - Docs → https://zod.dev/ -- form automation: filling and submitting inputs to trigger results before extraction. -- structured scraping: extracting consistent, typed data that can flow into apps, CRMs, or compliance systems. - -## QUICKSTART - -1. cd license-verification-template -2. npm install -3. cp .env.example .env -4. Add your Browserbase API key and Project ID to .env -5. npm start - -## EXPECTED OUTPUT - -- Navigates to California DRE license verification website -- Fills in license ID and submits form -- Extracts structured license data using Zod schema -- Returns typed object with license verification details - -## COMMON PITFALLS - -- "Cannot find module 'dotenv'": ensure pnpm install ran successfully -- Missing API key: verify .env is loaded and file is not committed -- Schema validation errors: ensure extracted data matches Zod schema structure -- Form submission failures: check if website structure has changed - -## USE CASES - -• License & credential verification: Extract and validate professional license data from regulatory portals. -• Compliance automation: Monitor status changes (active, expired, disciplinary) for risk and regulatory workflows. -• Structured research: Collect validated datasets from government or industry registries for BI or due diligence. - -## NEXT STEPS - -• Expand schema coverage: Add more fields (disciplinary actions, broker info, historical data) for richer records. -• Scale across sources: Point the same flow at other jurisdictions, databases, or professional directories. -• Persist & integrate: Store structured results in a database or push directly into CRM/compliance systems. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/license-verification/index.js b/javascript/license-verification/index.js deleted file mode 100644 index 9dab95cb..00000000 --- a/javascript/license-verification/index.js +++ /dev/null @@ -1,58 +0,0 @@ -// Real Estate License Verification - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// License verification variables -const variables = { - input1: "02237476", // DRE License ID to search for -}; -async function main() { - // Initialize Stagehand with Browserbase for cloud-based browser automation. - const stagehand = new Stagehand({ - env: "BROWSERBASE", // Use Browserbase cloud browsers for reliable automation. - verbose: 1, - model: "openai/gpt-4.1", - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - }); - // Initialize browser session to start data extraction process. - await stagehand.init(); - console.log(`Stagehand Session Started`); - // Provide live session URL for debugging and monitoring extraction process. - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Navigate to California DRE license verification website for data extraction. - console.log("Navigating to: https://www2.dre.ca.gov/publicasp/pplinfo.asp"); - await page.goto("https://www2.dre.ca.gov/publicasp/pplinfo.asp"); - // Fill in license ID to search for specific real estate professional. - console.log(`Performing action: type ${variables.input1} into the License ID input field`); - await stagehand.act(`type ${variables.input1} into the License ID input field`); - // Submit search form to retrieve license verification data. - console.log(`Performing action: click the Find button`); - await stagehand.act(`click the Find button`); - // Extract structured license data using Zod schema for type safety and validation. - console.log(`Extracting: extract all the license verification details for DRE#02237476`); - const extractedData4 = await stagehand.extract(`extract all the license verification details for DRE#02237476`, z.object({ - licenseType: z.string().optional(), // Type of real estate license - name: z.string().optional(), // License holder's full name - mailingAddress: z.string().optional(), // Current mailing address - licenseId: z.string().optional(), // Unique license identifier - expirationDate: z.string().optional(), // License expiration date - licenseStatus: z.string().optional(), // Current status (active, expired, etc.) - salespersonLicenseIssued: z.string().optional(), // Date salesperson license was issued - formerNames: z.string().optional(), // Any previous names used - responsibleBroker: z.string().optional(), // Associated broker name - brokerLicenseId: z.string().optional(), // Broker's license ID - brokerAddress: z.string().optional(), // Broker's business address - disciplinaryAction: z.string().optional(), // Any disciplinary actions taken - otherComments: z.string().optional(), // Additional relevant information - })); - console.log("Extracted:", extractedData4); - // Always close session to release resources and clean up. - await stagehand.close(); -} -main().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/javascript/manual-mfa-with-contexts/README.md b/javascript/manual-mfa-with-contexts/README.md deleted file mode 100644 index bcbd7e9a..00000000 --- a/javascript/manual-mfa-with-contexts/README.md +++ /dev/null @@ -1,65 +0,0 @@ -# Stagehand + Browserbase: Manual MFA with Contexts - -## AT A GLANCE - -- Goal: demonstrate how to persist authentication across sessions using Browserbase Contexts, eliminating MFA friction after the first login. -- Flow: first session creates context and completes MFA manually → context saves auth state → second session reuses context with no MFA required. - -## GLOSSARY - -- context: a Browserbase feature that persists browser state (cookies, localStorage, sessionStorage) across sessions. - Docs → https://docs.browserbase.com/features/contexts -- persist: setting that saves authentication state including MFA trust/remember device state to the context. -- MFA (Multi-Factor Authentication): two-factor authentication requiring a code from an authenticator app. -- session persistence: maintaining logged-in state across multiple browser sessions without re-authentication. - -## QUICKSTART - -1. cd manual-mfa-with-contexts -2. pnpm install -3. cp .env.example .env -4. Add your Browserbase API key, GitHub username, and password to .env -5. Ensure 2FA is enabled on your GitHub test account (Settings → Password and authentication → Enable two-factor authentication) -6. pnpm start - -## EXPECTED OUTPUT - -- Creates a new Browserbase context -- First session: navigates to GitHub login, fills credentials, detects MFA prompt -- Pauses and displays Browserbase session link for manual MFA completion -- Waits for MFA completion (2 minute timeout) -- Saves authentication state to context -- Second session: reuses context, navigates to GitHub (already logged in, no MFA) -- Extracts logged-in username to verify authentication -- Cleans up context - -## COMMON PITFALLS - -- "Cannot find module 'dotenv'": ensure pnpm install ran successfully -- Missing credentials: verify .env contains BROWSERBASE_API_KEY, GITHUB_USERNAME, and GITHUB_PASSWORD -- MFA timeout: ensure you complete MFA within 2 minutes, or increase timeout value -- 2FA not enabled: GitHub account must have 2FA enabled for this demo to work -- Context not persisting: verify context.persist is set to true in browserSettings - -## USE CASES - -• Payment automation: Complete MFA once for utility portals, then automate future payments without MFA prompts. -• Account management: Persist authentication for services requiring MFA, enabling automated account management workflows. -• Compliance automation: Store trusted device state for regulatory portals, reducing friction for recurring compliance tasks. - -## NEXT STEPS - -• Store context IDs: Save context_id per customer in your database to reuse across sessions. -• Multi-portal support: Extend to multiple portals/services, each with their own context. -• Context management: Implement context cleanup, rotation, and expiration policies. -• Error handling: Add retry logic and better error messages for MFA timeouts. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -📚 Contexts Docs: https://docs.browserbase.com/features/contexts -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/manual-mfa-with-contexts/index.js b/javascript/manual-mfa-with-contexts/index.js deleted file mode 100644 index c13063b5..00000000 --- a/javascript/manual-mfa-with-contexts/index.js +++ /dev/null @@ -1,201 +0,0 @@ -// Manual MFA with Browserbase Contexts - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { Browserbase } from "@browserbasehq/sdk"; -import { z } from "zod"; -const bb = new Browserbase({ - apiKey: process.env.BROWSERBASE_API_KEY, -}); -/** - * First session: Create context and login (with MFA) - */ -async function createSessionWithContext() { - console.log("Creating new Browserbase context..."); - const context = await bb.createContext(); - console.log(`Context created: ${context.id}`); - console.log("First session: Performing login with MFA..."); - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1-mini", - browserbaseSessionCreateParams: { - browserSettings: { - context: { - id: context.id, - persist: true, // Save authentication state including MFA - }, - }, - }, - }); - await stagehand.init(); - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Navigate to GitHub login - console.log("Navigating to GitHub login..."); - await page.goto("https://github.com/login"); - await page.waitForLoadState("domcontentloaded"); - // Fill in credentials - console.log("Entering username..."); - await stagehand.act(`Type '${process.env.GITHUB_USERNAME}' into the username field`); - console.log("Entering password..."); - await stagehand.act(`Type '${process.env.GITHUB_PASSWORD}' into the password field`); - console.log("Clicking Sign in..."); - await stagehand.act("Click the Sign in button"); - await page.waitForLoadState("networkidle"); - // Check if MFA is required - const mfaRequired = await stagehand.extract("Is there a two-factor authentication or verification code prompt on the page?", z.boolean()); - if (mfaRequired) { - console.log("MFA DETECTED!"); - console.log("═══════════════════════════════════════════════════════════"); - console.log("PAUSED: Please complete MFA in the browser"); - console.log("═══════════════════════════════════════════════════════════"); - console.log(`1. Open the Browserbase session in your browser: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - console.log("2. Enter your 2FA code from authenticator app"); - console.log("3. Click 'Verify' or submit"); - console.log("4. Wait for login to complete"); - console.log("\nThe script will wait for you to complete MFA...\n"); - // Wait for MFA completion (poll until we're no longer on login page) - let loginComplete = false; - const startTime = Date.now(); - const timeout = 120000; // 2 minutes - while (!loginComplete && Date.now() - startTime < timeout) { - await new Promise((resolve) => setTimeout(resolve, 3000)); // Check every 3 seconds - const currentUrl = page.url(); - if (!currentUrl.includes("/login") && !currentUrl.includes("/sessions/two-factor")) { - loginComplete = true; - } - } - if (!loginComplete) { - throw new Error("MFA timeout - login was not completed within 2 minutes"); - } - console.log("MFA completed! Login successful.\n"); - } - else { - console.log("Login successful (no MFA required)\n"); - } - console.log(`Context ${context.id} now contains:`); - console.log(" - Session cookies"); - console.log(" - MFA trust/remember device state"); - console.log(" - All authentication data\n"); - await stagehand.close(); - return context.id; -} -/** - * Second session: Reuse context - NO MFA needed! - */ -async function reuseContext(contextId) { - console.log(`Second session: Reusing context ${contextId}`); - console.log(" (No login, no MFA required - auth state persisted)\n"); - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1-mini", - browserbaseSessionCreateParams: { - browserSettings: { - context: { - id: contextId, - persist: true, - }, - }, - }, - }); - await stagehand.init(); - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Navigate directly to GitHub (should already be logged in) - console.log("Navigating to GitHub..."); - await page.goto("https://github.com"); - await page.waitForLoadState("networkidle"); - // Check if we're logged in - const username = await stagehand.extract("Extract the logged-in username or check if we're authenticated", z.string()); - console.log("\nSUCCESS! Already logged in without MFA!"); - console.log(` Username: ${username}`); - console.log("\nThis is the power of Browserbase Contexts:"); - console.log(" - First session: User completes MFA once"); - console.log(" - Context saves trusted device state"); - console.log(" - All future sessions: No MFA required\n"); - await stagehand.close(); -} -/** - * Clean up context - */ -async function deleteContext(contextId) { - console.log(`Deleting context: ${contextId}`); - try { - // Delete via API (SDK doesn't have delete method) - const response = await fetch(`https://api.browserbase.com/v1/contexts/${contextId}`, { - method: "DELETE", - headers: { - "X-BB-API-Key": process.env.BROWSERBASE_API_KEY, - }, - }); - if (response.ok) { - console.log("Context deleted\n"); - } - else { - console.log(`Could not delete context: ${response.status} ${response.statusText}`); - console.log(" Context will auto-expire after 30 days\n"); - } - } - catch (error) { - console.log(`Could not delete context: ${error instanceof Error ? error.message : String(error)}`); - console.log(" Context will auto-expire after 30 days\n"); - } -} -async function main() { - console.log("Starting Browserbase Context MFA Persistence Demo..."); - // Check environment variables - if (!process.env.BROWSERBASE_API_KEY) { - console.error("\n❌ Missing Browserbase credentials"); - console.error(" Set BROWSERBASE_API_KEY in .env"); - process.exit(1); - } - if (!process.env.GITHUB_USERNAME || !process.env.GITHUB_PASSWORD) { - console.error("\nError: Missing GitHub credentials"); - console.error(" Set GITHUB_USERNAME and GITHUB_PASSWORD in .env"); - console.error("Setup Instructions:"); - console.error(" 1. Create a test GitHub account"); - console.error(" 2. Enable 2FA: Settings → Password and authentication"); - console.error(" 3. Set credentials in .env file"); - process.exit(1); - } - try { - console.log("\n📋 Demo Flow:"); - console.log(" 1. First session: Login + complete MFA manually"); - console.log(" 2. Second session: No login, no MFA needed"); - console.log(" 3. Clean up context\n"); - // First session: Create context and login with MFA - const contextId = await createSessionWithContext(); - console.log("⏳ Waiting 5 seconds before reusing context...\n"); - await new Promise((resolve) => setTimeout(resolve, 5000)); - // Second session: Reuse context (NO MFA!) - await reuseContext(contextId); - // Clean up - await deleteContext(contextId); - console.log("═══════════════════════════════════════════════════════════"); - console.log("Key Takeaway:"); - console.log("═══════════════════════════════════════════════════════════"); - console.log("✅ First session: User completes MFA once"); - console.log("✅ Context saves trusted device state"); - console.log("✅ All future sessions: No MFA prompt"); - console.log("✅ Store context_id per customer in database\n"); - } - catch (error) { - console.error("\n❌ Error:", error instanceof Error ? error.message : String(error)); - console.error("\nTroubleshooting:"); - console.error(" - Ensure GitHub credentials are correct"); - console.error(" - Ensure 2FA is enabled on the test account"); - console.error(" - Check Browserbase dashboard for session details"); - throw error; - } -} -main().catch((err) => { - console.error("Application error:", err); - process.exit(1); -}); diff --git a/javascript/manual-mfa-with-contexts/package.json b/javascript/manual-mfa-with-contexts/package.json deleted file mode 100644 index e81067df..00000000 --- a/javascript/manual-mfa-with-contexts/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "manual-mfa-with-contexts", - "version": "1.0.0", - "description": "Stagehand + Browserbase: Manual MFA with Contexts", - "type": "module", - "main": "index.js", - "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" - }, - "dependencies": { - "@browserbasehq/sdk": "^2.9.0", - "@browserbasehq/stagehand": "latest", - "dotenv": "^16.4.5", - "zod": "^3.23.8" - } -} diff --git a/javascript/mfa-handling/README.md b/javascript/mfa-handling/README.md deleted file mode 100644 index cade14a1..00000000 --- a/javascript/mfa-handling/README.md +++ /dev/null @@ -1,72 +0,0 @@ -# Stagehand + Browserbase: MFA Handling - TOTP Automation - -## AT A GLANCE - -- Goal: Automate MFA (Multi-Factor Authentication) completion using TOTP (Time-based One-Time Password) code generation. -- TOTP Generation: Implements RFC 6238 compliant algorithm to generate time-based authentication codes programmatically. -- Automatic Form Filling: Extracts TOTP secrets from pages and automatically fills MFA forms without user interaction. -- Retry Logic: Handles time window edge cases by regenerating codes and retrying authentication when needed. -- Docs → https://docs.stagehand.dev/basics/act - -## GLOSSARY - -- act: perform UI actions from a prompt (type, click, fill forms) - Docs → https://docs.stagehand.dev/basics/act -- extract: extract structured data from web pages using natural language instructions - Docs → https://docs.stagehand.dev/basics/extract -- TOTP: Time-based One-Time Password - a 6-digit code that changes every 30 seconds, generated using HMAC-SHA1 algorithm -- RFC 6238: Standard specification for TOTP authentication codes used by Google Authenticator, Authy, and other authenticator apps - -## QUICKSTART - -1. pnpm install -2. cp .env.example .env -3. Add your Browserbase API key to .env (BROWSERBASE_API_KEY) -4. pnpm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Displays live session link for monitoring -- Navigates to TOTP challenge demo page (authenticationtest.com/totpChallenge/) -- Extracts test credentials (email, password) and TOTP secret from the page -- Generates TOTP code using RFC 6238 algorithm -- Fills in email and password fields -- Fills in TOTP code field with generated code -- Submits authentication form -- Checks authentication result -- Retries with fresh code if initial attempt fails (handles time window edge cases) -- Closes session cleanly - -## COMMON PITFALLS - -- Dependency install errors: ensure pnpm install completed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- TOTP code expiration: codes are valid for 30 seconds - if authentication fails, the script automatically retries with a fresh code -- Page structure changes: if the demo site structure changes, extraction may fail -- Network timeouts: ensure stable internet connection for reliable page loading -- Find more information on your Browserbase dashboard -> https://www.browserbase.com/sign-in - -## USE CASES - -• Automated authentication: Complete MFA challenges automatically when session persistence isn't enough (session expired, new device, etc.) -• TOTP integration: Store encrypted TOTP secrets during user onboarding and generate codes programmatically when needed -• Zero-touch MFA: Eliminate user interaction for MFA completion in automated workflows -• Session recovery: Automatically handle MFA prompts when re-authenticating expired sessions - -## NEXT STEPS - -• Secure storage: Implement encrypted TOTP secret storage (AES-256) in your database during user onboarding -• Multiple time windows: Add support for trying ±1 time window (60s range) if current code fails -• SMS/Email MFA: Extend to support SMS codes (via Twilio/Bandwidth API) or email codes (via Gmail API/IMAP) -• Backup codes: Implement fallback to backup codes stored during initial MFA setup -• Context integration: Combine with Browserbase Contexts to minimize MFA prompts (95% context reuse, 4% auto TOTP, 1% user-mediated) -• Error handling: Add graceful fallback to user prompts when automation fails - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com diff --git a/javascript/mfa-handling/index.js b/javascript/mfa-handling/index.js deleted file mode 100644 index 788195bf..00000000 --- a/javascript/mfa-handling/index.js +++ /dev/null @@ -1,149 +0,0 @@ -// Stagehand + Browserbase: MFA Handling - TOTP Automation - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -import crypto from "crypto"; -// Demo site URL for TOTP challenge testing -const DEMO_URL = "https://authenticationtest.com/totpChallenge/"; -// Generate TOTP code (Time-based One-Time Password) using RFC 6238 compliant algorithm -// Same algorithm used by Google Authenticator, Authy, and other authenticator apps -function generateTOTP(secret, window = 0) { - // Convert base32 secret to buffer - const base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - let bits = ""; - let hex = ""; - secret = secret.toUpperCase().replace(/=+$/, ""); - for (let i = 0; i < secret.length; i++) { - const val = base32chars.indexOf(secret.charAt(i)); - if (val === -1) - throw new Error("Invalid base32 character in secret"); - bits += val.toString(2).padStart(5, "0"); - } - for (let i = 0; i + 4 <= bits.length; i += 4) { - const chunk = bits.substr(i, 4); - hex += parseInt(chunk, 2).toString(16); - } - const secretBuffer = Buffer.from(hex, "hex"); - // Get current time window (30 second intervals) - const time = Math.floor(Date.now() / 1000 / 30) + window; - const timeBuffer = Buffer.alloc(8); - timeBuffer.writeBigInt64BE(BigInt(time)); - // Generate HMAC-SHA1 hash - const hmac = crypto.createHmac("sha1", secretBuffer); - hmac.update(timeBuffer); - const hmacResult = hmac.digest(); - // Dynamic truncation to extract 6-digit code - const offset = hmacResult[hmacResult.length - 1] & 0xf; - const code = ((hmacResult[offset] & 0x7f) << 24) | - ((hmacResult[offset + 1] & 0xff) << 16) | - ((hmacResult[offset + 2] & 0xff) << 8) | - (hmacResult[offset + 3] & 0xff); - // Return 6-digit code with leading zeros - return (code % 1000000).toString().padStart(6, "0"); -} -async function main() { - console.log("Starting MFA Handling - TOTP Automation..."); - // Initialize Stagehand with Browserbase for cloud-based browser automation - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "google/gemini-2.5-flash", // Routed through Model Gateway - }); - try { - // Initialize browser session to start automation - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to TOTP challenge demo page - console.log("Navigating to TOTP Challenge page..."); - await page.goto(DEMO_URL, { - waitUntil: "domcontentloaded", - }); - // Extract test credentials and TOTP secret from the page - console.log("Extracting test credentials and TOTP secret..."); - const credentials = await stagehand.extract("Extract the test email, password, and TOTP secret key shown on the page", z.object({ - email: z.string(), - password: z.string(), - totpSecret: z.string().describe("The TOTP secret key for generating codes"), - })); - console.log(`Credentials extracted - Email: ${credentials.email}`); - // Generate TOTP code using RFC 6238 algorithm - const totpCode = generateTOTP(credentials.totpSecret); - const secondsLeft = 30 - (Math.floor(Date.now() / 1000) % 30); - console.log(`Generated TOTP code: ${totpCode} (valid for ${secondsLeft} seconds)`); - // Fill in login form with email and password - console.log("Filling in email..."); - await stagehand.act(`Type '${credentials.email}' into the email field`); - console.log("Filling in password..."); - await stagehand.act(`Type '${credentials.password}' into the password field`); - // Fill in TOTP code - console.log("Filling in TOTP code..."); - await stagehand.act(`Type '${totpCode}' into the TOTP code field`); - // Submit the form - console.log("Submitting form..."); - await stagehand.act("Click the submit or login button"); - // Wait for response - be tolerant of sites that never reach full "networkidle" - try { - console.log("Waiting for page to finish loading after submit..."); - await page.waitForLoadState("networkidle", { timeout: 15000 }); - } - catch (err) { - console.warn("Timed out waiting for 'networkidle' after submit; continuing because the login likely succeeded.", err); - } - // Check if login succeeded - console.log("Checking authentication result..."); - const result = await stagehand.extract("Check if the login was successful or if there's an error message", z.object({ - success: z.boolean(), - message: z.string(), - })); - if (result.success) { - console.log("SUCCESS! TOTP authentication completed automatically!"); - console.log("Authentication Result:", JSON.stringify(result, null, 2)); - } - else { - console.log("Authentication may have failed. Message:", result.message); - console.log("Retrying with a fresh TOTP code..."); - // Regenerate and retry with new code (handles time window edge cases) - const newCode = generateTOTP(credentials.totpSecret); - console.log(`New TOTP code: ${newCode}`); - await stagehand.act("Clear the TOTP code field"); - await stagehand.act(`Type '${newCode}' into the TOTP code field`); - await stagehand.act("Click the submit or login button"); - try { - console.log("Waiting for page to finish loading after retry submit..."); - await page.waitForLoadState("networkidle", { timeout: 15000 }); - } - catch (err) { - console.warn("Timed out waiting for 'networkidle' after retry submit; continuing because the login likely succeeded.", err); - } - const retryResult = await stagehand.extract("Check if the login was successful", z.boolean()); - if (retryResult) { - console.log("Success on retry!"); - } - else { - console.log("Authentication failed after retry"); - } - } - } - catch (error) { - console.error("Error during MFA handling:", error); - } - finally { - // Always close session to release resources and clean up - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in MFA handling:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - TOTP code may have expired (try running again)"); - console.error(" - Page structure may have changed"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/mfa-handling/package.json b/javascript/mfa-handling/package.json deleted file mode 100644 index aa0d9ab2..00000000 --- a/javascript/mfa-handling/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "mfa-handling-template", - "version": "1.0.0", - "description": "Stagehand + Browserbase: MFA Handling - TOTP Automation", - "main": "index.js", - "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" - }, - "keywords": [ - "stagehand", - "browserbase", - "automation", - "mfa", - "totp", - "2fa" - ], - "author": "", - "license": "MIT", - "dependencies": { - "@browserbasehq/stagehand": "latest", - "dotenv": "^16.0.0", - "zod": "^3.22.0" - } -} diff --git a/javascript/microsoft-cua/README.md b/javascript/microsoft-cua/README.md deleted file mode 100644 index de58c89b..00000000 --- a/javascript/microsoft-cua/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Stagehand + Browserbase: Computer Use Agent (CUA) Example - -## AT A GLANCE - -- Goal: demonstrate autonomous web browsing using Microsoft's Computer Use Agent with Stagehand and Browserbase. -- Uses Stagehand Agent to automate complex workflows with AI powered browser agents -- Leverages Microsoft's fara-7b model for autonomous web interaction and decision-making. - -## GLOSSARY - -- agent: create an autonomous AI agent that can execute complex multi-step tasks - Docs → https://docs.stagehand.dev/basics/agent#what-is-agent - -## QUICKSTART - -1. npm install -2. cp .env.example .env -3. Add your Browserbase API key, Azure API key, and Azure endpoint to .env -4. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Navigates to Google search engine -- Executes autonomous search and data extraction task -- Displays live session link for monitoring -- Returns structured results or completion status -- Closes session cleanly - -## COMMON PITFALLS - -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY, AZURE_API_KEY, and AZURE_ENDPOINT -- Microsoft API access: ensure you have access to Microsoft's fara-7b model via Azure or Fireworks - -## USE CASES - -• Autonomous research: Let AI agents independently research topics, gather information, and compile reports without manual intervention. -• Complex web workflows: Automate multi-step processes that require decision-making, form filling, and data extraction across multiple pages. -• Content discovery: Search for specific information, verify data accuracy, and cross-reference sources autonomously. - -## NEXT STEPS - -• Customize instructions: Modify the instruction variable to test different autonomous tasks and scenarios. -• Add error handling: Implement retry logic, fallback strategies, and better error recovery for failed agent actions. -• Extend capabilities: Add support for file downloads, form submissions, and more complex interaction patterns. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/microsoft-cua/index.js b/javascript/microsoft-cua/index.js deleted file mode 100644 index badb7891..00000000 --- a/javascript/microsoft-cua/index.js +++ /dev/null @@ -1,91 +0,0 @@ -// Stagehand + Browserbase: Computer Use Agent (CUA) Example - See README.md for full documentation -import { Stagehand } from "@browserbasehq/stagehand"; -// ============================================================================ -// EXAMPLE INSTRUCTIONS - Choose one to test different scenarios -// ============================================================================ -// Example 1: Learning Plan Creation -// const instruction = `I want to learn more about Sourdough Bread Making. It's my first time learning about it, and want to get a good grasp by investing 1 hour a day for the next 2 months. Go find online courses/resources, create a plan cross-referencing the time I want to invest with the modules/timelines of the courses and return the plan`; -// Example 2: Flight Search -// const instruction = `Use flights.google.com to find the lowest fare from all eligible one-way flights for 1 adult from JFK to Heathrow in the next 30 days.`; -// Example 3: Solar Eclipse Research -const instruction = `Search for the next visible solar eclipse in North America and its expected date, and what about the one after that.`; -// Example 4: GitHub PR Verification -// const instruction = `Find the most recently opened non-draft PR on Github for Browserbase's Stagehand project and make sure the combination-evals in the PR validation passed.`; -// ============================================================================ -async function main() { - const stagehand = new Stagehand({ - env: "BROWSERBASE", - // model: "google/gemini-2.5-pro", // this is the model stagehand uses in act, observe, extract (not agent) - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - browserbaseSessionCreateParams: { - proxies: true, // Using proxies will give the agent a better chance of success - requires Developer Plan or higher, comment out if you don't have access - region: "us-west-2", - browserSettings: { - blockAds: true, - viewport: { - width: 1288, - height: 711, - }, - }, - }, - }); - try { - // Initialize browser session to start automation. - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - console.log(`Live View Link: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to search engine with extended timeout for slow-loading sites. - await page.goto("https://www.google.com/", { - waitUntil: "domcontentloaded", - }); - // Create agent with computer use capabilities for autonomous web browsing. - const agent = stagehand.agent({ - cua: true, - model: { - modelName: "microsoft/fara-7b", - apiKey: process.env.AZURE_API_KEY, - baseURL: process.env.AZURE_ENDPOINT, - /** Alternative model configuration for Fireworks Deployments */ - // modelName: "accounts/...", - // apiKey: process.env.FIREWORKS_API_KEY, - // baseURL: "https://api.fireworks.ai/inference/v1", - // provider: "microsoft", // Important: this routes to the MicrosoftCUAClient - }, - systemPrompt: `You are a helpful assistant that can use a web browser. - You are currently on the following page: ${page.url()}. - Do not ask follow up questions, the user will trust your judgement. If you are getting blocked on google, try another search engine.`, - }); - console.log("Executing instruction:", instruction); - const result = await agent.execute({ - instruction: instruction, - maxSteps: 30, - highlightCursor: true, - }); - if (result.success === true) { - console.log("Task completed successfully!"); - console.log("Result:", result); - } - else { - console.log("Task failed or was incomplete"); - } - } - catch (error) { - console.error("Error executing computer use agent:", error); - } - finally { - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Error in computer use agent example:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify AZURE_API_KEY is set for the agent"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/nurse-verification/README.md b/javascript/nurse-verification/README.md deleted file mode 100644 index 3d6c38e4..00000000 --- a/javascript/nurse-verification/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Stagehand + Browserbase: Nurse License Verification - -## AT A GLANCE - -- Goal: automate verification of nurse licenses by filling forms and extracting structured results from verification sites. -- Flow: loop through license records → navigate to verification site → fill form → search → extract verification results. -- Benefits: quickly verify multiple licenses without manual form filling, structured data ready for compliance tracking or HR systems. - Docs → https://docs.stagehand.dev/basics/act - -## GLOSSARY - -- act: perform UI actions from a prompt (type, click, fill forms). - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from a page using AI and Zod schemas. - Docs → https://docs.stagehand.dev/basics/extract -- schema: a Zod definition that enforces data types, optional fields, and validation rules. - Docs → https://zod.dev/ -- license verification: process of confirming the validity and status of professional licenses. - -## QUICKSTART - -1. cd nurse-verification -2. npm install -3. cp .env.example .env -4. Add your Browserbase API key to .env -5. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase -- Loops through license records in LicenseRecords array -- For each record: navigates to verification site, fills form, searches -- Extracts verification results: name, license number, status, info URL -- Displays structured JSON output with all verification results -- Provides live session URL for monitoring -- Closes session cleanly - -## COMMON PITFALLS - -- "Cannot find module 'dotenv'": ensure npm install ran successfully -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- No results found: check if license numbers are valid or if verification site structure has changed -- Network issues: ensure internet access and verification sites are accessible -- Schema validation errors: ensure extracted data matches Zod schema structure - -## USE CASES - -• HR compliance: Automate license verification for healthcare staff onboarding and annual reviews. -• Healthcare staffing: Verify credentials of temporary or contract nurses before assignment. -• Regulatory reporting: Collect license status data for compliance reporting and audits. - -## NEXT STEPS - -• Multi-site support: Add support for different license verification sites and adapt form filling logic. -• Batch processing: Load license records from CSV/Excel files for large-scale verification. -• Status monitoring: Set up scheduled runs to track license status changes and expiration dates. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/nurse-verification/index.js b/javascript/nurse-verification/index.js deleted file mode 100644 index 7e855ac7..00000000 --- a/javascript/nurse-verification/index.js +++ /dev/null @@ -1,83 +0,0 @@ -// Stagehand + Browserbase: Automated Nurse License Verification - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// License records to verify - add more records as needed -const LicenseRecords = [ - { - Site: "https://pod-search.kalmservices.net/", - FirstName: "Ronald", - LastName: "Agee", - LicenseNumber: "346", - }, -]; -async function main() { - console.log("Starting Nurse License Verification Automation..."); - // Initialize Stagehand with Browserbase for cloud-based browser automation - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1", - }); - try { - // Initialize browser session - console.log("Initializing browser session..."); - await stagehand.init(); - console.log("Stagehand session started successfully"); - // Provide live session URL for debugging and monitoring - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Process each license record sequentially - for (const LicenseRecord of LicenseRecords) { - console.log(`Verifying license for: ${LicenseRecord.FirstName} ${LicenseRecord.LastName}`); - // Navigate to license verification site - console.log(`Navigating to: ${LicenseRecord.Site}`); - await page.goto(LicenseRecord.Site); - await page.waitForLoadState("domcontentloaded"); - // Fill in form fields with license information - console.log("Filling in license information..."); - await stagehand.act(`Type "${LicenseRecord.FirstName}" into the first name field`); - await stagehand.act(`Type "${LicenseRecord.LastName}" into the last name field`); - await stagehand.act(`Type "${LicenseRecord.LicenseNumber}" into the license number field`); - // Submit search - console.log("Clicking search button..."); - await stagehand.act("Click the search button"); - // Wait for search results to load - await page.waitForLoadState("domcontentloaded"); - // Extract license verification results - console.log("Extracting license verification results..."); - const results = await stagehand.extract("Extract ALL the license verification results from the page, including name, license number and status", z.object({ - list_of_licenses: z.array(z.object({ - name: z.string(), - license_number: z.string(), - status: z.string(), - more_info_url: z.string(), - })), - })); - console.log("License verification results extracted:"); - console.log(JSON.stringify(results, null, 2)); - } - } - catch (error) { - console.error("Error during license verification:", error); - // Provide helpful troubleshooting information - console.error("\nCommon issues:"); - console.error("1. Check .env file has BROWSERBASE_API_KEY"); - console.error("2. Ensure internet access and license verification site is accessible"); - console.error("3. Verify Browserbase account has sufficient credits"); - throw error; - } - finally { - // Clean up browser session - console.log("Closing browser session..."); - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Application error:", err); - process.exit(1); -}); diff --git a/javascript/pickleball/README.md b/javascript/pickleball/README.md deleted file mode 100644 index b7d3cf03..00000000 --- a/javascript/pickleball/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# Stagehand + Browserbase: AI-Powered Court Booking Automation - -## AT A GLANCE - -- Goal: automate tennis and pickleball court bookings in San Francisco Recreation & Parks system. -- AI Integration: Stagehand for UI interaction and data extraction. -- Browser Automation: automates login, filtering, court selection, and booking confirmation. -- User Interaction: prompts for activity type, date, and time preferences with validation. - Docs → https://docs.browserbase.com/fundamentals/create-browser-session - -## GLOSSARY - -- act: perform UI actions from a prompt (click, type, select) - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from pages using schemas - Docs → https://docs.stagehand.dev/basics/extract -- observe: plan actions and get selectors before executing - Docs → https://docs.stagehand.dev/basics/observe -- browser automation: automated interaction with web applications for booking systems - Docs → https://docs.browserbase.com/fundamentals/create-browser-session -- form validation: ensure user input meets booking system requirements - -## QUICKSTART - -1. Create an account with SF Recreation & Parks website -> https://www.rec.us/organizations/san-francisco-rec-park -2. cd pickleball-template -3. npm install -4. npm install inquirer -5. cp .env.example .env -6. Add your Browserbase API key, Project ID, and SF Rec Park credentials to .env -7. npm start - -## EXPECTED OUTPUT - -- Prompts user for activity type (Tennis/Pickleball), date, and time -- Automates login to SF Recreation & Parks booking system -- Filters courts by activity, date, and time preferences -- Extracts available court information and displays options -- Automates court booking with verification code handling -- Confirms successful booking with details - -## COMMON PITFALLS - -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains all required API keys and SF Rec Park login -- Login failures: check SF Rec Park credentials and account status -- Booking errors: verify court availability and booking system accessibility -- Verification codes: ensure you can receive SMS/email codes for booking confirmation - -## FURTHER USE CASES - -• Court Booking: Automate tennis and pickleball court reservations in San Francisco -• Recreation & ticketing: courts, parks, events, museum passes, campsite reservations -• Appointments & scheduling: DMV, healthcare visits, test centers, field service dispatch -• Permits & licensing: business licenses, parking permits, construction approvals, hunting/fishing tags -• Procurement portals: reserve inventory, request quotes, confirm orders -• Travel & logistics: dock door scheduling, freight pickups, crew shifts, equipment rentals -• Education & training: lab reservations, proctored exam slots, workshop sign-ups -• Internal admin portals: hardware checkout, conference-room overflow, cafeteria or shift scheduling - -## NEXT STEPS - -• Swap the target site: point the script at a different booking or reservation portal (e.g., gyms, coworking, campsites) -• Generalize filters: extend date/time/activity prompts to handle more categories or custom filters -• Automate recurring bookings: wrap the script in a scheduler (cron/queue) to secure slots automatically -• Add notifications: send booking confirmations to Slack, email, or SMS once a reservation succeeds -• Handle multi-user accounts: support multiple credentials so a team can share automation -• Export structured results: save court/slot data as JSON, CSV, or push to a database for reporting -• Integrate with APIs: connect confirmed reservations to a calendar system (Google Calendar, Outlook) -• Enhance verification flow: add support for automatically fetching OTP codes from email/SMS inboxes -• Improve resilience: add retries, backoff, and selector caching to handle UI changes gracefully -• Template it: strip out "pickleball" wording and reuse as a boilerplate for any authenticate → filter → extract → book workflow - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/pickleball/index.js b/javascript/pickleball/index.js deleted file mode 100644 index 507b98a9..00000000 --- a/javascript/pickleball/index.js +++ /dev/null @@ -1,370 +0,0 @@ -// SF Court Booking Automation - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import inquirer from "inquirer"; -import { z } from "zod"; -async function loginToSite(stagehand, email, password) { - console.log("Logging in..."); - // Perform login sequence: each step is atomic to handle dynamic page changes. - await stagehand.act("Click the Login button"); - await stagehand.act(`Fill in the email or username field with "${email}"`); - await stagehand.act("Click the next, continue, or submit button to proceed"); - await stagehand.act(`Fill in the password field with "${password}"`); - await stagehand.act("Click the login, sign in, or submit button"); - console.log("Logged in"); -} -async function selectFilters(stagehand, activity, timeOfDay, selectedDate) { - console.log("Selecting the activity"); - // Filter by activity type first to narrow down available courts. - await stagehand.act(`Click the activites drop down menu`); - await stagehand.act(`Select the ${activity} activity`); - await stagehand.act(`Click the Done button`); - console.log(`Selecting date: ${selectedDate}`); - // Open calendar to select specific date for court booking. - await stagehand.act(`Click the date picker or calendar`); - // Parse date string to extract day number for calendar selection. - const dateParts = selectedDate.split("-"); - if (dateParts.length !== 3) { - throw new Error(`Invalid date format: ${selectedDate}. Expected YYYY-MM-DD`); - } - const dayNumber = parseInt(dateParts[2], 10); - if (isNaN(dayNumber) || dayNumber < 1 || dayNumber > 31) { - throw new Error(`Invalid day number: ${dayNumber} from date: ${selectedDate}`); - } - console.log(`Looking for day number: ${dayNumber} in calendar`); - // Click specific day number in calendar to select date. - await stagehand.act(`Click on the number ${dayNumber} in the calendar`); - console.log(`Selecting time of day: ${timeOfDay}`); - // Filter by time period to find courts available during preferred hours. - await stagehand.act(`Click the time filter or time selection dropdown`); - await stagehand.act(`Select ${timeOfDay} time period`); - await stagehand.act(`Click the Done button`); - // Apply additional filters to show only available courts that accept reservations. - await stagehand.act(`Click Available Only button`); - await stagehand.act(`Click All Facilities dropdown list`); - await stagehand.act(`Select Accept Reservations checkbox`); - await stagehand.act(`Click the Done button`); -} -async function checkAndExtractCourts(stagehand, timeOfDay) { - console.log("Checking for available courts..."); - // First observe the page to find all available court booking options. - const availableCourts = await stagehand.observe("Find all available court booking slots, time slots, or court reservation options"); - console.log(`Found ${availableCourts.length} available court options`); - // Extract structured court data using Zod schema for type safety and validation. - const courtData = await stagehand.extract("Extract all available court booking information including court names, time slots, locations, and any other relevant details", z.object({ - courts: z.array(z.object({ - name: z.string().describe("the name or identifier of the court"), - openingTimes: z.string().describe("the opening hours or operating times of the court"), - location: z.string().describe("the location or facility name"), - availability: z.string().describe("availability status or any restrictions"), - duration: z.string().nullable().describe("the duration of the court session in minutes"), - })), - })); - // Check if any courts are actually available by filtering out unavailable status messages. - let hasAvailableCourts = courtData.courts.some((court) => !court.availability.toLowerCase().includes("no free spots") && - !court.availability.toLowerCase().includes("unavailable") && - !court.availability.toLowerCase().includes("next available") && - !court.availability.toLowerCase().includes("the next available reservation")); - // If no courts available for selected time, try alternative time periods as fallback. - if (availableCourts.length === 0 || !hasAvailableCourts) { - console.log("No courts available for selected time. Trying different time periods..."); - // Generate alternative time periods to try if original selection has no availability. - const alternativeTimes = timeOfDay === "Morning" - ? ["Afternoon", "Evening"] - : timeOfDay === "Afternoon" - ? ["Morning", "Evening"] - : ["Morning", "Afternoon"]; - for (const altTime of alternativeTimes) { - console.log(`Trying ${altTime} time period...`); - // Change time filter to alternative time period and check availability. - await stagehand.act(`Click the time filter dropdown that currently shows "${timeOfDay}"`); - await stagehand.act(`Select ${altTime} from the time period options`); - await stagehand.act(`Click the Done button`); - const altAvailableCourts = await stagehand.observe("Find all available court booking slots, time slots, or court reservation options"); - console.log(`Found ${altAvailableCourts.length} available court options for ${altTime}`); - if (altAvailableCourts.length > 0) { - const altCourtData = await stagehand.extract("Extract all available court booking information including court names, time slots, locations, and any other relevant details", z.object({ - courts: z.array(z.object({ - name: z.string().describe("the name or identifier of the court"), - openingTimes: z - .string() - .describe("the opening hours or operating times of the court"), - location: z.string().describe("the location or facility name"), - availability: z.string().describe("availability status or any restrictions"), - duration: z - .string() - .nullable() - .describe("the duration of the court session in minutes"), - })), - })); - const hasAltAvailableCourts = altCourtData.courts.some((court) => !court.availability.toLowerCase().includes("no free spots") && - !court.availability.toLowerCase().includes("unavailable") && - !court.availability.toLowerCase().includes("next available") && - !court.availability.toLowerCase().includes("the next available reservation")); - // If alternative time has available courts, use that data and stop searching. - if (hasAltAvailableCourts) { - console.log(`Found actually available courts for ${altTime}!`); - courtData.courts = altCourtData.courts; - hasAvailableCourts = true; - break; - } - } - } - } - // If still no available courts found, extract final court data for display. - if (!hasAvailableCourts) { - console.log("Extracting final court information..."); - const finalCourtData = await stagehand.extract("Extract all available court booking information including court names, time slots, locations, and any other relevant details", z.object({ - courts: z.array(z.object({ - name: z.string().describe("the name or identifier of the court"), - openingTimes: z.string().describe("the opening hours or operating times of the court"), - location: z.string().describe("the location or facility name"), - availability: z.string().describe("availability status or any restrictions"), - duration: z - .string() - .nullable() - .describe("the duration of the court session in minutes"), - })), - })); - courtData.courts = finalCourtData.courts; - } - // Display all found court information to user for review and selection. - console.log("Available Courts:"); - if (courtData.courts && courtData.courts.length > 0) { - courtData.courts.forEach((court, index) => { - console.log(`${index + 1}. ${court.name}`); - console.log(` Opening Times: ${court.openingTimes}`); - console.log(` Location: ${court.location}`); - console.log(` Availability: ${court.availability}`); - if (court.duration) { - console.log(` Duration: ${court.duration} minutes`); - } - console.log(""); - }); - } - else { - console.log("No court data available to display"); - } -} -async function bookCourt(stagehand) { - console.log("Starting court booking process..."); - try { - // Select the first available court time slot for booking. - console.log("Clicking the top available time slot..."); - await stagehand.act("Click the first available time slot or court booking option"); - // Select participant from dropdown - assumes only one participant is available. - console.log("Opening participant dropdown..."); - await stagehand.act("Click the participant dropdown menu or select participant field"); - await stagehand.act("Click the only named participant in the dropdown!"); - // Complete booking process and trigger verification code request. - console.log("Clicking the book button to complete reservation..."); - await stagehand.act("Click the book, reserve, or confirm booking button"); - await stagehand.act("Click the Send Code Button"); - // Prompt user for verification code received via SMS/email for booking confirmation. - const codeAnswer = await inquirer.prompt([ - { - type: "input", - name: "verificationCode", - message: "Please enter the verification code you received:", - validate: (input) => { - if (!input.trim()) { - return "Please enter a verification code"; - } - return true; - }, - }, - ]); - console.log(`Verification code: ${codeAnswer.verificationCode}`); - // Enter verification code and confirm booking to complete reservation. - await stagehand.act(`Fill in the verification code field with "${codeAnswer.verificationCode}"`); - await stagehand.act("Click the confirm button"); - // Extract booking confirmation details to verify successful reservation. - console.log("Checking for booking confirmation..."); - const confirmation = await stagehand.extract("Extract any booking confirmation message, success notification, or reservation details", z.object({ - confirmationMessage: z.string().nullable().describe("any confirmation or success message"), - bookingDetails: z.string().nullable().describe("booking details like time, court, etc."), - errorMessage: z.string().nullable().describe("any error message if booking failed"), - })); - // Display confirmation details if booking was successful. - if (confirmation.confirmationMessage || confirmation.bookingDetails) { - console.log("Booking Confirmed!"); - if (confirmation.confirmationMessage) { - console.log(`${confirmation.confirmationMessage}`); - } - if (confirmation.bookingDetails) { - console.log(`${confirmation.bookingDetails}`); - } - } - // Display error message if booking failed. - if (confirmation.errorMessage) { - console.log("Booking Error:"); - console.log(confirmation.errorMessage); - } - } - catch (error) { - console.error("Error during court booking:", error); - throw error; - } -} -async function selectActivity() { - // Prompt user to select between Tennis and Pickleball activities. - const answers = await inquirer.prompt([ - { - type: "list", - name: "activity", - message: "Please select an activity:", - choices: [ - { name: "Tennis", value: "Tennis" }, - { name: "Pickleball", value: "Pickleball" }, - ], - default: 0, - }, - ]); - console.log(`Selected: ${answers.activity}`); - return answers.activity; -} -async function selectTimeOfDay() { - // Prompt user to select preferred time period for court booking. - const answers = await inquirer.prompt([ - { - type: "list", - name: "timeOfDay", - message: "Please select the time of day:", - choices: [ - { name: "Morning (Before 12 PM)", value: "Morning" }, - { name: "Afternoon (After 12 PM)", value: "Afternoon" }, - { name: "Evening (After 5 PM)", value: "Evening" }, - ], - default: 0, - }, - ]); - console.log(`Selected: ${answers.timeOfDay}`); - return answers.timeOfDay; -} -async function selectDate() { - // Generate date options for the next 7 days including today. - const today = new Date(); - const dateOptions = []; - for (let i = 0; i < 7; i++) { - const date = new Date(today); - date.setDate(today.getDate() + i); - const dayName = date.toLocaleDateString("en-US", { weekday: "long" }); - const monthDay = date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - }); - const fullDate = date.toISOString().split("T")[0]; - const displayName = i === 0 ? `${dayName}, ${monthDay} (Today)` : `${dayName}, ${monthDay}`; - dateOptions.push({ - name: displayName, - value: fullDate, - }); - } - // Prompt user to select from available date options. - const answers = await inquirer.prompt([ - { - type: "list", - name: "selectedDate", - message: "Please select a date:", - choices: dateOptions, - default: 0, - }, - ]); - // Format selected date for display and return ISO date string. - const selectedDate = new Date(answers.selectedDate); - const displayDate = selectedDate.toLocaleDateString("en-US", { - weekday: "long", - year: "numeric", - month: "long", - day: "numeric", - }); - console.log(`Selected: ${displayDate}`); - return answers.selectedDate; -} -async function bookTennisPaddleCourt() { - console.log("Starting tennis/paddle court booking automation in SF..."); - // Load credentials from environment variables for SF Rec & Parks login. - const email = process.env.SF_REC_PARK_EMAIL; - const password = process.env.SF_REC_PARK_PASSWORD; - const _debugMode = process.env.DEBUG === "true"; - // Collect user preferences for activity, date, and time selection. - const activity = await selectActivity(); - const selectedDate = await selectDate(); - const timeOfDay = await selectTimeOfDay(); - console.log(`Booking ${activity} courts in San Francisco for ${timeOfDay} on ${selectedDate}...`); - // Validate that required credentials are available before proceeding. - if (!email || !password) { - throw new Error("Missing SF_REC_PARK_EMAIL or SF_REC_PARK_PASSWORD environment variables"); - } - // Initialize Stagehand with Browserbase for AI-powered browser automation. - console.log("Initializing Stagehand with Browserbase"); - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1", - browserbaseSessionCreateParams: { - timeout: 900, - region: "us-west-2", - }, - }); - try { - // Start browser session and connect to SF Rec & Parks booking system. - await stagehand.init(); - console.log("Browserbase Session Started"); - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Navigate to SF Rec & Parks booking site with extended timeout for slow loading. - console.log("Navigating to court booking site..."); - await page.goto("https://www.rec.us/organizations/san-francisco-rec-park", { - waitUntil: "domcontentloaded", - timeout: 60000, - }); - // Execute booking workflow: login, filter, find courts, and complete booking. - await loginToSite(stagehand, email, password); - await selectFilters(stagehand, activity, timeOfDay, selectedDate); - await checkAndExtractCourts(stagehand, timeOfDay); - await bookCourt(stagehand); - } - catch (error) { - console.error("Error during court booking:", error); - throw error; - } - finally { - // Always close browser session to release resources and clean up. - await stagehand.close(); - console.log("\nBrowser session closed"); - } -} -async function main() { - // Display welcome message and explain the automation workflow to user. - console.log("Welcome to SF Court Booking Automation!"); - console.log(""); - console.log("This tool automates tennis and pickleball court bookings in San Francisco."); - console.log("Here's what we'll do:"); - console.log(""); - console.log("1. Navigate to https://www.rec.us/organizations/san-francisco-rec-park"); - console.log("2. Use automated login with your credentials"); - console.log("3. Select your preferred activity, date, and time"); - console.log("4. Find and book available courts automatically"); - console.log("5. Handle verification codes and confirmation"); - console.log(""); - try { - // Execute the complete court booking automation workflow. - await bookTennisPaddleCourt(); - console.log("Court booking completed successfully!"); - console.log("Your court has been reserved. Check your email for confirmation details."); - } - catch (error) { - console.log("Failed to complete court booking"); - console.log(`Error: ${error}`); - process.exit(1); - } -} -main().catch((err) => { - console.error("Application error:", err); - console.log("Check your environment variables"); - process.exit(1); -}); diff --git a/javascript/polymarket-research/README.md b/javascript/polymarket-research/README.md deleted file mode 100644 index aa5ccd81..00000000 --- a/javascript/polymarket-research/README.md +++ /dev/null @@ -1,63 +0,0 @@ -# Stagehand + Browserbase: Market Research Automation - -## AT A GLANCE - -- Goal: demonstrate how to automate market research on prediction markets using Stagehand. -- Navigation & Search: automate website navigation, search interactions, and result selection. -- Data Extraction: extract structured market data with validated output using Zod schemas. -- Practical Example: research and extract current odds from Polymarket prediction markets. - -## GLOSSARY - -- act: perform UI actions from a natural language prompt (type, click, navigate). - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from web pages into validated objects. - Docs → https://docs.stagehand.dev/basics/extract -- schema: a Zod definition that enforces data types, optional fields, and validation rules. - Docs → https://zod.dev/ -- market research automation: navigate to prediction markets, search for specific topics, and extract current odds. -- structured data extraction: convert unstructured web content into typed, validated objects. - -## QUICKSTART - -1. cd polymarket-research -2. npm install -3. cp ../../.env.example .env (or create .env with BROWSERBASE_API_KEY) -4. Add your Browserbase API key to .env -5. npm start - -## EXPECTED OUTPUT - -- Navigates to Polymarket prediction market website -- Searches for specified market query -- Selects the first search result -- Extracts structured market data including odds, prices, and volume -- Returns typed object with market information - -## COMMON PITFALLS - -- "Cannot find module 'dotenv'": ensure npm install ran successfully -- Missing API key: verify .env is loaded and file is not committed -- Search results not found: check if the market exists or if website structure has changed -- Schema validation errors: ensure extracted data matches Zod schema structure - -## USE CASES - -• Market tracking: automate monitoring of prediction market odds for specific events or topics. -• Research aggregation: collect current prices and volume data from multiple prediction markets. -• Trading automation: extract structured market data for integration with trading or analysis systems. -• Sentiment analysis: track how prediction markets assess the likelihood of future events. - -## NEXT STEPS - -• Parameterize search queries: make the search term configurable via environment variables or prompts. -• Multi-market extraction: extend the flow to search and extract data from multiple markets in parallel. -• Historical tracking: persist extracted data over time to track market movement and trends. -• Price alerts: add logic to monitor specific price thresholds and send notifications. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/polymarket-research/index.js b/javascript/polymarket-research/index.js deleted file mode 100644 index 433a41ef..00000000 --- a/javascript/polymarket-research/index.js +++ /dev/null @@ -1,76 +0,0 @@ -// Stagehand + Browserbase: Polymarket prediction market research - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -/** - * Searches Polymarket for a prediction market and extracts current odds, pricing, and volume data. - * Uses AI-powered browser automation to navigate and interact with the site. - */ -async function main() { - console.log("Starting Polymarket research automation..."); - // Initialize Stagehand with Browserbase for cloud-based browser automation - // Using BROWSERBASE environment to run in cloud rather than locally - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1", - }); - try { - // Initialize browser session - console.log("Initializing browser session..."); - await stagehand.init(); - console.log("Stagehand session started successfully"); - // Provide live session URL for debugging and monitoring - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionID}`); - const page = stagehand.context.pages()[0]; - // Navigate to Polymarket - console.log("Navigating to: https://polymarket.com/"); - await page.goto("https://polymarket.com/"); - console.log("Page loaded successfully"); - // Click the search box to trigger search dropdown - console.log("Clicking the search box at the top of the page"); - await stagehand.act("click the search box at the top of the page"); - // Type search query - const searchQuery = "Elon Musk unfollow Trump"; - console.log(`Typing '${searchQuery}' into the search box`); - await stagehand.act(`type '${searchQuery}' into the search box`); - // Click the first market result from the search dropdown - console.log("Selecting first market result from search dropdown"); - await stagehand.act("click the first market result from the search dropdown"); - console.log("Market page loaded"); - // Extract market data using AI to parse the structured information - console.log("Extracting market information..."); - const marketData = await stagehand.extract("Extract the current odds and market information for the prediction market", z.object({ - marketTitle: z.string().optional().describe("the title of the market"), - currentOdds: z.string().optional().describe("the current odds or probability"), - yesPrice: z.string().optional().describe("the yes price"), - noPrice: z.string().optional().describe("the no price"), - totalVolume: z.string().optional().describe("the total trading volume"), - priceChange: z.string().optional().describe("the recent price change"), - })); - console.log("Market data extracted successfully:"); - console.log(JSON.stringify(marketData, null, 2)); - } - catch (error) { - console.error("Error during market research:", error); - // Provide helpful troubleshooting information - console.error("\nCommon issues:"); - console.error("1. Check .env file has BROWSERBASE_API_KEY"); - console.error("2. Ensure internet access and https://polymarket.com is accessible"); - console.error("3. Verify Browserbase account has sufficient credits"); - throw error; - } - finally { - // Clean up browser session - console.log("Closing browser session..."); - await stagehand.close(); - console.log("Session closed successfully"); - } -} -main().catch((err) => { - console.error("Application error:", err); - process.exit(1); -}); diff --git a/javascript/proxies-weather/README.md b/javascript/proxies-weather/README.md deleted file mode 100644 index 61bab727..00000000 --- a/javascript/proxies-weather/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Stagehand + Browserbase: Weather Proxy Demo - -## AT A GLANCE - -- Goal: demonstrate geolocation proxies by fetching location-specific weather data from multiple cities using Browserbase's proxy infrastructure. -- Uses geolocation proxies to route traffic through specific geographic locations (New York, London, Tokyo, São Paulo). -- Extracts structured weather data using Stagehand's extraction capabilities with Zod schema validation. -- Sequential processing shows how different proxy locations return different weather data from the same website. -- Docs → https://docs.browserbase.com/features/proxies - -## GLOSSARY - -- geolocation proxies: route traffic through specific geographic locations (city, country, state) to access location-specific content - Docs → https://docs.browserbase.com/features/proxies#set-proxy-geolocation -- extract: extract structured data from web pages using natural language instructions and Zod schemas - Docs → https://docs.stagehand.dev/basics/extract -- proxies: Browserbase's managed proxy infrastructure supporting 201+ countries for geolocation-based routing - Docs → https://docs.browserbase.com/features/proxies - -## QUICKSTART - -1. cd proxies-weather-template -2. pnpm install -3. pnpm install @browserbasehq/sdk @browserbasehq/stagehand zod -4. cp .env.example .env -5. Add your Browserbase API key to .env -6. pnpm start - -## EXPECTED OUTPUT - -- Creates Browserbase sessions with geolocation proxies for each location (New York, London, Tokyo, São Paulo) -- Displays session URLs for each location for monitoring -- Navigates to weather service (windy.com) through location-specific proxies -- Extracts temperature and unit for each location -- Displays formatted results showing different weather data based on proxy location -- Demonstrates how geolocation proxies enable location-specific content access - -## COMMON PITFALLS - -- Browserbase Developer plan or higher is required to use proxies -- "Cannot find module": ensure all dependencies are installed (@browserbasehq/sdk, @browserbasehq/stagehand, zod) -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Geolocation fields are case-insensitive (city, country, state can be any case) -- State is required for US locations to ensure accurate geolocation -- ERR_TUNNEL_CONNECTION_FAILED: indicates either a temporary proxy hiccup or a site unsupported by our built-in proxies - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/proxies-weather/index.js b/javascript/proxies-weather/index.js deleted file mode 100644 index 3b62ffe7..00000000 --- a/javascript/proxies-weather/index.js +++ /dev/null @@ -1,137 +0,0 @@ -// Stagehand + Browserbase: Weather Proxy Demo - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// Fetches weather data for a specific location using geolocation proxies -// Configures Stagehand with location-specific proxy, navigates to weather site, -// and extracts temperature data using Stagehand's structured extraction capabilities -async function getWeatherForLocation(geolocation) { - const cityName = geolocation.city.replace(/_/g, " "); - console.log(`\n=== Getting weather for ${cityName}, ${geolocation.country} ===`); - // Initialize Stagehand with geolocation proxy configuration - // This ensures all browser traffic routes through the specified geographic location - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - browserbaseSessionCreateParams: { - proxies: [ - { - type: "browserbase", // Use Browserbase's managed proxy infrastructure for reliable geolocation routing - geolocation: { - city: geolocation.city, // City name (case-insensitive, e.g., "NEW_YORK", "new_york", "New York" all work) - country: geolocation.country, // ISO country code (case-insensitive, e.g., "US", "us", "gb", "GB" all work) - ...(geolocation.state && { state: geolocation.state }), // State required for US locations (case-insensitive) - }, - }, - ], - }, - }); - try { - // Initialize browser session to start automation - console.log(`Initializing Stagehand for ${cityName}...`); - await stagehand.init(); - console.log(`Stagehand initialized successfully for ${cityName}`); - const page = stagehand.context.pages()[0]; - // Navigate to weather service - geolocation proxy ensures location-specific weather data - console.log(`Navigating to weather service for ${cityName}...`); - await page.goto("https://www.windy.com/", { - waitUntil: "networkidle", // Wait for network to be idle to ensure weather data is loaded - }); - console.log(`Page loaded for ${cityName}`); - // Wait a bit for weather data to render - await new Promise((resolve) => setTimeout(resolve, 2000)); - // Extract structured temperature data using Stagehand and Zod schema for type safety - console.log(`Extracting temperature data for ${cityName}...`); - const extractResult = await stagehand.extract("Extract the current temperature and its unit", z.object({ - temperature: z.number().describe("The current temperature value"), - unit: z.string().describe("The temperature unit)"), - })); - console.log(`Successfully extracted weather data for ${cityName}: ${extractResult.temperature} ${extractResult.unit}`); - // Close Stagehand session to release resources - await stagehand.close(); - return { - city: cityName, - country: geolocation.country, - temperature: extractResult.temperature, - unit: extractResult.unit, - }; - } - catch (error) { - await stagehand.close(); - console.error(`Error getting weather for ${cityName}:`, error); - return { - city: cityName, - country: geolocation.country, - temperature: 0, - unit: "", - error: error instanceof Error ? error.message : String(error), - }; - } -} -// Displays formatted weather results for all processed locations -// Shows successful results with temperature data or error messages for failed locations -function displayResults(results) { - console.log("\n=== Weather Results ==="); - for (const result of results) { - if (result.error) { - console.log(`${result.city}, ${result.country}: Error - ${result.error}`); - } - else { - console.log(`${result.city}, ${result.country}: ${result.temperature} ${result.unit}`); - } - } -} -// Main orchestration function: processes multiple locations sequentially using geolocation proxies -// Demonstrates how different proxy locations return different weather data from the same website -async function main() { - // Define locations to test - demonstrating the power of geolocation proxies - // Each location will route traffic through its respective geographic proxy to get location-specific weather - // Note: All geolocation fields (city, country, state) are case-insensitive - const locations = [ - { - city: "NEW_YORK", - state: "NY", // State required for US locations (case-insensitive) - country: "US", - }, - { - city: "LONDON", - country: "GB", - }, - { - city: "TOKYO", - country: "JP", - }, - { - city: "SAO_PAULO", - country: "BR", - }, - ]; - console.log("=== Weather Proxy Demo - Running Sequentially ===\n"); - console.log(`Processing ${locations.length} locations with geolocation proxies...`); - console.log("Each location will use a different proxy to fetch location-specific weather data\n"); - // Collect all results for final summary - const results = []; - // Run each location sequentially to show different weather based on proxy location - // Sequential processing ensures clear demonstration of proxy-based location differences - for (let i = 0; i < locations.length; i++) { - const location = locations[i]; - console.log(`\n[${i + 1}/${locations.length}] Processing ${location.city}, ${location.country}...`); - const result = await getWeatherForLocation(location); - results.push(result); - } - // Display all results in formatted summary - displayResults(results); - console.log("\n=== All locations completed ==="); -} -main().catch((err) => { - console.error("Application error:", err); - console.error("Common issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify geolocation proxy locations are valid (see https://docs.browserbase.com/features/proxies)"); - console.error(" - Ensure locations array is properly configured"); - console.error("Docs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/proxies-weather/package.json b/javascript/proxies-weather/package.json deleted file mode 100644 index 1ddae10d..00000000 --- a/javascript/proxies-weather/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "proxies-weather-template", - "type": "module", - "scripts": { - "start": "node index.js" - }, - "dependencies": { - "@browserbasehq/stagehand": "latest", - "dotenv": "^16.4.7", - "zod": "latest" - } -} diff --git a/javascript/proxies/README.md b/javascript/proxies/README.md deleted file mode 100644 index f3c768eb..00000000 --- a/javascript/proxies/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Browserbase Proxy Testing Script - -## AT A GLANCE - -- Goal: demonstrate different proxy configurations with Browserbase sessions. - -## GLOSSARY - -- Proxies: Browserbase's default proxy rotation for enhanced privacy - Docs → https://docs.browserbase.com/features/proxies - -## QUICKSTART - -1. cd proxies-template -2. npm install -3. npm install @browserbasehq/sdk playwright-core -4. cp .env.example .env -5. Add your Browserbase API key to .env -6. npm start - -## EXPECTED OUTPUT - -- Tests built-in proxy rotation -- Tests geolocation-specific proxies (New York) -- Tests custom external proxies (commented out by default) -- Displays IP information and geolocation data for each test -- Shows how different proxy configurations affect your apparent location - -## COMMON PITFALLS - -- Browserbase Developer plan or higher is required to use proxies -- "Cannot find module": ensure all dependencies are installed -- Missing credentials: verify .env contains BROWSERBASE_API_KEY -- Custom proxy errors: verify external proxy server credentials and availability - -## USE CASES - -• Geo-testing: Verify location-specific content, pricing, or compliance banners. -• Scraping at scale: Rotate IPs to reduce blocks and increase CAPTCHA success rates. -• Custom routing: Mix built-in and external proxies, or apply domain-based rules for compliance. - -## NEXT STEPS - -• Add routing rules: Configure domainPattern to direct specific sites through targeted proxies. -• Test multiple geos: Compare responses from different cities/countries and log differences. -• Improve reliability: Add retries and fallbacks to handle proxy errors like ERR_TUNNEL_CONNECTION_FAILED. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/proxies/index.js b/javascript/proxies/index.js deleted file mode 100644 index 88929c35..00000000 --- a/javascript/proxies/index.js +++ /dev/null @@ -1,111 +0,0 @@ -// Browserbase Proxy Testing Script - See README.md for full documentation -import { chromium } from "playwright-core"; -import { Browserbase } from "@browserbasehq/sdk"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -import dotenv from "dotenv"; -dotenv.config(); -const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY }); -async function createSessionWithBuiltInProxies() { - // Use Browserbase's default proxy rotation for enhanced privacy and IP diversity. - const session = await bb.sessions.create({ - proxies: true, // Enables automatic proxy rotation across different IP addresses. - }); - return session; -} -async function createSessionWithGeoLocation() { - // Route traffic through specific geographic location to test location-based restrictions. - const session = await bb.sessions.create({ - proxies: [ - { - type: "browserbase", // Use Browserbase's managed proxy infrastructure. - geolocation: { - city: "NEW_YORK", // Simulate traffic from New York for testing geo-specific content. - state: "NY", // See https://docs.browserbase.com/features/proxies for more geolocation options. - country: "US", - }, - }, - ], - }); - return session; -} -async function createSessionWithCustomProxies() { - // Use external proxy servers for custom routing or specific proxy requirements. - const session = await bb.sessions.create({ - proxies: [ - { - type: "external", // Connect to your own proxy server infrastructure. - server: "http://...", // Your proxy server endpoint. - username: "user", // Authentication credentials for proxy access. - password: "pass", - }, - ], - }); - return session; -} -async function testSession(sessionFunction, sessionName) { - console.log(`\n=== Testing ${sessionName} ===`); - // Create session with specific proxy configuration to test different routing scenarios. - const session = await sessionFunction(); - console.log("Session URL: https://browserbase.com/sessions/" + session.id); - // Connect to browser via CDP to control the session programmatically. - const browser = await chromium.connectOverCDP(session.connectUrl); - const defaultContext = browser.contexts()[0]; - if (!defaultContext) { - throw new Error("No default context found"); - } - const page = defaultContext.pages()[0]; - if (!page) { - throw new Error("No page found in default context"); - } - // Initialize Stagehand for structured data extraction - const stagehand = new Stagehand({ - env: "BROWSERBASE", - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "openai/gpt-4.1", - browserbaseSessionID: session.id, // Use the existing Browserbase session - }); - try { - // Initialize Stagehand - await stagehand.init(); - const stagehandPage = stagehand.context.pages()[0]; - // Navigate to IP info service to verify proxy location and IP address. - await stagehandPage.goto("https://ipinfo.io/json", { - waitUntil: "domcontentloaded", - }); - // Extract structured IP and location data using Stagehand and Zod schema - const geoInfo = await stagehand.extract("Extract all IP information and geolocation data from the JSON response", z.object({ - ip: z.string().optional().describe("The IP address"), - city: z.string().optional().describe("The city name"), - region: z.string().optional().describe("The state or region"), - country: z.string().optional().describe("The country code"), - loc: z.string().optional().describe("The latitude and longitude coordinates"), - timezone: z.string().optional().describe("The timezone"), - org: z.string().optional().describe("The organization or ISP"), - postal: z.string().optional().describe("The postal code"), - hostname: z.string().optional().describe("The hostname if available"), - })); - console.log("Geo Info:", JSON.stringify(geoInfo, null, 2)); - // Close Stagehand session - await stagehand.close(); - } - catch (error) { - console.error("Error during Stagehand extraction:", error); - } - // Close browser to release resources and end the test session. - await browser.close(); - console.log(`${sessionName} test completed`); -} -async function main() { - // Test 1: Built-in proxies - Verify default proxy rotation works and shows different IPs. - await testSession(createSessionWithBuiltInProxies, "Built-in Proxies"); - // Test 2: Geolocation proxies - Confirm traffic routes through specified location (New York). - await testSession(createSessionWithGeoLocation, "Geolocation Proxies (New York)"); - // Test 3: Custom external proxies - Enable if you have a custom proxy server set up. - // await testSession(createSessionWithCustomProxies, "Custom External Proxies"); - console.log("\n=== All tests completed ==="); -} -main(); diff --git a/javascript/sec-filing-research/.env.example b/javascript/sec-filing-research/.env.example deleted file mode 100644 index 12472549..00000000 --- a/javascript/sec-filing-research/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -# Browserbase credentials (required) -# Get these from https://www.browserbase.com/settings -BROWSERBASE_API_KEY=your-browserbase-api-key diff --git a/javascript/sec-filing-research/README.md b/javascript/sec-filing-research/README.md deleted file mode 100644 index b9262797..00000000 --- a/javascript/sec-filing-research/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Stagehand + Browserbase: SEC Filing Research - -## AT A GLANCE - -- Goal: automate searching SEC EDGAR for a company and extracting recent filing metadata (type, date, description, accession number, file number). -- Search: supports company name, ticker symbol, or CIK number (e.g. "Apple Inc", "AAPL", "0000320193"). -- Data Extraction: uses Stagehand act/extract with Zod schemas to navigate SEC.gov and pull structured filing data. -- Output: company name, CIK, and a configurable number of most recent filings, printed as summary and JSON. - -## GLOSSARY - -- act: perform UI actions from a natural language prompt (click, type, submit). - Docs → https://docs.stagehand.dev/basics/act -- extract: pull structured data from web pages into validated objects using a Zod schema. - Docs → https://docs.stagehand.dev/basics/extract -- schema: Zod definition for filing and company info; enforces types and validation. - Docs → https://zod.dev/ -- SEC EDGAR: SEC’s company and filing search and filing system. - https://www.sec.gov/edgar/searchedgar/companysearch.html -- CIK: Central Index Key — unique numeric identifier for each company in EDGAR. - -## QUICKSTART - -1. cd sec-filing-research -2. npm install -3. cp .env.example .env -4. Add BROWSERBASE_API_KEY to .env -5. (Optional) Edit SEARCH_QUERY and NUM_FILINGS in index.ts -6. npm start - -## EXPECTED OUTPUT - -- Initializes Stagehand session with Browserbase and shows live view URL -- Navigates to SEC EDGAR company search -- Enters search query, submits, and selects the matching company -- Extracts company name and CIK from the filings page -- Extracts the N most recent filings (type, date, description, accession number, file number) -- Logs SEC FILING METADATA summary and per-filing details -- Outputs full result as JSON -- Closes session cleanly - -## COMMON PITFALLS - -- "Cannot find module": run npm install in sec-filing-research -- Missing credentials: ensure .env has BROWSERBASE_API_KEY -- No company match: use a valid company name, ticker, or CIK; SEC search is case-sensitive for some queries -- Extraction errors: SEC page layout changes can break selectors; check live view and adjust act/extract prompts if needed -- Rate limiting: avoid excessive runs; SEC may throttle heavy or automated traffic - -## USE CASES - -• Compliance and due diligence: quickly pull recent 10-K, 10-Q, 8-K metadata for a list of companies. -• Research pipelines: feed accession numbers into downstream tools to fetch full filings or parse specific sections. -• Monitoring: periodically extract latest filings for watchlists and alert on new filings. -• Data enrichment: attach official company name and CIK to internal records using SEC as source of truth. - -## NEXT STEPS - -• Parameterize search: read SEARCH_QUERY and NUM_FILINGS from env or CLI for batch runs. -• Fetch full filings: use accession numbers with SEC’s full-text filing URLs or APIs to download documents. -• Multiple companies: loop over a list of tickers/names and aggregate results into a single report or JSON. -• Filter by type: restrict to 10-K/10-Q/8-K or other form types in the extract step or in post-processing. - -## HELPFUL RESOURCES - -📚 Stagehand Docs: https://docs.stagehand.dev/v3/first-steps/introduction -🎮 Browserbase: https://www.browserbase.com -💡 Try it out: https://www.browserbase.com/playground -🔧 Templates: https://www.browserbase.com/templates -📧 Need help? support@browserbase.com -💬 Discord: http://stagehand.dev/discord diff --git a/javascript/sec-filing-research/index.js b/javascript/sec-filing-research/index.js deleted file mode 100644 index 91b62dad..00000000 --- a/javascript/sec-filing-research/index.js +++ /dev/null @@ -1,136 +0,0 @@ -// Stagehand + Browserbase: SEC Filing Downloader - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod"; -// Search query - can be company name, ticker symbol, or CIK number -// Examples: "Apple Inc", "AAPL", "0000320193" -const SEARCH_QUERY = "Apple Inc"; -// Number of filings to retrieve -const NUM_FILINGS = 5; -// Schema for extracted filing data -const FilingSchema = z.object({ - filings: z.array(z.object({ - type: z.string().describe("Filing type (e.g., 10-K, 10-Q, 8-K)"), - date: z.string().describe("Filing date in YYYY-MM-DD format"), - description: z.string().describe("Full description of the filing"), - accessionNumber: z.string().describe("SEC accession number"), - fileNumber: z.string().optional().describe("File/Film number"), - })), -}); -// Schema for company info extraction -const CompanyInfoSchema = z.object({ - companyName: z.string().describe("Official company name"), - cik: z.string().describe("Central Index Key (CIK) number"), -}); -/** - * Searches SEC EDGAR for a company (by name, ticker, or CIK) and extracts - * recent filing metadata: type, date, description, accession number, file number. - * Uses Stagehand + Browserbase for AI-powered browser automation. - */ -async function main() { - console.log("Starting SEC Filing Downloader..."); - console.log(`Search query: ${SEARCH_QUERY}`); - console.log(`Retrieving ${NUM_FILINGS} most recent filings\n`); - // Initialize Stagehand with Browserbase for cloud-based browser automation - const stagehand = new Stagehand({ - env: "BROWSERBASE", - apiKey: process.env.BROWSERBASE_API_KEY, - verbose: 1, - // 0 = errors only, 1 = info, 2 = debug - // (When handling sensitive data like passwords or API keys, set verbose: 0 to prevent secrets from appearing in logs.) - // https://docs.stagehand.dev/configuration/logging - model: "google/gemini-2.5-flash", - }); - try { - // Initialize browser session - await stagehand.init(); - console.log("Stagehand initialized successfully!"); - const page = stagehand.context.pages()[0]; - // Provide live session URL for debugging and monitoring - if (stagehand.browserbaseSessionId) { - console.log(`Live View: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - } - // Navigate to modern SEC EDGAR company search page - console.log("\nNavigating to SEC EDGAR..."); - await page.goto("https://www.sec.gov/edgar/searchedgar/companysearch.html", { - waitUntil: "domcontentloaded", - }); - // Enter search query in the Company and Person Lookup search box - console.log(`Searching for: ${SEARCH_QUERY}`); - await stagehand.act(`Click on the Company and Person Lookup search textbox`); - await stagehand.act(`Type "${SEARCH_QUERY}" in the search field`); - // Submit search to load company results - await stagehand.act("Click the search submit button"); - // Select the matching company from results to view their filings page - console.log("Selecting the correct company from results..."); - await stagehand.act(`Click on "${SEARCH_QUERY}" in the search results to view their filings`); - // Extract company information from the filings page - console.log("Extracting company information..."); - let companyInfo = { companyName: SEARCH_QUERY, cik: "Unknown" }; - try { - const extractedInfo = await stagehand.extract("Extract the company name and CIK number from the page header or company information section. The CIK should be a numeric identifier.", CompanyInfoSchema); - if (extractedInfo && extractedInfo.companyName) { - companyInfo = extractedInfo; - } - } - catch (error) { - // Fallback to search query if extraction fails (e.g. page layout differs) - console.log("Could not extract company info, using search query as company name:", error); - } - // Extract filing metadata from the filings table using structured schema - console.log(`Extracting the ${NUM_FILINGS} most recent filings...`); - const filingsData = await stagehand.extract(`Extract the ${NUM_FILINGS} most recent SEC filings from the filings table. For each filing, get: the filing type (column: Filings, like 10-K, 10-Q, 8-K), the filing date (column: Filing Date), description, accession number (from the link or description), and file/film number if shown.`, FilingSchema); - // Build result object with company info and normalized filing list - const result = { - company: companyInfo.companyName, - cik: companyInfo.cik, - searchQuery: SEARCH_QUERY, - filings: filingsData.filings.slice(0, NUM_FILINGS).map((f) => ({ - ...f, - fileNumber: f.fileNumber || "", - })), - }; - // Log summary and per-filing details to console - console.log("\n" + "=".repeat(60)); - console.log("SEC FILING METADATA"); - console.log("=".repeat(60)); - console.log(`Company: ${result.company}`); - console.log(`CIK: ${result.cik}`); - console.log(`Search Query: ${result.searchQuery}`); - console.log(`Filings Retrieved: ${result.filings.length}`); - console.log("=".repeat(60)); - // Display each filing's type, date, description, accession number, file number - result.filings.forEach((filing, index) => { - console.log(`\nFiling ${index + 1}:`); - console.log(` Type: ${filing.type}`); - console.log(` Date: ${filing.date}`); - console.log(` Description: ${filing.description.substring(0, 80)}${filing.description.length > 80 ? "..." : ""}`); - console.log(` Accession Number: ${filing.accessionNumber}`); - console.log(` File Number: ${filing.fileNumber}`); - }); - // Output full result as JSON for piping or integration - console.log("\n" + "=".repeat(60)); - console.log("JSON OUTPUT:"); - console.log("=".repeat(60)); - console.log(JSON.stringify(result, null, 2)); - } - catch (error) { - console.error("Error during SEC filing extraction:", error); - throw error; - } - finally { - // Always close session to release resources and clean up - await stagehand.close(); - console.log("\nSession closed successfully"); - } -} -main().catch((err) => { - console.error("Application error:", err); - // Provide helpful troubleshooting information - console.error("\nCommon issues:"); - console.error(" - Check .env file has BROWSERBASE_API_KEY"); - console.error(" - Verify internet connection and SEC website accessibility"); - console.error(" - Ensure the search query is valid (company name, ticker, or CIK)"); - console.error("\nDocs: https://docs.stagehand.dev/v3/first-steps/introduction"); - process.exit(1); -}); diff --git a/javascript/sec-filing-research/package.json b/javascript/sec-filing-research/package.json deleted file mode 100644 index 7b37a405..00000000 --- a/javascript/sec-filing-research/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "sec-filing-research", - "version": "1.0.0", - "description": "Stagehand + Browserbase: SEC Filing Research - Extract company and filing metadata from SEC EDGAR", - "type": "module", - "main": "index.js", - "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" - }, - "dependencies": { - "@browserbasehq/stagehand": "latest", - "dotenv": "^16.4.5", - "zod": "^3.23.8" - } -} diff --git a/javascript/website-link-tester/README.md b/javascript/website-link-tester/README.md deleted file mode 100644 index b529b288..00000000 --- a/javascript/website-link-tester/README.md +++ /dev/null @@ -1,103 +0,0 @@ -## Stagehand + Browserbase: Website Link Tester - -### AT A GLANCE - -- **Goal**: Crawl a website’s homepage, collect all links, and verify that each link loads successfully and matches its link text. -- **Link extraction**: Uses `Stagehand.extract()` with a Zod schema to pull all links and their visible text from the homepage. -- **Content verification**: Opens each link and uses AI to assess whether the page content matches what the link text suggests. -- **Social link handling**: Detects social media domains and only checks that they load (skipping full content verification). -- **Batch processing**: Processes links in batches controlled by `MAX_CONCURRENT_LINKS` (sequential by default, can be made concurrent). - -### GLOSSARY - -- **extract**: extract structured data from web pages using natural language instructions - Docs → `https://docs.stagehand.dev/basics/extract` -- **concurrent sessions**: run multiple browser sessions at the same time for faster batch processing - Docs → `https://docs.browserbase.com/guides/concurrency-rate-limits` - -### QUICKSTART - -1. **cd into the template** - - `cd typescript/website-link-tester` -2. **Install dependencies** - - `npm install` -3. **Configure environment** - - Copy your root `.env.example` to `.env` if needed - - Add your Browserbase API key: - - `BROWSERBASE_API_KEY` -4. **Run the script** - - `npm start` - -### EXPECTED OUTPUT - -- **Initial setup** - - Initializes a Stagehand session with Browserbase - - Prints a live session link for monitoring the browser in real time -- **Link collection** - - Navigates to the configured `URL` (default: `https://www.browserbase.com`) - - Extracts all links and their link text from the homepage - - Logs total link count and unique link count after de-duplication -- **Verification** - - Verifies links in batches using `MAX_CONCURRENT_LINKS` - - For each link: - - Confirms the page loads successfully - - For non-social links, extracts: - - `pageTitle` - - `contentMatches` (boolean) - - short `assessment` (max ~8 words) - - For social links, confirms load and skips detailed content checks -- **Final report** - - Prints a JSON summary including: - - total links - - successful vs failed checks - - per-link details (title, match flag, assessment, and any errors) - - Always closes browser sessions cleanly - -### COMMON PITFALLS - -- **Missing credentials** - - Ensure `.env` contains `BROWSERBASE_API_KEY` -- **Concurrency limits** - - `MAX_CONCURRENT_LINKS > 1` will open multiple browsers in parallel and requires a Browserbase plan that supports concurrency (Startup or Developer or higher) -- **Slow or failing pages** - - Some links may be slow, geo-restricted, or require auth/consent; these can produce timeouts or error messages in the results -- **Dynamic or JS-heavy sites** - - Heavily scripted pages might take longer to reach `"domcontentloaded"`; adjust timeouts if needed -- **Social / external redirects** - - Social links and complex redirect chains may succeed in loading but not be fully verifiable for content; these are marked as special cases - -### USE CASES - -- **Regression testing**: Quickly verify that all key marketing and product links on your homepage still resolve correctly after a deployment. -- **Content QA**: Detect mismatches between link text and destination page content (e.g., wrong page wired to a CTA). -- **SEO and UX audits**: Find broken or misdirected links that can harm search rankings or user experience. -- **Monitoring**: Run this periodically to flag link issues across your marketing site or documentation hub. - -### TUNING BATCH SIZE & CONCURRENCY - -- **`MAX_CONCURRENT_LINKS` in `index.ts`** - - Default: `1` → sequential link verification (works on all plans) - - Set to `> 1` → more concurrent link verifications per batch (requires higher Browserbase concurrency limits) -- **Using Semaphores for advanced control** - - For more fine-grained control over concurrency (e.g., rate limiting, prioritization, or per-domain limits), you can wrap link verification in a **Semaphore** or similar concurrency primitive. - - This lets you: - - Cap how many verifications run at once - - Smooth out spikes in resource usage - - Apply different limits for external vs internal links if desired - -### NEXT STEPS - -- **Filter link scopes**: Limit verification to specific path prefixes (e.g., only `/docs` or `/blog`) or exclude certain domains. -- **Recursive crawling**: Start from the homepage, follow internal links to secondary/tertiary pages, and cascade link discovery deeper into the site to build a more complete link map. -- **Alerting & monitoring**: Integrate with Slack, email, or logging tools to notify when links start failing. -- **CI integration**: Run this in CI and fail builds when a critical link (e.g., signup, pricing, docs) breaks. -- **Richer assessments**: Expand the extraction schema to capture additional metadata (e.g., HTTP status code, canonical URL, or key headings). - -### HELPFUL RESOURCES - -- 📚 **Stagehand Docs**: `https://docs.stagehand.dev/v3/first-steps/introduction` -- 🎮 **Browserbase**: `https://www.browserbase.com` -- 💡 **Try it out**: `https://www.browserbase.com/playground` -- 🔧 **Templates**: `https://www.browserbase.com/templates` -- 📧 **Need help?**: `support@browserbase.com` -- 💬 **Discord**: `http://stagehand.dev/discord` diff --git a/javascript/website-link-tester/index.js b/javascript/website-link-tester/index.js deleted file mode 100644 index 0cfb863f..00000000 --- a/javascript/website-link-tester/index.js +++ /dev/null @@ -1,228 +0,0 @@ -// Stagehand + Browserbase: Website Link Tester - See README.md for full documentation -import "dotenv/config"; -import { Stagehand } from "@browserbasehq/stagehand"; -import { z } from "zod/v3"; -// Base URL whose links we want to crawl and verify -const URL = "https://www.browserbase.com"; -// Maximum number of links to verify concurrently. -// Default: 1 (sequential processing - works on all plans) -// Set to > 1 for more concurrent link verification (requires Startup or Developer plan or higher). -// For more advanced concurrency control (rate limiting, prioritization, per-domain caps), -// you can also wrap link verification in a Semaphore or similar concurrency primitive. -const MAX_CONCURRENT_LINKS = 1; -// Domains that are treated as social links; we only check that they load, -// and skip content verification because they often require auth/consent flows. -const SOCIAL_DOMAINS = [ - "twitter.com", - "x.com", - "facebook.com", - "linkedin.com", - "instagram.com", - "youtube.com", - "tiktok.com", - "reddit.com", - "discord.com", -]; -// Creates a preconfigured Stagehand instance for Browserbase sessions -function createStagehand() { - return new Stagehand({ - env: "BROWSERBASE", - verbose: 0, - model: "google/gemini-2.5-pro", - }); -} -// Removes duplicate links by URL while preserving the first occurrence -function deduplicateLinks(extractedLinks) { - const map = new Map(); - for (const link of extractedLinks.links) { - if (!map.has(link.url)) { - map.set(link.url, link); - } - } - return Array.from(map.values()); -} -/** - * Opens the homepage and uses Stagehand `extract()` to collect all links. - * Returns a de-duplicated array of link objects that we will later verify. - */ -async function collectLinksFromHomepage() { - const stagehand = createStagehand(); - try { - // Start a fresh browser session for link collection - await stagehand.init(); - console.log(`Watch live: https://browserbase.com/sessions/${stagehand.browserbaseSessionId}`); - const page = stagehand.context.pages()[0]; - // Navigate to the base URL where we will harvest links - console.log(`Navigating to ${URL}...`); - await page.goto(URL); - console.log(`Successfully loaded ${URL}. Extracting links...`); - const extractedLinks = await stagehand.extract("extract all links on the page with their link text", z.object({ - links: z.array(z.object({ - url: z.string().url(), - linkText: z.string(), - })), - })); - // Remove duplicate URLs and log both raw and unique counts for visibility - const uniqueLinks = deduplicateLinks(extractedLinks); - console.log(`All links on the page (${extractedLinks.links.length} total, ${uniqueLinks.length} unique):`); - console.log(JSON.stringify({ links: uniqueLinks }, null, 2)); - console.log("\nClosing initial browser..."); - await stagehand.close(); - console.log("Initial browser closed"); - return uniqueLinks; - } - catch (error) { - console.error("Error while collecting links:", error); - // Ensure the browser is closed even when link collection fails - await stagehand.close(); - throw error; - } -} -/** - * Verifies a single link by opening it in a dedicated browser session. - * - Confirms the page loads successfully. - * - For non-social links, uses `extract()` to check that the page content - * matches what the link text suggests. - */ -async function verifySingleLink(link) { - console.log(`\nChecking: ${link.linkText} (${link.url})`); - let browser = null; - try { - browser = createStagehand(); - await browser.init(); - const page = browser.context.pages()[0]; - // Detect if this is a social link (we treat those differently) - const isSocialLink = SOCIAL_DOMAINS.some((domain) => link.url.includes(domain)); - await page.goto(link.url, { timeoutMs: 30000 }); - await page.waitForLoadState("domcontentloaded"); - const currentUrl = page.url(); - // Guard against pages that never load or redirect to an invalid URL - if (!currentUrl || currentUrl === "about:blank") { - throw new Error("Page failed to load - invalid URL detected"); - } - console.log(`Link opened successfully: ${link.linkText}`); - // For social links, we consider a successful load good enough - if (isSocialLink) { - console.log(`[${link.linkText}] Social media link - skipping content verification`); - return { - linkText: link.linkText, - url: link.url, - success: true, - pageTitle: "Social Media Link", - contentMatches: true, - assessment: "Social media link loaded successfully (content verification skipped)", - }; - } - // Ask the model to read the page and decide whether it matches the link text - const verification = await browser.extract(`Does the page content match what the link text "${link.linkText}" suggests? Extract the page title and provide a brief assessment (maximum 8 words).`, z.object({ - pageTitle: z.string(), - contentMatches: z.boolean(), - assessment: z.string(), - })); - console.log(`[${link.linkText}] Page Title: ${verification.pageTitle}`); - console.log(`[${link.linkText}] Content Matches: ${verification.contentMatches ? "YES" : "NO"}`); - console.log(`[${link.linkText}] Assessment: ${verification.assessment}`); - return { - linkText: link.linkText, - url: link.url, - success: true, - pageTitle: verification.pageTitle, - contentMatches: verification.contentMatches, - assessment: verification.assessment, - }; - } - catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - console.error(`Failed to verify link "${link.linkText}": ${errorMessage}`); - // On failure, return a structured result capturing the error message - return { - linkText: link.linkText, - url: link.url, - success: false, - error: errorMessage, - }; - } - finally { - if (browser) { - // Always close the browser to free resources, even on error - await browser.close(); - console.log(`Browser closed for: ${link.linkText}`); - } - } -} -/** - * Verifies all links in batches to avoid opening too many concurrent sessions. - * Returns an array of `LinkCheckResult` objects for all processed links. - */ -async function verifyLinksInBatches(links) { - console.log(`\nVerifying links in batches of ${MAX_CONCURRENT_LINKS}...`); - const results = []; - for (let i = 0; i < links.length; i += MAX_CONCURRENT_LINKS) { - const batch = links.slice(i, i + MAX_CONCURRENT_LINKS); - console.log(`\n=== Processing batch ${Math.floor(i / MAX_CONCURRENT_LINKS) + 1} (${batch.length} links) ===`); - const batchResults = await Promise.all(batch.map((link) => verifySingleLink(link))); - results.push(...batchResults); - console.log(`\nBatch ${Math.floor(i / MAX_CONCURRENT_LINKS) + 1} complete (${results.length} total verified)`); - } - return results; -} -/** - * Logs a JSON summary of all link verification results. - * Falls back to a brief textual summary if JSON stringification fails. - */ -function outputResults(results, label = "FINAL RESULTS") { - console.log("\n" + "=".repeat(80)); - console.log(label); - console.log("=".repeat(80)); - const finalReport = { - totalLinks: results.length, - successful: results.filter((r) => r.success).length, - failed: results.filter((r) => !r.success).length, - results, - }; - try { - console.log(JSON.stringify(finalReport, null, 2)); - } - catch (stringifyError) { - console.error("Error stringifying results:", stringifyError); - console.log("Summary only:"); - console.log(`Total: ${finalReport.totalLinks}`); - console.log(`Successful: ${finalReport.successful}`); - console.log(`Failed: ${finalReport.failed}`); - } - console.log("\n" + "=".repeat(80)); -} -/** - * Orchestrates the full flow: - * 1. Collect all links from the homepage. - * 2. Verify them in batches. - * 3. Print a final JSON report (or partial results if an error occurs). - */ -async function main() { - console.log("Starting main function..."); - let results = []; - try { - const links = await collectLinksFromHomepage(); - console.log(`Collected ${links.length} links, starting verification...`); - results = await verifyLinksInBatches(links); - console.log("\n✓ All links verified!"); - console.log(`Results array length: ${results.length}`); - outputResults(results); - console.log("Script completed successfully"); - } - catch (error) { - console.error("\nError occurred during execution:", error); - if (results.length > 0) { - console.log(`\nOutputting partial results (${results.length} links processed before error):`); - outputResults(results, "PARTIAL RESULTS (Error Occurred)"); - } - else { - console.log("No results to output - error occurred before any links were verified"); - } - throw error; - } -} -main().catch((err) => { - console.error("Application error:", err); - process.exit(1); -}); diff --git a/scripts/build-javascript.mjs b/scripts/build-javascript.mjs index 4b7a52b2..951afae6 100644 --- a/scripts/build-javascript.mjs +++ b/scripts/build-javascript.mjs @@ -150,6 +150,8 @@ function isTypesPackage(depName) { function adaptPackageJsonForJavaScript(packageJson) { const pkg = JSON.parse(JSON.stringify(packageJson)); + pkg.type = "module"; + if (typeof pkg.main === "string") { pkg.main = pkg.main.replace(/\.tsx$/u, ".jsx").replace(/\.ts$/u, ".js"); }