diff --git a/.cursorrules b/.cursorrules deleted file mode 100644 index 24af3d5..0000000 --- a/.cursorrules +++ /dev/null @@ -1,62 +0,0 @@ -# Animations Guidelines - -## Keep your animations fast - -- Default to use `ease-out` for most animations. -- Animations should never be longer than 1s (unless it's illustrative), most of them should be around 0.2s to 0.3s. - -## Easing rules - -- Don't use built-in CSS easings unless it's `ease` or `linear`. -- Use the following easings for their described use case: - - **`ease-in`**: (Starts slow, speeds up) Should generally be avoided as it makes the UI feel slow. - - `ease-in-quad`: `cubic-bezier(.55, .085, .68, .53)` - - `ease-in-cubic`: `cubic-bezier(.550, .055, .675, .19)` - - `ease-in-quart`: `cubic-bezier(.895, .03, .685, .22)` - - `ease-in-quint`: `cubic-bezier(.755, .05, .855, .06)` - - `ease-in-expo`: `cubic-bezier(.95, .05, .795, .035)` - - `ease-in-circ`: `cubic-bezier(.6, .04, .98, .335)` - - - **`ease-out`**: (Starts fast, slows down) Best for elements entering the screen or user-initiated interactions. - - `ease-out-quad`: `cubic-bezier(.25, .46, .45, .94)` - - `ease-out-cubic`: `cubic-bezier(.215, .61, .355, 1)` - - `ease-out-quart`: `cubic-bezier(.165, .84, .44, 1)` - - `ease-out-quint`: `cubic-bezier(.23, 1, .32, 1)` - - `ease-out-expo`: `cubic-bezier(.19, 1, .22, 1)` - - `ease-out-circ`: `cubic-bezier(.075, .82, .165, 1)` - - - **`ease-in-out`**: (Smooth acceleration and deceleration) Perfect for elements moving within the screen. - - `ease-in-out-quad`: `cubic-bezier(.455, .03, .515, .955)` - - `ease-in-out-cubic`: `cubic-bezier(.645, .045, .355, 1)` - - `ease-in-out-quart`: `cubic-bezier(.77, 0, .175, 1)` - - `ease-in-out-quint`: `cubic-bezier(.86, 0, .07, 1)` - - `ease-in-out-expo`: `cubic-bezier(1, 0, 0, 1)` - - `ease-in-out-circ`: `cubic-bezier(.785, .135, .15, .86)` - - -## Hover transitions - -- Use the built-in CSS `ease` with a duration of `200ms` for simple hover transitions like `color`, `background-color`,`opacity`. -- Fall back to easing rules for more complex hover transitions. -- Disable hover transitions on touch devices with the `@media (hover: hover) and (pointer: fine)` media query. - -## Accessibility - -- If `transform` is used in the animation, disable it in the `prefers-reduced-motion` media query. - -## Origin-aware animations - -- Elements should animate from the trigger. If you open a dropdown or a popover it should animate from the button. Change `transform-origin` according to the trigger position. - -## Performance - -- Stick to opacity and transforms when possible. Example: Animate using `transform` instead of `top`, `left`, etc. when trying to move an element. -- Do not animate drag gestures using CSS variables. -- Do not animate blur values higher than 20px. -- Use `will-change` to optimize your animation, but use it only for: `transform`, `opacity`, `clipPath`, `filter`. -- When using Motion/Framer Motion use `transform` instead of `x` or `y` if you need animations to be hardware accelerated. - -## Spring animations - -- Default to spring animations when using Framer Motion. -- Avoid using bouncy spring animations unless you are working with drag gestures. \ No newline at end of file diff --git a/.env.example b/.env.example index 17706c1..8af98ab 100644 --- a/.env.example +++ b/.env.example @@ -8,4 +8,6 @@ BROWSERBASE_PROJECT_ID=your_browserbase_project_id_here NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com NEXT_PUBLIC_POSTHOG_KEY=your_public_posthog_key_here -NEXT_PUBLIC_SITE_URL=http://localhost:3000 \ No newline at end of file +NEXT_PUBLIC_SITE_URL=http://localhost:3000 + +EDGE_CONFIG=your_edge_config_url \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7c98d80..c192313 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,5 @@ target/ # pnpm test/ +.vercel +.env*.local diff --git a/README.md b/README.md index 3860c2a..06361f5 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,108 @@ -# Google CUA Browser +# Gemini CUA Browser -A powerful browser automation playground powered by Google's new Computer Use Agent and Browserbase. This free demo showcases the capabilities of AI-driven browser automation using Stagehand and Google's computer-use capabilities. +[Demo](https://gemini.browserbase.com) + +A powerful browser automation playground powered by Gemini's new Computer Use Agent and Browserbase. This free demo showcases the capabilities of AI-driven browser automation using Stagehand and Gemini's computer-use capabilities. ## Features -- 🤖 **AI-Powered Browser Control**: Uses Google's Computer Use model to interact with web pages naturally -- 🌐 **Real Browser Environment**: Runs on actual Chrome browsers via Browserbase -- 🎯 **Natural Language Commands**: Simply describe what you want to do in plain English -- 📊 **Real-time Feedback**: Watch the AI navigate, click, type, and interact with websites -- 📝 **Rich Markdown Support**: AI responses rendered with proper formatting, code blocks, and typography -- 🔄 **Session Management**: Persistent browser sessions with tab management -- 🖼️ **Non-Interactive Preview**: View-only browser iframe prevents accidental user interference +- 🤖 **Gemini Computer Use Agent**: Leverages Gemini's `computer-use-preview-10-2025` model for intelligent web interactions +- 🌐 **Real Browser Control**: Runs on browsers via Browserbase's infrastructure +- 🎯 **Natural Language Commands**: Describe tasks in plain English and watch the AI execute them +- 📊 **Real-time Streaming**: Server-Sent Events (SSE) for live agent feedback and progress updates +- 🔄 **Session Management**: Persistent browser sessions with automatic viewport management ## Tech Stack -- **Frontend**: Next.js 15 with TypeScript, React 19, and Tailwind CSS -- **AI Model**: Google Computer Use +### Frontend +- **Framework**: Next.js 15 with React 19 and TypeScript +- **Styling**: Tailwind CSS with custom fonts (PP Neue, PP Supply) +- **Animation**: Framer Motion for smooth transitions +- **Icons**: Lucide React +- **Markdown**: ReactMarkdown with GitHub Flavored Markdown (remark-gfm) + +### Backend +- **AI Model**: Gemini Computer Use (`computer-use-preview-10-2025`) - **Browser Automation**: Browserbase + Stagehand -- **Streaming**: Server-Sent Events (SSE) for real-time updates -- **UI Components**: Framer Motion animations, Lucide React icons -- **Markdown Rendering**: ReactMarkdown with GitHub Flavored Markdown support +- **Agent Framework**: Stagehand with Playwright Core +- **Streaming**: Server-Sent Events (SSE) +- **Runtime**: Node.js with Next.js API routes + +### Infrastructure - **Analytics**: PostHog for user tracking +- **Configuration**: Vercel Edge Config for region distribution +- **Deployment**: Optimized for Vercel with 600s max duration ## Prerequisites - Node.js 18.x or later -- pnpm (recommended) or npm -- API keys for Google AI Studio and Browserbase +- pnpm 10.x or later (recommended) +- API keys: + - [Google AI Studio](https://aistudio.google.com/apikey) - for Computer Use Agent + - [Browserbase](https://www.browserbase.com) - for browser infrastructure ## Getting Started -1. **Clone the repository:** - ```bash - git clone https://github.com/browserbase/google-cua-browser.git - cd -browser - ``` - -2. **Install dependencies:** - ```bash - pnpm install - # or - npm install - ``` - -3. **Set up environment variables:** - ```bash - cp .env.example .env.local - ``` - - Then edit `.env.local` with your API keys: - ```env - # Google API Configuration - GOOGLE_API_KEY=your_google_api_key_here - - # Browserbase Configuration - BROWSERBASE_API_KEY=your_browserbase_api_key_here - BROWSERBASE_PROJECT_ID=your_browserbase_project_id_here - - # Optional: Analytics and monitoring - NEXT_PUBLIC_POSTHOG_KEY=your_posthog_key - NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com - - # Site URL (for local development) - NEXT_PUBLIC_SITE_URL=http://localhost:3000 - ``` - - **Get your API keys:** - - Google API: [Google AI Studio](https://aistudio.google.com/apikey) - - Browserbase: [Browserbase Dashboard](https://www.browserbase.com) - -4. **Start the development server:** - ```bash - pnpm dev - # or - npm run dev - ``` - -5. **Open your browser:** - Navigate to [http://localhost:3000](http://localhost:3000) +### 1. Clone the repository +```bash +git clone https://github.com/browserbase/gemini-cua-browser.git +cd gemini-cua-browser +``` -## Usage +### 2. Install dependencies +```bash +pnpm install +``` -1. **Start a Session**: Click "New Session" to initialize a browser instance -2. **Enter Commands**: Type natural language instructions like: - - "Go to google.com and search for AI news" - - "Navigate to GitHub and explore trending repositories" - - "Fill out the contact form on this page" -3. **Watch the Magic**: The AI will interpret your request and perform the actions -4. **View Results**: See real-time updates with rich markdown formatting including code blocks, lists, and formatted text +### 3. Configure environment variables +```bash +cp .env.example .env.local +``` -## Key Components +Edit `.env.local` with your credentials: +```env +# Google AI Studio API Key +GOOGLE_API_KEY=your_google_api_key -- **Stream API** (`/api/agent/stream`): Handles real-time agent execution with SSE -- **Session Management** (`/api/session`): Creates and manages Browserbase sessions -- **Agent Integration**: Uses Stagehand with Google's Computer Use for browser automation -- **Markdown Chat**: AI responses support rich text formatting with code syntax highlighting -- **Browser Preview**: Non-interactive iframe for viewing agent actions without user interference -- **UI Components**: Modern, animated interface with real-time updates +# Browserbase Configuration +BROWSERBASE_API_KEY=your_browserbase_api_key +BROWSERBASE_PROJECT_ID=your_browserbase_project_id -## Codebase Optimization +# Optional: Analytics +NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com +NEXT_PUBLIC_POSTHOG_KEY=your_posthog_key -This project has been optimized for production deployment: +# Optional: Site URL +NEXT_PUBLIC_SITE_URL=http://localhost:3000 -- **Clean Dependencies**: Removed all unused npm packages and dev dependencies -- **Asset Optimization**: Eliminated unused images, fonts, and static files -- **Type Safety**: Cleaned up unused TypeScript types and interfaces with proper ReactMarkdown component typing -- **Bundle Size**: Reduced bundle size by removing dead code and unused imports -- **UI Components**: Modular markdown rendering components for consistent styling -- **Performance**: Optimized for Vercel deployment with proper runtime configuration +# Optional: Vercel Edge Config +EDGE_CONFIG=your_edge_config_url +``` + +### 4. Start the development server +```bash +pnpm dev +``` + +### 5. Open your browser +Navigate to [http://localhost:3000](http://localhost:3000) + +## Usage + +1. **Enter a Command**: Type a natural language instruction or select a preset example: + - "What's the price of NVIDIA stock?" + - "Review a pull request on Github" + - "Browse Hacker News for trending debates" + - "Play a game of 2048" + +2. **Watch the Agent**: The AI will: + - Create a browser session + - Navigate to relevant websites + - Interact with page elements (click, type, scroll) + - Take screenshots to verify actions + - Stream real-time progress updates + +3. **View Results**: See the agent's reasoning, actions, and final response in rich markdown format ## Available Scripts @@ -121,36 +116,13 @@ pnpm build # Start production server pnpm start -# Run linting +# Lint code pnpm lint ``` -## Configuration - -The agent is configured with specific behaviors: -- Works in atomic steps (one action at a time) -- Prefers direct navigation over search -- Avoids risky actions unless necessary -- Fixed viewport at 1024x768 pixels -- Automatic screenshot capture after actions - -## Limitations - -- Maximum session duration: 10 minutes (Vercel timeout) -- Viewport locked at 1024x768 pixels -- No keyboard shortcuts support (uses click + type instead) -- Browser sessions are temporary and will expire - -## Troubleshooting - -- **Session fails to start**: Check your Browserbase API credentials -- **Agent not responding**: Verify your Google API key has access to Google Computer Use -- **Timeout errors**: Complex tasks may exceed the 10-minute limit -- **Connection issues**: Ensure stable internet connection for browser streaming - ## Contributing -This is a demo playground project. Feel free to fork and experiment! +This is a demo project showcasing Gemini Computer Use Agent capabilities. Feel free to fork and experiment! ## License @@ -158,7 +130,7 @@ MIT ## Acknowledgments -- [Browserbase](https://browserbase.com) for browser infrastructure -- [Stagehand](https://github.com/browserbasehq/stagehand) for automation framework -- [Google AI Studio](https://aistudio.google.com/) for AI capabilities -- [Vercel](https://vercel.com) for hosting and edge functions \ No newline at end of file +- [Browserbase](https://browserbase.com) - Browser infrastructure and remote browser sessions +- [Stagehand](https://github.com/browserbasehq/stagehand) - Browser automation framework with AI capabilities +- [Google AI Studio](https://aistudio.google.com/) - Computer Use Agent API +- [Vercel](https://vercel.com) - Hosting, edge functions, and edge config \ No newline at end of file diff --git a/app/api/agent/stream/route.ts b/app/api/agent/stream/route.ts index fb732dc..e60f39c 100644 --- a/app/api/agent/stream/route.ts +++ b/app/api/agent/stream/route.ts @@ -18,10 +18,9 @@ function sseComment(comment: string): Uint8Array { export async function GET(request: Request) { const { searchParams } = new URL(request.url); - const [sessionId, goal, fromChat] = [ + const [sessionId, goal] = [ searchParams.get("sessionId"), searchParams.get("goal"), - searchParams.get("fromChat") === "true" ]; if (!sessionId || !goal) { @@ -122,7 +121,6 @@ export async function GET(request: Request) { width: 1288, height: 711, }, - solveCaptchas: !fromChat, // false if session is from a search param, true otherwise }, }, useAPI: false, @@ -139,14 +137,14 @@ export async function GET(request: Request) { send("start", { sessionId, goal, - model: "computer-use-preview-10-2025", + model: "gemini-2.5-computer-use-preview-10-2025", init, startedAt: new Date().toISOString(), }); const agent = stagehand.agent({ provider: "google", - model: "computer-use-preview-10-2025", + model: "gemini-2.5-computer-use-preview-10-2025", options: { apiKey: process.env.GOOGLE_API_KEY, }, @@ -200,4 +198,4 @@ export async function GET(request: Request) { "X-Accel-Buffering": "no", }, }); -} +} \ No newline at end of file diff --git a/app/api/session/route.ts b/app/api/session/route.ts index 0965b60..d0f246e 100644 --- a/app/api/session/route.ts +++ b/app/api/session/route.ts @@ -1,5 +1,6 @@ -import { NextResponse } from "next/server"; import Browserbase from "@browserbasehq/sdk"; +import { getAll } from "@vercel/edge-config"; +import { NextResponse } from "next/server"; type BrowserbaseRegion = | "us-west-2" @@ -7,100 +8,178 @@ type BrowserbaseRegion = | "eu-central-1" | "ap-southeast-1"; -// Exact timezone matches for east coast cities -const exactTimezoneMap: Record = { - "America/New_York": "us-east-1", - "America/Detroit": "us-east-1", - "America/Toronto": "us-east-1", - "America/Montreal": "us-east-1", - "America/Boston": "us-east-1", - "America/Chicago": "us-east-1", +// Timezone abbreviation to region mapping +const timezoneAbbreviationMap: Record = { + // US East Coast + EST: "us-east-1", + EDT: "us-east-1", + + // US West Coast + PST: "us-west-2", + PDT: "us-west-2", + + // US Mountain/Central - route to appropriate region + MST: "us-west-2", + MDT: "us-west-2", + CST: "us-east-1", + CDT: "us-east-1", + + // Europe + GMT: "eu-central-1", + BST: "eu-central-1", + CET: "eu-central-1", + CEST: "eu-central-1", + EET: "eu-central-1", + EEST: "eu-central-1", + WET: "eu-central-1", + WEST: "eu-central-1", + + // Asia-Pacific + JST: "ap-southeast-1", // Japan Standard Time + KST: "ap-southeast-1", // Korea Standard Time + IST: "ap-southeast-1", // India Standard Time + AEST: "ap-southeast-1", // Australian Eastern Standard Time + AEDT: "ap-southeast-1", // Australian Eastern Daylight Time + AWST: "ap-southeast-1", // Australian Western Standard Time + NZST: "ap-southeast-1", // New Zealand Standard Time + NZDT: "ap-southeast-1", // New Zealand Daylight Time }; -// Prefix-based region mapping -const prefixToRegion: Record = { - America: "us-west-2", - US: "us-west-2", - Canada: "us-west-2", - Europe: "eu-central-1", - Africa: "eu-central-1", - Asia: "ap-southeast-1", - Australia: "ap-southeast-1", - Pacific: "ap-southeast-1", +// Default fallback distributions if edge config is not available +const defaultDistributions: Record< + BrowserbaseRegion, + Record +> = { + "us-west-2": { + "us-west-2": 100, + "us-east-1": 0, + "eu-central-1": 0, + "ap-southeast-1": 0, + }, + "us-east-1": { + "us-east-1": 100, + "us-west-2": 0, + "eu-central-1": 0, + "ap-southeast-1": 0, + }, + "eu-central-1": { + "eu-central-1": 100, + "us-west-2": 0, + "us-east-1": 0, + "ap-southeast-1": 0, + }, + "ap-southeast-1": { + "ap-southeast-1": 100, + "us-west-2": 0, + "us-east-1": 0, + "eu-central-1": 0, + }, }; -// Offset ranges to regions (inclusive bounds) -const offsetRanges: { - min: number; - max: number; - region: BrowserbaseRegion; -}[] = [ - { min: -24, max: -4, region: "us-west-2" }, // UTC-24 to UTC-4 - { min: -3, max: 4, region: "eu-central-1" }, // UTC-3 to UTC+4 - { min: 5, max: 24, region: "ap-southeast-1" }, // UTC+5 to UTC+24 -]; - -function getClosestRegion(timezone?: string): BrowserbaseRegion { - try { - if (!timezone) { - return "us-west-2"; // Default if no timezone provided - } +function selectRegionWithProbability( + baseRegion: BrowserbaseRegion, + distributions: Record> +): BrowserbaseRegion { + const distribution = distributions[baseRegion]; + if (!distribution) { + return baseRegion; + } + + const random = Math.random() * 100; // Generate random number between 0-100 - // Check exact matches first - if (timezone in exactTimezoneMap) { - return exactTimezoneMap[timezone]; + let cumulativeProbability = 0; + for (const [region, probability] of Object.entries(distribution)) { + cumulativeProbability += probability; + if (random < cumulativeProbability) { + return region as BrowserbaseRegion; } + } + + // Fallback to base region if something goes wrong + return baseRegion; +} - // Check prefix matches - const prefix = timezone.split("/")[0]; - if (prefix in prefixToRegion) { - return prefixToRegion[prefix]; +function getRegionFromTimezoneAbbr(timezoneAbbr?: string): BrowserbaseRegion { + try { + if (!timezoneAbbr) { + return "us-west-2"; // Default if no timezone provided } - // Use offset-based fallback - const date = new Date(); - // Create a date formatter for the given timezone - const formatter = new Intl.DateTimeFormat("en-US", { timeZone: timezone }); - // Get the timezone offset in minutes - const timeString = formatter.format(date); - const testDate = new Date(timeString); - const hourOffset = (testDate.getTime() - date.getTime()) / (1000 * 60 * 60); - - const matchingRange = offsetRanges.find( - (range) => hourOffset >= range.min && hourOffset <= range.max - ); + // Direct lookup from timezone abbreviation + const region = timezoneAbbreviationMap[timezoneAbbr.toUpperCase()]; + if (region) { + return region; + } - return matchingRange?.region ?? "us-west-2"; + // Fallback to us-west-2 for unknown abbreviations + return "us-west-2"; } catch { return "us-west-2"; } } +interface EdgeConfig { + advancedStealth: boolean | undefined; + proxies: boolean | undefined; + regionDistribution: + | Record> + | undefined; +} + async function createSession(timezone?: string) { const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY!, }); - const browserSettings: Browserbase.Sessions.SessionCreateParams.BrowserSettings = { - viewport: { - width: 2560, - height: 1440, - }, - // @ts-expect-error - os is not a valid property - os: "windows", - blockAds: true, - advancedStealth: true - }; - - console.log("timezone ", timezone); - console.log("getClosestRegion(timezone)", getClosestRegion(timezone)); + const config = await getAll(); + + const { + advancedStealth: advancedStealthConfig, + proxies: proxiesConfig, + regionDistribution: distributionsConfig, + } = config; + + const advancedStealth: boolean = advancedStealthConfig ?? false; + const proxies: boolean = proxiesConfig ?? true; + + // Build browserSettings conditionally + const browserSettings: Browserbase.Sessions.SessionCreateParams.BrowserSettings = + { + viewport: { + width: 2560, + height: 1440, + }, + blockAds: true, + advancedStealth, + // Only set os if advancedStealth is true + ...(advancedStealth + ? { + os: "mac", + } + : { + os: "linux", + }), + }; + + // Use timezone abbreviation to determine base region + const closestRegion = getRegionFromTimezoneAbbr(timezone); + + // Get distributions from config or use default + const distributions = distributionsConfig ?? defaultDistributions; + + // Apply probability routing for potential load balancing + const finalRegion = selectRegionWithProbability(closestRegion, distributions); + + console.log("timezone abbreviation:", timezone); + console.log("mapped to region:", closestRegion); + console.log("final region after probability routing:", finalRegion); const session = await bb.sessions.create({ projectId: process.env.BROWSERBASE_PROJECT_ID!, - proxies: true, + proxies, browserSettings, keepAlive: true, - region: getClosestRegion(timezone), + region: finalRegion, }); return { session, diff --git a/app/components/BrowserSessionContainer.tsx b/app/components/BrowserSessionContainer.tsx index 68eaba2..0defc4b 100644 --- a/app/components/BrowserSessionContainer.tsx +++ b/app/components/BrowserSessionContainer.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; -import { SessionControls } from "./SessionControls"; +import { SessionControls } from "@/app/components/SessionControls"; import { RotateCcwIcon } from "lucide-react"; interface BrowserSessionContainerProps { @@ -209,7 +209,7 @@ const BrowserSessionContainer: React.FC = ({ {/* Simple loading animation that will always show when session URL is not available */}

