Skip to content

Commit 97e6428

Browse files
committed
Add local mode support and examples
1 parent f4b2f76 commit 97e6428

10 files changed

Lines changed: 1039 additions & 33 deletions

File tree

README.md

Lines changed: 205 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,67 @@
1-
# Stagehand Ruby API library
2-
3-
The Stagehand Ruby library provides convenient access to the Stagehand REST API from any Ruby 3.2.0+ application. It ships with comprehensive types & docstrings in Yard, RBS, and RBI – [see below](https://github.com/browserbase/stagehand-ruby#Sorbet) for usage with Sorbet. The standard library's `net/http` is used as the HTTP transport, with connection pooling via the `connection_pool` gem.
4-
5-
It is generated with [Stainless](https://www.stainless.com/).
6-
7-
## MCP Server
8-
9-
Use the Stagehand MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.
10-
11-
[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=stagehand-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsInN0YWdlaGFuZC1tY3AiXX0)
12-
[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22stagehand-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22stagehand-mcp%22%5D%7D)
13-
14-
> Note: You may need to set environment variables in your MCP client.
15-
16-
## Documentation
17-
18-
Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/stagehand).
19-
20-
The REST API documentation can be found on [docs.stagehand.dev](https://docs.stagehand.dev).
1+
<div id="toc" align="center" style="margin-bottom: 0;">
2+
<ul style="list-style: none; margin: 0; padding: 0;">
3+
<a href="https://stagehand.dev">
4+
<picture>
5+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/browserbase/stagehand/main/media/dark_logo.png" />
6+
<img alt="Stagehand" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_logo.png" width="200" style="margin-right: 30px;" />
7+
</picture>
8+
</a>
9+
</ul>
10+
</div>
11+
<p align="center">
12+
<strong>The AI Browser Automation Framework</strong><br>
13+
<a href="https://docs.stagehand.dev/v3/sdk/ruby">Read the Docs</a>
14+
</p>
15+
16+
<p align="center">
17+
<a href="https://github.com/browserbase/stagehand/tree/main?tab=MIT-1-ov-file#MIT-1-ov-file">
18+
<picture>
19+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/browserbase/stagehand/main/media/dark_license.svg" />
20+
<img alt="MIT License" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_license.svg" />
21+
</picture>
22+
</a>
23+
<a href="https://stagehand.dev/discord">
24+
<picture>
25+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/browserbase/stagehand/main/media/dark_discord.svg" />
26+
<img alt="Discord Community" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_discord.svg" />
27+
</picture>
28+
</a>
29+
</p>
30+
31+
<p align="center">
32+
<a href="https://trendshift.io/repositories/12122" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12122" alt="browserbase%2Fstagehand | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
33+
</p>
34+
35+
<p align="center">
36+
If you're looking for other languages, you can find them
37+
<a href="https://docs.stagehand.dev/v3/first-steps/introduction"> here</a>
38+
</p>
39+
40+
<div align="center" style="display: flex; align-items: center; justify-content: center; gap: 4px; margin-bottom: 0;">
41+
<b>Vibe code</b>
42+
<span style="font-size: 1.05em;"> Stagehand with </span>
43+
<a href="https://director.ai" style="display: flex; align-items: center;">
44+
<span>Director</span>
45+
</a>
46+
<span> </span>
47+
<picture>
48+
<img alt="Director" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/director_icon.svg" width="25" />
49+
</picture>
50+
</div>
51+
52+
## What is Stagehand?
53+
54+
Stagehand is a browser automation framework used to control web browsers with natural language and code. By combining the power of AI with the precision of code, Stagehand makes web automation flexible, maintainable, and actually reliable.
55+
56+
## Why Stagehand?
57+
58+
Most existing browser automation tools either require you to write low-level code in a framework like Selenium, Playwright, or Puppeteer, or use high-level agents that can be unpredictable in production. By letting developers choose what to write in code vs. natural language (and bridging the gap between the two) Stagehand is the natural choice for browser automations in production.
59+
60+
1. **Choose when to write code vs. natural language**: use AI when you want to navigate unfamiliar pages, and use code when you know exactly what you want to do.
61+
62+
2. **Go from AI-driven to repeatable workflows**: Stagehand lets you preview AI actions before running them, and also helps you easily cache repeatable actions to save time and tokens.
63+
64+
3. **Write once, run forever**: Stagehand's auto-caching combined with self-healing remembers previous actions, runs without LLM inference, and knows when to involve AI whenever the website changes and your automation breaks.
2165

2266
## Installation
2367

@@ -26,7 +70,7 @@ To use this gem, install via Bundler by adding the following to your application
2670
<!-- x-release-please-start-version -->
2771

2872
```ruby
29-
gem "stagehand", "~> 3.0.2"
73+
gem "stagehand", "~> 3.0.1"
3074
```
3175

3276
<!-- x-release-please-end -->
@@ -37,15 +81,143 @@ gem "stagehand", "~> 3.0.2"
3781
require "bundler/setup"
3882
require "stagehand"
3983

40-
stagehand = Stagehand::Client.new(
41-
browserbase_api_key: ENV["BROWSERBASE_API_KEY"], # This is the default and can be omitted
42-
browserbase_project_id: ENV["BROWSERBASE_PROJECT_ID"], # This is the default and can be omitted
43-
model_api_key: ENV["MODEL_API_KEY"] # This is the default and can be omitted
84+
# Create a new Stagehand client with your credentials
85+
client = Stagehand::Client.new(
86+
browserbase_api_key: ENV["BROWSERBASE_API_KEY"], # defaults to ENV["BROWSERBASE_API_KEY"]
87+
browserbase_project_id: ENV["BROWSERBASE_PROJECT_ID"], # defaults to ENV["BROWSERBASE_PROJECT_ID"]
88+
model_api_key: ENV["MODEL_API_KEY"] # defaults to ENV["MODEL_API_KEY"]
89+
)
90+
91+
# Start a new browser session
92+
start_response = client.sessions.start(
93+
model_name: "openai/gpt-5-nano"
94+
)
95+
puts "Session started: #{start_response.data.session_id}"
96+
97+
session_id = start_response.data.session_id
98+
99+
# Navigate to a webpage
100+
client.sessions.navigate(
101+
session_id,
102+
url: "https://news.ycombinator.com"
103+
)
104+
puts "Navigated to Hacker News"
105+
106+
# Use Observe to find possible actions on the page
107+
observe_response = client.sessions.observe(
108+
session_id,
109+
instruction: "find the link to view comments for the top post"
110+
)
111+
112+
actions = observe_response.data.result
113+
puts "Found #{actions.length} possible actions"
114+
115+
# Take the first action returned by Observe
116+
action = actions.first
117+
puts "Acting on: #{action.description}"
118+
119+
# Pass the structured action to Act
120+
# Convert the observe result to a hash and ensure method is set to "click"
121+
act_response = client.sessions.act(
122+
session_id,
123+
input: action.to_h.merge(method: "click")
124+
)
125+
puts "Act completed: #{act_response.data.result[:message]}"
126+
127+
# Extract data from the page
128+
# We're now on the comments page, so extract the top comment text
129+
extract_response = client.sessions.extract(
130+
session_id,
131+
instruction: "extract the text of the top comment on this page",
132+
schema: {
133+
type: "object",
134+
properties: {
135+
comment_text: {
136+
type: "string",
137+
description: "The text content of the top comment"
138+
},
139+
author: {
140+
type: "string",
141+
description: "The username of the comment author"
142+
}
143+
},
144+
required: ["comment_text"]
145+
}
44146
)
147+
puts "Extracted data: #{extract_response.data.result}"
148+
149+
# Get the author from the extracted data
150+
extracted_data = extract_response.data.result
151+
author = extracted_data[:author]
152+
puts "Looking up profile for author: #{author}"
153+
154+
# Use the Agent to find the author's profile
155+
# Execute runs an autonomous agent that can navigate and interact with pages
156+
execute_response = client.sessions.execute(
157+
session_id,
158+
execute_options: {
159+
instruction: "Find any personal website, GitHub, LinkedIn, or other best profile URL for the Hacker News user '#{author}'. " \
160+
"Click on their username to go to their profile page and look for any links they have shared.",
161+
max_steps: 15
162+
},
163+
agent_config: {
164+
model: Stagehand::ModelConfig::ModelConfigObject.new(
165+
model_name: "openai/gpt-5-nano",
166+
api_key: ENV["MODEL_API_KEY"]
167+
),
168+
cua: false
169+
}
170+
)
171+
puts "Agent completed: #{execute_response.data.result[:message]}"
172+
puts "Agent success: #{execute_response.data.result[:success]}"
173+
puts "Agent actions taken: #{execute_response.data.result[:actions]&.length || 0}"
174+
175+
# End the session to cleanup browser resources
176+
client.sessions.end_(session_id)
177+
puts "Session ended"
178+
```
179+
180+
### Running the Examples
181+
182+
Install dependencies, set credentials, and run the scripts below.
183+
184+
```bash
185+
# Install the gem dependencies
186+
bundle install
187+
```
188+
189+
Remote API example:
190+
191+
```bash
192+
export BROWSERBASE_API_KEY="your-browserbase-api-key"
193+
export BROWSERBASE_PROJECT_ID="your-browserbase-project-id"
194+
export MODEL_API_KEY="your-openai-api-key"
195+
bundle exec ruby examples/basic.rb
196+
```
197+
198+
Local mode example (embedded server, local Chrome/Chromium):
199+
200+
```bash
201+
export MODEL_API_KEY="your-openai-api-key"
202+
bundle exec ruby examples/local.rb
203+
```
204+
205+
Playwright local example:
206+
207+
```bash
208+
gem install playwright-ruby-client
209+
npm install playwright
210+
./node_modules/.bin/playwright install chromium
211+
export MODEL_API_KEY="your-openai-api-key"
212+
bundle exec ruby examples/playwright_local_example.rb
213+
```
45214

46-
response = stagehand.sessions.act("00000000-your-session-id-000000000000", input: "click the first link on the page")
215+
Watir local example:
47216

48-
puts(response.data)
217+
```bash
218+
gem install watir
219+
export MODEL_API_KEY="your-openai-api-key"
220+
bundle exec ruby examples/watir_local_example.rb
49221
```
50222

51223
### Streaming
@@ -223,25 +395,25 @@ stagehand.sessions.act("00000000-your-session-id-000000000000", **params)
223395
Since this library does not depend on `sorbet-runtime`, it cannot provide [`T::Enum`](https://sorbet.org/docs/tenum) instances. Instead, we provide "tagged symbols" instead, which is always a primitive at runtime:
224396

225397
```ruby
226-
# :true
227-
puts(Stagehand::SessionActParams::XStreamResponse::TRUE)
398+
# :typescript
399+
puts(Stagehand::SessionActParams::XLanguage::TYPESCRIPT)
228400

229-
# Revealed type: `T.all(Stagehand::SessionActParams::XStreamResponse, Symbol)`
230-
T.reveal_type(Stagehand::SessionActParams::XStreamResponse::TRUE)
401+
# Revealed type: `T.all(Stagehand::SessionActParams::XLanguage, Symbol)`
402+
T.reveal_type(Stagehand::SessionActParams::XLanguage::TYPESCRIPT)
231403
```
232404

233405
Enum parameters have a "relaxed" type, so you can either pass in enum constants or their literal value:
234406

235407
```ruby
236408
# Using the enum constants preserves the tagged type information:
237409
stagehand.sessions.act(
238-
x_stream_response: Stagehand::SessionActParams::XStreamResponse::TRUE,
410+
x_language: Stagehand::SessionActParams::XLanguage::TYPESCRIPT,
239411
#
240412
)
241413

242414
# Literal values are also permissible:
243415
stagehand.sessions.act(
244-
x_stream_response: :true,
416+
x_language: :typescript,
245417
#
246418
)
247419
```

examples/local.rb

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
require "bundler/setup"
5+
require "stagehand"
6+
7+
# Local mode runs the embedded Stagehand server and uses a local Chrome/Chromium.
8+
# Set MODEL_API_KEY before running this script.
9+
model_key = ENV["MODEL_API_KEY"].to_s
10+
if model_key.empty?
11+
warn "Set MODEL_API_KEY to run the local example."
12+
exit 1
13+
end
14+
15+
client = Stagehand::Client.new(
16+
model_api_key: model_key,
17+
server: "local"
18+
)
19+
20+
session_id = nil
21+
22+
begin
23+
start_response = client.sessions.start(
24+
model_name: "openai/gpt-5-nano",
25+
browser: { type: :local }
26+
)
27+
session_id = start_response.data.session_id
28+
puts "Session started: #{session_id}"
29+
30+
client.sessions.navigate(session_id, url: "https://example.com")
31+
puts "Navigated to example.com"
32+
33+
observe_response = client.sessions.observe(
34+
session_id,
35+
instruction: "Find all clickable links on this page"
36+
)
37+
puts "Found #{observe_response.data.result.length} possible actions"
38+
39+
action = observe_response.data.result.first
40+
act_input = action ? action.to_h.merge(method: "click") : "Click the 'Learn more' link"
41+
act_response = client.sessions.act(
42+
session_id,
43+
input: act_input
44+
)
45+
puts "Act completed: #{act_response.data.result[:message]}"
46+
47+
extract_response = client.sessions.extract(
48+
session_id,
49+
instruction: "extract the main heading and any links on this page",
50+
schema: {
51+
type: "object",
52+
properties: {
53+
heading: { type: "string" },
54+
links: { type: "array", items: { type: "string" } }
55+
}
56+
}
57+
)
58+
puts "Extracted: #{extract_response.data.result}"
59+
60+
execute_response = client.sessions.execute(
61+
session_id,
62+
execute_options: {
63+
instruction: "Click on the 'Learn more' link if available and report the page title",
64+
max_steps: 3
65+
},
66+
agent_config: {
67+
model: Stagehand::ModelConfig::ModelConfigObject.new(
68+
model_name: "openai/gpt-5-nano",
69+
api_key: model_key
70+
),
71+
cua: false
72+
}
73+
)
74+
puts "Agent completed: #{execute_response.data.result[:message]}"
75+
puts "Agent success: #{execute_response.data.result[:success]}"
76+
ensure
77+
client.sessions.end_(session_id) if session_id
78+
client.close
79+
end

0 commit comments

Comments
 (0)