- Starting Google CUA Browser + Starting Gemini Browser

@@ -318,4 +318,4 @@ const BrowserSessionContainer: React.FC = ({ ); }; -export default BrowserSessionContainer; +export default BrowserSessionContainer; \ No newline at end of file diff --git a/app/components/ChatFeed.tsx b/app/components/ChatFeed.tsx index b707706..b9ae4b5 100644 --- a/app/components/ChatFeed.tsx +++ b/app/components/ChatFeed.tsx @@ -5,21 +5,20 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { useWindowSize } from "usehooks-ts"; import posthog from "posthog-js"; -import { SessionControls } from "./SessionControls"; -import BrowserSessionContainer from "./BrowserSessionContainer"; +import { SessionControls } from "@/app/components/SessionControls"; +import BrowserSessionContainer from "@/app/components/BrowserSessionContainer"; import { SessionLiveURLs } from "@browserbasehq/sdk/resources/index.mjs"; -import BrowserTabs from "./ui/BrowserTabs"; -import NavBar from "./NavBar"; -import PinnedGoalMessage from "./chat/PinnedGoalMessage"; -import PinnedFinalAnswer from "./chat/PinnedFinalAnswer"; -import ChatMessagesList from "./chat/ChatMessagesList"; -import ChatInput from "./chat/ChatInput"; -import { useAgentStream } from "../hooks/useAgentStream"; -import { ChatFeedProps, AgentState, BrowserStep } from "../types/ChatFeed"; +import BrowserTabs from "@/app/components/ui/BrowserTabs"; +import NavBar from "@/app/components/NavBar"; +import PinnedGoalMessage from "@/app/components/chat/PinnedGoalMessage"; +import PinnedFinalAnswer from "@/app/components/chat/PinnedFinalAnswer"; +import ChatMessagesList from "@/app/components/chat/ChatMessagesList"; +import ChatInput from "@/app/components/chat/ChatInput"; +import { useAgentStream } from "@/app/hooks/useAgentStream"; +import { ChatFeedProps, AgentState, BrowserStep } from "@/app/types/ChatFeed"; export default function ChatFeed({ initialMessage, - isFromSearchParam = false, onClose, }: ChatFeedProps) { const renderCount = useRef(0); @@ -200,7 +199,6 @@ export default function ChatFeed({ } = useAgentStream({ sessionId: null, goal: initialMessage, - isFromSearchParam, onStart: handleStart, onDone: handleDone, onError: handleError, @@ -254,7 +252,7 @@ export default function ChatFeed({ exit="exit" > @@ -373,4 +370,4 @@ export default function ChatFeed({ ); -} +} \ No newline at end of file diff --git a/app/components/NavBar.tsx b/app/components/NavBar.tsx index a170cb8..f856c14 100644 --- a/app/components/NavBar.tsx +++ b/app/components/NavBar.tsx @@ -13,7 +13,7 @@ interface NavBarProps { } export default function NavBar({ - title = "Google CUA Browser", + title = "Gemini Browser", showCloseButton = false, onClose, showGitHubButton = true, @@ -31,14 +31,14 @@ export default function NavBar({ >
Google CUA Browser
@@ -69,7 +69,7 @@ export default function NavBar({ {showGitHubButton && ( @@ -81,7 +81,7 @@ export default function NavBar({ height={20} className="sm:mr-2" /> - View GitHub + GitHub )} diff --git a/app/components/chat/ChatMessage.tsx b/app/components/chat/ChatMessage.tsx index 1f4f893..4eb4497 100644 --- a/app/components/chat/ChatMessage.tsx +++ b/app/components/chat/ChatMessage.tsx @@ -5,7 +5,7 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { BrowserStep } from "@/app/types/ChatFeed"; import { toolNameMapping } from "@/constants/tools"; -import { createMarkdownComponents } from "./markdown"; +import { createMarkdownComponents } from "@/app/components/chat/markdown"; interface ChatMessageProps { step: BrowserStep; diff --git a/app/components/chat/ChatMessagesList.tsx b/app/components/chat/ChatMessagesList.tsx index f54dcae..f23c5bf 100644 --- a/app/components/chat/ChatMessagesList.tsx +++ b/app/components/chat/ChatMessagesList.tsx @@ -1,6 +1,6 @@ import { RefObject } from "react"; -import { BrowserStep } from "../../types/ChatFeed"; -import ChatMessage from "./ChatMessage"; +import { BrowserStep } from "@/app/types/ChatFeed"; +import ChatMessage from "@/app/components/chat/ChatMessage"; interface ChatMessagesListProps { steps: BrowserStep[]; diff --git a/app/components/chat/PinnedFinalAnswer.tsx b/app/components/chat/PinnedFinalAnswer.tsx index ca1c815..14c7cb8 100644 --- a/app/components/chat/PinnedFinalAnswer.tsx +++ b/app/components/chat/PinnedFinalAnswer.tsx @@ -1,4 +1,7 @@ import { motion } from "framer-motion"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { createMarkdownComponents } from "@/app/components/chat/markdown"; interface PinnedFinalAnswerProps { message: string; @@ -40,9 +43,14 @@ export default function PinnedFinalAnswer({ }} >
-

- {message} -

+
+ + {message} + +
); diff --git a/app/hooks/useAgentStream.ts b/app/hooks/useAgentStream.ts index 29f364a..523c8fa 100644 --- a/app/hooks/useAgentStream.ts +++ b/app/hooks/useAgentStream.ts @@ -1,25 +1,36 @@ "use client"; import { useState, useEffect, useRef, useCallback } from "react"; -import { BrowserStep } from "../types/ChatFeed"; -import { AgentLog, UseAgentStreamProps, AgentStreamState, LogEvent } from "../types/Agent"; +import moment from "moment-timezone"; +import { BrowserStep } from "@/app/types/ChatFeed"; +import { + AgentLog, + UseAgentStreamProps, + AgentStreamState, + LogEvent, +} from "@/app/types/Agent"; // Global trackers to avoid duplicate session creation in React Strict Mode // by sharing a single in-flight promise across mounts for the same goal. const sessionCreationPromises = new Map< string, - Promise<{ sessionId: string; sessionUrl: string | null; connectUrl: string | null }> + Promise<{ + sessionId: string; + sessionUrl: string | null; + connectUrl: string | null; + }> >(); export function useAgentStream({ sessionId, goal, - isFromSearchParam = false, onStart, onDone, onError, }: UseAgentStreamProps) { - console.log(`[useAgentStream] Hook called with goal: "${goal?.substring(0, 50)}...", sessionId: ${sessionId}`); + console.log( + `[useAgentStream] Hook called with goal: "${goal?.substring(0, 50)}...", sessionId: ${sessionId}` + ); const [state, setState] = useState({ sessionId: sessionId, sessionUrl: null, @@ -69,7 +80,9 @@ export function useAgentStream({ return { kind: "summary", step: parseInt(execMatch[1], 10), text: "" }; } // Function call lines, optionally without args, and possibly multi-line JSON - const fnMatch = raw.match(/^Found function call:\s*([A-Za-z0-9_]+)(?:\s+with args:\s*([\s\S]+))?$/i); + const fnMatch = raw.match( + /^Found function call:\s*([A-Za-z0-9_]+)(?:\s+with args:\s*([\s\S]+))?$/i + ); if (fnMatch) { let args: unknown = {}; const jsonText = (fnMatch[2] || "").trim(); @@ -80,16 +93,31 @@ export function useAgentStream({ args = jsonText; // keep raw if not valid JSON } } - return { kind: "action", step: stepCounterRef.current, tool: fnMatch[1], args }; + return { + kind: "action", + step: stepCounterRef.current, + tool: fnMatch[1], + args, + }; } return null; }, []); - const isPlainObject = useCallback((v: unknown) => typeof v === "object" && v !== null && !Array.isArray(v), []); - const isEmptyObject = useCallback((v: unknown) => isPlainObject(v) && Object.keys(v as Record).length === 0, [isPlainObject]); + const isPlainObject = useCallback( + (v: unknown) => typeof v === "object" && v !== null && !Array.isArray(v), + [] + ); + const isEmptyObject = useCallback( + (v: unknown) => + isPlainObject(v) && + Object.keys(v as Record).length === 0, + [isPlainObject] + ); useEffect(() => { - console.log(`[useAgentStream] useEffect triggered with goal: "${goal?.substring(0, 50)}..."`); + console.log( + `[useAgentStream] useEffect triggered with goal: "${goal?.substring(0, 50)}..."` + ); if (!goal) { console.log(`[useAgentStream] No goal, returning`); return; @@ -108,20 +136,41 @@ export function useAgentStream({ let promise = sessionCreationPromises.get(goal); if (!promise) { promise = (async () => { + // Detect timezone using moment-timezone for cleaner abbreviations + const getTimezoneAbbreviation = () => { + try { + // Only detect timezone on the client side + if (typeof window === "undefined") { + return "PDT"; // Server-side fallback + } + + const abbr = moment.tz(moment.tz.guess()).format("z"); + return abbr; + } catch { + return "PDT"; // Fallback + } + }; + + const timezone = getTimezoneAbbreviation(); + const sessionResponse = await fetch("/api/session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + timezone: timezone, }), }); const sessionData = await sessionResponse.json(); if (!sessionData.success) { - throw new Error(sessionData.error || "Failed to create session"); + throw new Error( + sessionData.error || "Failed to create session" + ); } - console.log(`[useAgentStream] Session created successfully: ${sessionData.sessionId}`); + console.log( + `[useAgentStream] Session created successfully: ${sessionData.sessionId}` + ); return { sessionId: sessionData.sessionId as string, sessionUrl: (sessionData.sessionUrl as string) ?? null, @@ -142,8 +191,13 @@ export function useAgentStream({ connectUrl: result.connectUrl, })); } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Failed to create session"; - setState((prev) => ({ ...prev, error: errorMessage, isLoading: false })); + const errorMessage = + error instanceof Error ? error.message : "Failed to create session"; + setState((prev) => ({ + ...prev, + error: errorMessage, + isLoading: false, + })); onErrorRef.current?.(errorMessage); // Allow retries by clearing promise on error sessionCreationPromises.delete(goal); @@ -163,7 +217,6 @@ export function useAgentStream({ const params = new URLSearchParams({ sessionId: currentSessionId!, goal, - fromChat: String(isFromSearchParam), }); const es = new EventSource(`/api/agent/stream?${params.toString()}`); @@ -185,193 +238,234 @@ export function useAgentStream({ } }); - es.addEventListener("log", (e) => { - if (cancelled) return; - try { - const payload = JSON.parse((e as MessageEvent).data) as LogEvent; - const parsed = parseLog(payload.message); - - setState((prev) => { - const newLogs = [...prev.logs, payload]; - if (!parsed) { - return { ...prev, logs: newLogs }; - } + es.addEventListener("log", (e) => { + if (cancelled) return; + try { + const payload = JSON.parse((e as MessageEvent).data) as LogEvent; + const parsed = parseLog(payload.message); - if (parsed.kind === "summary") { - // If the first visible step starts at >1 (because step 1 was hidden), - // shift numbering so the first visible step is Step 1. - if (stepOffsetRef.current === 0 && prev.steps.length === 0 && parsed.step > 1) { - stepOffsetRef.current = parsed.step - 1; - } - const displayStep = Math.max(1, parsed.step - stepOffsetRef.current); - stepCounterRef.current = displayStep; - - const trimmedText = (parsed.text || "").trim(); - - // Update existing step with matching number if present; else append - const existingIndex = prev.steps.findIndex((s) => s.stepNumber === displayStep); - if (existingIndex >= 0) { - const existing = prev.steps[existingIndex]; - const existingText = (existing.text || "").trim(); - // If text is identical, no change; otherwise update text/instruction - if (trimmedText && trimmedText !== existingText) { - const updated: BrowserStep = { - ...existing, - // Keep whatever tool it currently has (may have been upgraded to an action) - text: parsed.text, - instruction: parsed.text, - }; - const newSteps = [...prev.steps]; - newSteps[existingIndex] = updated; - return { ...prev, logs: newLogs, steps: newSteps }; - } + setState((prev) => { + const newLogs = [...prev.logs, payload]; + if (!parsed) { return { ...prev, logs: newLogs }; } - const newStep: BrowserStep = { - stepNumber: displayStep, - text: parsed.text, - reasoning: "", - tool: "MESSAGE", - instruction: parsed.text, - }; - return { ...prev, logs: newLogs, steps: [...prev.steps, newStep] }; - } + if (parsed.kind === "summary") { + // If the first visible step starts at >1 (because step 1 was hidden), + // shift numbering so the first visible step is Step 1. + if ( + stepOffsetRef.current === 0 && + prev.steps.length === 0 && + parsed.step > 1 + ) { + stepOffsetRef.current = parsed.step - 1; + } + const displayStep = Math.max( + 1, + parsed.step - stepOffsetRef.current + ); + stepCounterRef.current = displayStep; + + const trimmedText = (parsed.text || "").trim(); + + // Update existing step with matching number if present; else append + const existingIndex = prev.steps.findIndex( + (s) => s.stepNumber === displayStep + ); + if (existingIndex >= 0) { + const existing = prev.steps[existingIndex]; + const existingText = (existing.text || "").trim(); + // If text is identical, no change; otherwise update text/instruction + if (trimmedText && trimmedText !== existingText) { + const updated: BrowserStep = { + ...existing, + // Keep whatever tool it currently has (may have been upgraded to an action) + text: parsed.text, + instruction: parsed.text, + }; + const newSteps = [...prev.steps]; + newSteps[existingIndex] = updated; + return { ...prev, logs: newLogs, steps: newSteps }; + } + return { ...prev, logs: newLogs }; + } - if (parsed.kind === "thought") { - if (prev.steps.length === 0) { - // create a placeholder step 1 to hold the thought - const placeholder: BrowserStep = { - stepNumber: stepCounterRef.current, - text: "", - reasoning: parsed.text, + const newStep: BrowserStep = { + stepNumber: displayStep, + text: parsed.text, + reasoning: "", tool: "MESSAGE", - instruction: "", + instruction: parsed.text, + }; + return { + ...prev, + logs: newLogs, + steps: [...prev.steps, newStep], }; - return { ...prev, logs: newLogs, steps: [...prev.steps, placeholder] }; } - const updated = prev.steps.map((s, idx, arr) => - idx === arr.length - 1 ? { ...s, reasoning: parsed.text } : s - ); - return { ...prev, logs: newLogs, steps: updated }; - } - - if (parsed.kind === "action") { - const toolName = parsed.tool; - const tool: BrowserStep["tool"] = toolName; - - // Track invoked tool names (dedupe) - const nextInvoked = new Set(prev.invokedTools); - nextInvoked.add(toolName); - // Align action to adjusted step numbering - if (stepOffsetRef.current === 0 && prev.steps.length === 0 && parsed.step > 1) { - stepOffsetRef.current = parsed.step - 1; + if (parsed.kind === "thought") { + if (prev.steps.length === 0) { + // create a placeholder step 1 to hold the thought + const placeholder: BrowserStep = { + stepNumber: stepCounterRef.current, + text: "", + reasoning: parsed.text, + tool: "MESSAGE", + instruction: "", + }; + return { + ...prev, + logs: newLogs, + steps: [...prev.steps, placeholder], + }; + } + const updated = prev.steps.map((s, idx, arr) => + idx === arr.length - 1 ? { ...s, reasoning: parsed.text } : s + ); + return { ...prev, logs: newLogs, steps: updated }; } - const displayStep = Math.max(1, parsed.step - stepOffsetRef.current); - - // Prefer updating the step with matching number; else update the last - const updated = (prev.steps.length > 0 ? prev.steps : []).map((s) => { - if (s.stepNumber !== displayStep) return s; - const sameTool = s.tool === tool; - const sameArgs = JSON.stringify(s.actionArgs) === JSON.stringify(parsed.args); - if (sameTool && sameArgs) return s; - return { ...s, tool, actionArgs: parsed.args }; - }); - // If there is no step to attach to, create one - const hasTarget = updated.length > 0 && updated.some((s) => s.stepNumber === displayStep); - if (!hasTarget) { - const newStep: BrowserStep = { - stepNumber: displayStep, - text: "", - reasoning: "", - tool, - instruction: "", - actionArgs: parsed.args, + + if (parsed.kind === "action") { + const toolName = parsed.tool; + const tool: BrowserStep["tool"] = toolName; + + // Track invoked tool names (dedupe) + const nextInvoked = new Set(prev.invokedTools); + nextInvoked.add(toolName); + + // Align action to adjusted step numbering + if ( + stepOffsetRef.current === 0 && + prev.steps.length === 0 && + parsed.step > 1 + ) { + stepOffsetRef.current = parsed.step - 1; + } + const displayStep = Math.max( + 1, + parsed.step - stepOffsetRef.current + ); + + // Prefer updating the step with matching number; else update the last + const updated = (prev.steps.length > 0 ? prev.steps : []).map( + (s) => { + if (s.stepNumber !== displayStep) return s; + const sameTool = s.tool === tool; + const sameArgs = + JSON.stringify(s.actionArgs) === + JSON.stringify(parsed.args); + if (sameTool && sameArgs) return s; + return { ...s, tool, actionArgs: parsed.args }; + } + ); + // If there is no step to attach to, create one + const hasTarget = + updated.length > 0 && + updated.some((s) => s.stepNumber === displayStep); + if (!hasTarget) { + const newStep: BrowserStep = { + stepNumber: displayStep, + text: "", + reasoning: "", + tool, + instruction: "", + actionArgs: parsed.args, + }; + return { + ...prev, + logs: newLogs, + invokedTools: Array.from(nextInvoked), + steps: [...prev.steps, newStep], + }; + } + return { + ...prev, + logs: newLogs, + invokedTools: Array.from(nextInvoked), + steps: updated, }; - return { ...prev, logs: newLogs, invokedTools: Array.from(nextInvoked), steps: [...prev.steps, newStep] }; } - return { ...prev, logs: newLogs, invokedTools: Array.from(nextInvoked), steps: updated }; - } - - return { ...prev, logs: newLogs }; - }); - } catch (err) { - console.error("Error parsing log event:", err); - setState((prev) => ({ - ...prev, - logs: [...prev.logs, { message: String((e as MessageEvent).data) }], - })); - } - }); - // Disable SSE 'step' duplication: logs already carry summary/action - es.addEventListener("step", () => {}); + return { ...prev, logs: newLogs }; + }); + } catch (err) { + console.error("Error parsing log event:", err); + setState((prev) => ({ + ...prev, + logs: [...prev.logs, { message: String((e as MessageEvent).data) }], + })); + } + }); - es.addEventListener("done", (e) => { - try { - const payload = JSON.parse((e as MessageEvent).data); - console.log("[useAgentStream] Done event received:", payload); + // Disable SSE 'step' duplication: logs already carry summary/action + es.addEventListener("step", () => {}); - // If there's a final message, add it as the last step - if (payload.finalMessage) { - console.log("[useAgentStream] Adding final message as step"); - setState((prev) => { - const finalStep: BrowserStep = { - stepNumber: prev.steps.length + 1, - text: payload.finalMessage, - reasoning: "", - tool: "MESSAGE", - instruction: "Final Answer", - }; - console.log("[useAgentStream] Final step created:", finalStep); - return { + es.addEventListener("done", (e) => { + try { + const payload = JSON.parse((e as MessageEvent).data); + console.log("[useAgentStream] Done event received:", payload); + + // If there's a final message, add it as the last step + if (payload.finalMessage) { + console.log("[useAgentStream] Adding final message as step"); + setState((prev) => { + const finalStep: BrowserStep = { + stepNumber: prev.steps.length + 1, + text: payload.finalMessage, + reasoning: "", + tool: "MESSAGE", + instruction: "Final Answer", + }; + console.log("[useAgentStream] Final step created:", finalStep); + return { + ...prev, + steps: [...prev.steps, finalStep], + isFinished: true, + }; + }); + } else { + setState((prev) => ({ ...prev, - steps: [...prev.steps, finalStep], isFinished: true, - }; - }); - } else { + })); + } + + onDoneRef.current?.(payload); + // Clear the session promise for this goal to allow future runs + sessionCreationPromises.delete(goal); + } catch (err) { + console.error("Error parsing done event:", err); + } + es.close(); + eventSourceRef.current = null; + }); + + es.addEventListener("error", (e) => { + try { + const payload = JSON.parse((e as MessageEvent).data); + const errorMessage = + payload.message || "Connection lost. Please try again."; setState((prev) => ({ ...prev, + error: errorMessage, isFinished: true, })); + onErrorRef.current?.(errorMessage); + } catch { + const errorMessage = "Connection lost. Please try again."; + setState((prev) => ({ + ...prev, + error: errorMessage, + isFinished: true, + })); + onErrorRef.current?.(errorMessage); } - - onDoneRef.current?.(payload); - // Clear the session promise for this goal to allow future runs + // Clear the session promise for this goal to allow retries sessionCreationPromises.delete(goal); - } catch (err) { - console.error("Error parsing done event:", err); - } - es.close(); - eventSourceRef.current = null; - }); - - es.addEventListener("error", (e) => { - try { - const payload = JSON.parse((e as MessageEvent).data); - const errorMessage = payload.message || "Connection lost. Please try again."; - setState((prev) => ({ - ...prev, - error: errorMessage, - isFinished: true, - })); - onErrorRef.current?.(errorMessage); - } catch { - const errorMessage = "Connection lost. Please try again."; - setState((prev) => ({ - ...prev, - error: errorMessage, - isFinished: true, - })); - onErrorRef.current?.(errorMessage); - } - // Clear the session promise for this goal to allow retries - sessionCreationPromises.delete(goal); - es.close(); - eventSourceRef.current = null; - }); + es.close(); + eventSourceRef.current = null; + }); // Store es in a variable for cleanup return () => { @@ -389,10 +483,10 @@ export function useAgentStream({ eventSourceRef.current.close(); } }; - }, [sessionId, goal, parseLog, isEmptyObject, isFromSearchParam]); + }, [sessionId, goal, parseLog, isEmptyObject]); return { ...state, stop, }; -} \ No newline at end of file +} diff --git a/app/layout.tsx b/app/layout.tsx index 663103a..0dc1d36 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -16,14 +16,14 @@ const ppNeue = localFont({ }); export const metadata: Metadata = { - metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000"), - title: "Google CUA Browser", + metadataBase: new URL("https://gemini.browserbase.com"), + title: "Gemini Browser", description: "Watch AI browse the web, for free", openGraph: { images: ["/og.png"], - title: "Google CUA Browser", + title: "Gemini Browser", description: "Watch AI browse the web, for free", - url: "https://google.browserbase.com", + url: "https://gemini.browserbase.com", }, icons: { icon: [ diff --git a/app/page.tsx b/app/page.tsx index be6f7be..d166ebe 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,7 +2,6 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { AnimatePresence, motion } from "framer-motion"; -import { useSearchParams } from "next/navigation"; import AnimatedButton from "./components/ui/AnimatedButton"; import posthog from "posthog-js"; import ChatFeed from "./components/ChatFeed"; @@ -48,12 +47,8 @@ const Tooltip = ({ }; export default function Home() { - const searchParams = useSearchParams(); - const rawChatParam = searchParams.get("chat"); - const chatParam = rawChatParam?.replace(/^["']|["']$/g, '') || null; - - const [isChatVisible, setIsChatVisible] = useState(() => !!chatParam); - const [initialMessage, setInitialMessage] = useState(() => chatParam); + const [isChatVisible, setIsChatVisible] = useState(() => false); + const [initialMessage, setInitialMessage] = useState(() => null); const inputRef = useRef(null); const startChat = useCallback( @@ -144,7 +139,7 @@ export default function Home() {

- Google CUA Browser + Gemini Browser

Hit run to watch AI browse the web. @@ -238,11 +233,11 @@ export default function Home() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3, delay: 0.7 }} - onClick={() => startChat("Compare the features and pricing of the top 3 project management tools on Product Hunt today. Create a simple comparison for me.")} + onClick={() => startChat("Find the current price of Bitcoin and Ethereum.")} className="p-3 md:p-5 lg:p-6 text-sm md:text-base lg:text-xl text-[#2E191E] border border-[#CAC8C7] hover:border-[#FF3B00] hover:text-[#FF3B00] transition-colors font-ppsupply font-medium text-center overflow-hidden text-ellipsis break-words whitespace-normal md:min-h-[100px] lg:min-h-[120px] flex items-center justify-center backdrop-blur-sm bg-opacity-60 bg-[rgba(245,240,255,0.15)] hover:bg-[rgba(255,59,0,0.05)] rounded-none" >

- Research and compare
trending tools
+ Get the latest crypto prices
@@ -307,7 +302,6 @@ export default function Home() { setIsChatVisible(false)} /> )} diff --git a/app/types/Agent.ts b/app/types/Agent.ts index 0975ea3..f7f1bb9 100644 --- a/app/types/Agent.ts +++ b/app/types/Agent.ts @@ -57,7 +57,6 @@ export interface DoneEventData { export interface UseAgentStreamProps { sessionId: string | null; goal: string | null; - isFromSearchParam?: boolean; onStart?: (data: StartEventData) => void; onDone?: (data?: DoneEventData) => void; onError?: (error: string) => void; diff --git a/app/types/ChatFeed.ts b/app/types/ChatFeed.ts index d8c659c..0acfbd2 100644 --- a/app/types/ChatFeed.ts +++ b/app/types/ChatFeed.ts @@ -1,6 +1,5 @@ export interface ChatFeedProps { initialMessage: string | null; - isFromSearchParam?: boolean; onClose: () => void; url?: string; } diff --git a/package.json b/package.json index 90ec6db..11ccd0d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "google-cua-browser", + "name": "gemini-browser", "version": "0.1.0", "private": true, "scripts": { @@ -11,13 +11,14 @@ }, "dependencies": { "@browserbasehq/sdk": "^2.6.0", - "@browserbasehq/stagehand": "file:/Users/kylejeong/Desktop/private-cua-exp/stagehand-ts", - "@tailwindcss/typography": "^0.5.19", + "@browserbasehq/stagehand": "github:browserbase/stagehand#cheetah", "@vercel/analytics": "^1.4.1", + "@vercel/edge-config": "^1.4.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^11.0.3", "lucide-react": "^0.479.0", + "moment-timezone": "^0.6.0", "next": "15.1.6", "playwright-core": "^1.55.0", "posthog-js": "^1.209.3", @@ -32,6 +33,7 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@eslint/js": "^9.34.0", + "@tailwindcss/typography": "^0.5.19", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c985262..a85d7e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,14 +12,14 @@ importers: specifier: ^2.6.0 version: 2.6.0 '@browserbasehq/stagehand': - specifier: file:/Users/kylejeong/Desktop/private-cua-exp/stagehand-ts - version: file:../private-cua-exp/stagehand-ts(deepmerge@4.3.1)(dotenv@16.6.1)(react@19.1.1)(zod@3.25.76) - '@tailwindcss/typography': - specifier: ^0.5.19 - version: 0.5.19(tailwindcss@3.4.17) + specifier: github:browserbase/stagehand#cheetah + version: https://codeload.github.com/browserbase/stagehand/tar.gz/b5e17490a4b2184d33922e55907f38b316dbfa46(deepmerge@4.3.1)(dotenv@16.6.1)(react@19.1.1)(zod@3.25.76) '@vercel/analytics': specifier: ^1.4.1 version: 1.5.0(next@15.1.6(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + '@vercel/edge-config': + specifier: ^1.4.0 + version: 1.4.0(@opentelemetry/api@1.9.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -32,6 +32,9 @@ importers: lucide-react: specifier: ^0.479.0 version: 0.479.0(react@19.1.1) + moment-timezone: + specifier: ^0.6.0 + version: 0.6.0 next: specifier: 15.1.6 version: 15.1.6(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -69,6 +72,9 @@ importers: '@eslint/js': specifier: ^9.34.0 version: 9.34.0 + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@3.4.17) '@types/node': specifier: ^20 version: 20.19.11 @@ -222,13 +228,14 @@ packages: '@browserbasehq/sdk@2.6.0': resolution: {integrity: sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA==} - '@browserbasehq/stagehand@file:../private-cua-exp/stagehand-ts': - resolution: {directory: ../private-cua-exp/stagehand-ts, type: directory} + '@browserbasehq/stagehand@https://codeload.github.com/browserbase/stagehand/tar.gz/b5e17490a4b2184d33922e55907f38b316dbfa46': + resolution: {tarball: https://codeload.github.com/browserbase/stagehand/tar.gz/b5e17490a4b2184d33922e55907f38b316dbfa46} + version: 2.5.0 hasBin: true peerDependencies: deepmerge: ^4.3.1 dotenv: ^16.4.5 - zod: '>=3.25.0 <4.1.0' + zod: '>=3.25.0 <3.25.68' '@emnapi/core@1.5.0': resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} @@ -277,9 +284,14 @@ packages: resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@google/genai@0.8.0': - resolution: {integrity: sha512-Zs+OGyZKyMbFofGJTR9/jTQSv8kITh735N3tEuIZj4VlMQXTC0soCFahysJ9NaeenRlD7xGb6fyqmX+FwrpU6Q==} - engines: {node: '>=18.0.0'} + '@google/genai@1.22.0': + resolution: {integrity: sha512-siETS3zTm3EGpTT4+BFc1z20xXBYfueD3gCYfxkOjuAKRk8lt8TJevDHi3zepn1oSI6NhG/LZvy0i+Q3qheObg==} + engines: {node: '>=20.0.0'} + peerDependencies: + '@modelcontextprotocol/sdk': ^1.11.4 + peerDependenciesMeta: + '@modelcontextprotocol/sdk': + optional: true '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -423,6 +435,10 @@ packages: '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@modelcontextprotocol/sdk@1.19.1': + resolution: {integrity: sha512-3Y2h3MZKjec1eAqSTBclATlX+AbC6n1LgfVzRMJLt3v6w0RCYgwLrjbxPDbhsYHt6Wdqc/aCceNJYgj448ELQQ==} + engines: {node: '>=18'} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -767,10 +783,26 @@ packages: vue-router: optional: true + '@vercel/edge-config-fs@0.1.0': + resolution: {integrity: sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg==} + + '@vercel/edge-config@1.4.0': + resolution: {integrity: sha512-69Wg5gw9DzwnyUmnjToSeLRm1nm8mCPgN0kflX8EHRHyqvzH80wPem5A8rI2LXPb2Y9tJNoqN3vXPcQhS2Wh5g==} + engines: {node: '>=14.6'} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -909,6 +941,10 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + body-parser@2.2.0: + resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + engines: {node: '>=18'} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -926,6 +962,10 @@ packages: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1017,9 +1057,29 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + content-disposition@1.0.0: + resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + core-js@3.45.1: resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==} + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1035,10 +1095,6 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -1093,6 +1149,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1134,12 +1194,19 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -1175,6 +1242,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1322,10 +1392,32 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.1.0: + resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} + engines: {node: '>= 18'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1371,10 +1463,6 @@ packages: picomatch: optional: true - fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - fetch-cookie@3.1.0: resolution: {integrity: sha512-s/XhhreJpqH0ftkGVcQt8JE9bqk+zRn4jF5mPJXWZeQMCI5odV9K+wEWYbnzFPHgQZlvPSMjS4n4yawWE8RINw==} @@ -1389,6 +1477,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@2.1.0: + resolution: {integrity: sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==} + engines: {node: '>= 0.8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1419,9 +1511,9 @@ packages: resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} engines: {node: '>= 12.20'} - formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} framer-motion@11.18.2: resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==} @@ -1437,6 +1529,10 @@ packages: react-dom: optional: true + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1562,6 +1658,10 @@ packages: html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} @@ -1569,6 +1669,14 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.0: + resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1585,6 +1693,9 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} @@ -1592,6 +1703,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -1686,6 +1801,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1890,6 +2008,14 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1986,10 +2112,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.1: + resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2004,6 +2138,12 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + moment-timezone@0.6.0: + resolution: {integrity: sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + motion-dom@11.18.1: resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} @@ -2029,6 +2169,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + next@15.1.6: resolution: {integrity: sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -2064,10 +2208,6 @@ packages: encoding: optional: true - node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -2121,6 +2261,10 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2162,6 +2306,10 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} @@ -2180,6 +2328,9 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2213,6 +2364,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pkce-challenge@5.0.0: + resolution: {integrity: sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==} + engines: {node: '>=16.20.0'} + playwright-core@1.55.0: resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==} engines: {node: '>=18'} @@ -2312,6 +2467,10 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -2319,12 +2478,24 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + qs@6.14.0: + resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.1: + resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==} + engines: {node: '>= 0.10'} + react-dom@19.1.1: resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} peerDependencies: @@ -2394,6 +2565,10 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2416,6 +2591,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} @@ -2434,6 +2612,14 @@ packages: engines: {node: '>=10'} hasBin: true + send@1.2.0: + resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} + engines: {node: '>= 18'} + + serve-static@2.2.0: + resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} + engines: {node: '>= 18'} + set-cookie-parser@2.7.1: resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} @@ -2449,6 +2635,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2501,6 +2690,14 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -2646,6 +2843,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tough-cookie@5.1.2: resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} engines: {node: '>=16'} @@ -2678,6 +2879,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2734,6 +2939,10 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -2758,16 +2967,16 @@ packages: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - web-streams-polyfill@4.0.0-beta.3: resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} engines: {node: '>= 14'} @@ -2994,18 +3203,17 @@ snapshots: transitivePeerDependencies: - encoding - '@browserbasehq/stagehand@file:../private-cua-exp/stagehand-ts(deepmerge@4.3.1)(dotenv@16.6.1)(react@19.1.1)(zod@3.25.76)': + '@browserbasehq/stagehand@https://codeload.github.com/browserbase/stagehand/tar.gz/b5e17490a4b2184d33922e55907f38b316dbfa46(deepmerge@4.3.1)(dotenv@16.6.1)(react@19.1.1)(zod@3.25.76)': dependencies: '@anthropic-ai/sdk': 0.39.0 '@browserbasehq/sdk': 2.6.0 - '@google/genai': 0.8.0 + '@google/genai': 1.22.0(@modelcontextprotocol/sdk@1.19.1) + '@modelcontextprotocol/sdk': 1.19.1 ai: 4.3.19(react@19.1.1)(zod@3.25.76) deepmerge: 4.3.1 devtools-protocol: 0.0.1464554 dotenv: 16.6.1 fetch-cookie: 3.1.0 - https-proxy-agent: 7.0.6 - node-fetch: 3.3.2 openai: 4.104.0(ws@8.18.3)(zod@3.25.76) pino: 9.9.0 pino-pretty: 13.1.1 @@ -3093,10 +3301,12 @@ snapshots: '@eslint/core': 0.15.2 levn: 0.4.1 - '@google/genai@0.8.0': + '@google/genai@1.22.0(@modelcontextprotocol/sdk@1.19.1)': dependencies: google-auth-library: 9.15.1 ws: 8.18.3 + optionalDependencies: + '@modelcontextprotocol/sdk': 1.19.1 transitivePeerDependencies: - bufferutil - encoding @@ -3214,6 +3424,23 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@modelcontextprotocol/sdk@1.19.1': + dependencies: + ajv: 6.12.6 + content-type: 1.0.5 + cors: 2.8.5 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.1.0 + express-rate-limit: 7.5.1(express@5.1.0) + pkce-challenge: 5.0.0 + raw-body: 3.0.1 + zod: 3.25.76 + zod-to-json-schema: 3.24.6(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.5.0 @@ -3510,10 +3737,23 @@ snapshots: next: 15.1.6(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 + '@vercel/edge-config-fs@0.1.0': {} + + '@vercel/edge-config@1.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@vercel/edge-config-fs': 0.1.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 + accepts@2.0.0: + dependencies: + mime-types: 3.0.1 + negotiator: 1.0.0 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -3661,6 +3901,20 @@ snapshots: binary-extensions@2.3.0: {} + body-parser@2.2.0: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.1 + http-errors: 2.0.0 + iconv-lite: 0.6.3 + on-finished: 2.4.1 + qs: 6.14.0 + raw-body: 3.0.1 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3680,6 +3934,8 @@ snapshots: dependencies: streamsearch: 1.1.0 + bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3770,8 +4026,23 @@ snapshots: concat-map@0.0.1: {} + content-disposition@1.0.0: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + core-js@3.45.1: {} + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3784,8 +4055,6 @@ snapshots: damerau-levenshtein@1.0.8: {} - data-uri-to-buffer@4.0.1: {} - data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -3836,6 +4105,8 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} detect-libc@2.0.4: @@ -3871,10 +4142,14 @@ snapshots: dependencies: safe-buffer: 5.2.1 + ee-first@1.1.1: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -3980,6 +4255,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -4197,8 +4474,52 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + event-target-shim@5.0.1: {} + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + express-rate-limit@7.5.1(express@5.1.0): + dependencies: + express: 5.1.0 + + express@5.1.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.0 + content-disposition: 1.0.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.0 + fresh: 2.0.0 + http-errors: 2.0.0 + merge-descriptors: 2.0.0 + mime-types: 3.0.1 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.0 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.0 + serve-static: 2.2.0 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} fast-copy@3.0.2: {} @@ -4239,11 +4560,6 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - fetch-blob@3.2.0: - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - fetch-cookie@3.1.0: dependencies: set-cookie-parser: 2.7.1 @@ -4259,6 +4575,17 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@2.1.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -4295,9 +4622,7 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 4.0.0-beta.3 - formdata-polyfill@4.0.10: - dependencies: - fetch-blob: 3.2.0 + forwarded@0.2.0: {} framer-motion@11.18.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: @@ -4308,6 +4633,8 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) + fresh@2.0.0: {} + fsevents@2.3.2: optional: true @@ -4477,6 +4804,14 @@ snapshots: html-url-attributes@3.0.1: {} + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -4488,6 +4823,14 @@ snapshots: dependencies: ms: 2.1.3 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.0: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4499,6 +4842,8 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + inline-style-parser@0.2.4: {} internal-slot@1.1.0: @@ -4507,6 +4852,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + ipaddr.js@1.9.1: {} + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -4601,6 +4948,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@4.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -4910,6 +5259,10 @@ snapshots: dependencies: '@types/mdast': 4.0.4 + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + merge2@1.4.1: {} micromark-core-commonmark@2.0.3: @@ -5110,10 +5463,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.1: + dependencies: + mime-db: 1.54.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -5126,6 +5485,12 @@ snapshots: minipass@7.1.2: {} + moment-timezone@0.6.0: + dependencies: + moment: 2.30.1 + + moment@2.30.1: {} + motion-dom@11.18.1: dependencies: motion-utils: 11.18.1 @@ -5146,6 +5511,8 @@ snapshots: natural-compare@1.4.0: {} + negotiator@1.0.0: {} + next@15.1.6(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@next/env': 15.1.6 @@ -5178,12 +5545,6 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-fetch@3.3.2: - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - normalize-path@3.0.0: {} object-assign@4.1.1: {} @@ -5241,6 +5602,10 @@ snapshots: on-exit-leak-free@2.1.2: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -5299,6 +5664,8 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parseurl@1.3.3: {} + partial-json@0.1.7: optional: true @@ -5313,6 +5680,8 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@8.3.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5359,6 +5728,8 @@ snapshots: pirates@4.0.7: {} + pkce-challenge@5.0.0: {} + playwright-core@1.55.0: {} playwright@1.55.0: @@ -5445,6 +5816,11 @@ snapshots: property-information@7.1.0: {} + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -5452,10 +5828,23 @@ snapshots: punycode@2.3.1: {} + qs@6.14.0: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} quick-format-unescaped@4.0.4: {} + range-parser@1.2.1: {} + + raw-body@3.0.1: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.7.0 + unpipe: 1.0.0 + react-dom@19.1.1(react@19.1.1): dependencies: react: 19.1.1 @@ -5565,6 +5954,16 @@ snapshots: reusify@1.1.0: {} + router@2.2.0: + dependencies: + debug: 4.4.1 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -5592,6 +5991,8 @@ snapshots: safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} + scheduler@0.26.0: {} secure-json-parse@2.7.0: {} @@ -5602,6 +6003,31 @@ snapshots: semver@7.7.2: {} + send@1.2.0: + dependencies: + debug: 4.4.1 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.0 + mime-types: 3.0.1 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.0: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + set-cookie-parser@2.7.1: {} set-function-length@1.2.2: @@ -5626,6 +6052,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -5706,6 +6134,10 @@ snapshots: stable-hash@0.0.5: {} + statuses@2.0.1: {} + + statuses@2.0.2: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -5895,6 +6327,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tough-cookie@5.1.2: dependencies: tldts: 6.1.86 @@ -5924,6 +6358,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.1 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -6014,6 +6454,8 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + unpipe@1.0.0: {} + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.3 @@ -6055,6 +6497,8 @@ snapshots: uuid@9.0.1: {} + vary@1.1.2: {} + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -6065,8 +6509,6 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - web-streams-polyfill@3.3.3: {} - web-streams-polyfill@4.0.0-beta.3: {} web-vitals@4.2.4: {} diff --git a/public/og.png b/public/og.png index d53bb60..6ddae65 100644 Binary files a/public/og.png and b/public/og.png differ diff --git a/tailwind.config.ts b/tailwind.config.ts index b0b7b94..ebe09fb 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -47,6 +47,5 @@ export default { } } }, - // eslint-disable-next-line @typescript-eslint/no-require-imports plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")], } satisfies Config; diff --git a/tsconfig.json b/tsconfig.json index d8b9323..2c1fc97 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,4 +24,4 @@ }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"] -} +} \ No newline at end of file