diff --git a/.gitignore b/.gitignore index 0e0e505..5e7c3d6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ target/ # Test outputs coverage/ +agent-skills-workspace/ *.log # OS generated files diff --git a/README.md b/README.md index 6cffc8d..837167a 100644 --- a/README.md +++ b/README.md @@ -149,8 +149,9 @@ Reference docs: - [Agent troubleshooting playbook](docs/references/agent-troubleshooting.md) - [Pinned upstream source snapshot](docs/references/upstream/agent-browser-commands.source.md) -Agent skill: +Agent skills: +- [Steel Developer skill package](skills/steel-developer/README.md) - [Steel Browser skill package](skills/steel-browser/README.md) --- diff --git a/skills/steel-browser/README.md b/skills/steel-browser/README.md index 033362a..8ca5113 100644 --- a/skills/steel-browser/README.md +++ b/skills/steel-browser/README.md @@ -24,8 +24,7 @@ npx skills add github:steel-dev/cli/skills/steel-browser ## When to use this skill -Use this skill by default for browser/web tasks that do not require local-only -execution: +Use this skill when the agent should operate a live browser itself for browser/web tasks that do not require local-only execution: - Multi-step website automation (`steel browser ...`) - Web extraction/summarization (`steel scrape`) @@ -37,6 +36,8 @@ Prefer Steel tooling over generic fetch/search paths when reliability matters. Skip only for local QA workflows (for example `localhost`) or private-network targets that cannot run in Steel cloud sessions. +Use the sibling [Steel Developer skill](../steel-developer/README.md) when writing reusable Steel SDK, Playwright, Puppeteer, Stagehand, Browser Use, credentials, profiles, proxy, or CAPTCHA automation code. + ## Why Steel - Cloud background browser sessions with explicit lifecycle control diff --git a/skills/steel-browser/SKILL.md b/skills/steel-browser/SKILL.md index 5dc0a19..885e261 100644 --- a/skills/steel-browser/SKILL.md +++ b/skills/steel-browser/SKILL.md @@ -3,23 +3,33 @@ name: steel-browser allowed-tools: - "Bash(steel:*)" description: >- - Use this skill for any web task where WebFetch or curl would fail or be - insufficient — pages that require JavaScript to render, forms to fill and - submit, screenshots or PDFs of live pages, CAPTCHA/bot-protection bypass, - login flows, and multi-step browser navigation with persistent session state. + Use this skill only when the agent should perform a live web task itself and + WebFetch or curl would fail or be insufficient — pages that require JavaScript + to render, forms to fill and submit, screenshots or PDFs of live pages, + CAPTCHA/bot-protection bypass, login flows, and multi-step browser navigation + with persistent session state. WebFetch returns empty HTML for JS-rendered pages; this skill runs a real cloud browser that executes JavaScript, maintains cookies, clicks buttons, and handles anti-bot measures. Trigger when the user wants you to actually perform a web task (visit, interact, extract, capture) rather than just write - code for it. Skip only for: static pages a simple GET can fetch, localhost or - private-network targets, writing browser automation code the user will run - themselves, or conceptual questions about browser tools. + code for it. Do not use when writing, debugging, or explaining reusable Steel + SDK/browser automation code, Playwright/Puppeteer integrations, Stagehand, + Browser Use, credentials, profiles, proxies, or CAPTCHA APIs; use the + `steel-developer` skill instead. Skip for static pages a simple GET can fetch, localhost + or private-network targets, and conceptual questions about browser tools. --- # Steel > Cloud browser infrastructure for AI agents. Steel gives your agent a real browser that can navigate pages, fill forms, solve CAPTCHAs, and extract content. +## Skill boundary + +- Use this skill to operate a browser now via `steel scrape`, `steel screenshot`, `steel pdf`, and `steel browser ...` +- Do not generate reusable SDK automation code from this skill +- If the user asks to build on Steel, integrate `steel-sdk`, connect Playwright/Puppeteer, use Stagehand or Browser Use, manage credentials/profiles programmatically, or implement CAPTCHA/proxy behavior in code, load the `steel-developer` skill and follow that guidance instead +- This skill can still be used for live reconnaissance before writing code, but the final code should come from the `steel-developer` skill + If Steel is not installed yet, execute the setup steps below autonomously. Only ask the user to do one thing: click the authentication link. OBJECTIVE: Install the Steel CLI, authenticate, and verify everything works. @@ -274,64 +284,17 @@ Do not attempt these — they will fail. | `steel browser geolocation` | `steel browser set geo LAT LON` | | `steel browser pdf` | Top-level `steel pdf ` | -## SDK integration (programmatic) +## Programmatic Steel builds -If you need to use Steel from code instead of the CLI: +Do not write SDK/browser automation code from this skill. If the user needs reusable code rather than a live agent-operated browser task, load the `steel-developer` skill and follow its current SDK guidance. -### Python (Playwright) - -```bash -pip install steel-sdk playwright -``` - -```python -from playwright.sync_api import sync_playwright -from steel import Steel -import os - -client = Steel(steel_api_key=os.environ["STEEL_API_KEY"]) -session = client.sessions.create() - -try: - pw = sync_playwright().start() - browser = pw.chromium.connect_over_cdp(session.websocket_url) - page = browser.contexts[0].pages[0] - - page.goto("https://example.com") - print(page.title()) -finally: - browser.close() - pw.stop() - client.sessions.release(session.id) -``` - -### Node.js (Puppeteer) - -```bash -npm install steel-sdk puppeteer -``` - -```typescript -import Steel from 'steel-sdk'; -import puppeteer from 'puppeteer'; - -const client = new Steel({ steelAPIKey: process.env.STEEL_API_KEY }); -const session = await client.sessions.create(); - -try { - const browser = await puppeteer.connect({ - browserWSEndpoint: `${session.websocketUrl}?apiKey=${process.env.STEEL_API_KEY}`, - }); - const page = await browser.newPage(); - await page.goto('https://example.com'); - console.log(await page.title()); - await browser.disconnect(); -} finally { - await client.sessions.release(session.id); -} -``` +High-level handoff cues for the build skill: -**Key pattern:** Create session → connect via CDP websocket URL → use any browser library → release session. +- Create a Steel session from application code +- Construct the CDP URL explicitly with `wss://connect.steel.dev?apiKey=...&sessionId=...` +- Connect Playwright or Puppeteer to the default browser context +- Release the Steel session in cleanup +- Gate CAPTCHA solving and Steel-managed proxies by plan before enabling them ## Troubleshooting @@ -339,7 +302,6 @@ try { |---------|-----| | `steel: command not found` | `curl -sSf https://setup.steel.dev/install.sh \| sh && export PATH="$HOME/.steel/bin:$PATH"` | | `Missing browser auth` | `steel login` or set `STEEL_API_KEY` env var | -| Authentication error in SDK | `export STEEL_API_KEY="your_key"` | | `No running session` | Check session name; `steel browser stop --all` then restart | | Stale element refs | Re-run `steel browser snapshot -i` before interacting | | CAPTCHA blocking | `steel browser start --stealth --session ` | diff --git a/skills/steel-developer/APIS.md b/skills/steel-developer/APIS.md new file mode 100644 index 0000000..2a47652 --- /dev/null +++ b/skills/steel-developer/APIS.md @@ -0,0 +1,232 @@ +# Steel APIs + +Use this file to choose the right Steel API and avoid common implementation mistakes. Do not copy complete request or response schemas into generated answers unless the user asks for them. + +## Source Of Truth + +- API reference: https://steel.apidocumentation.com/api-reference +- Sessions create endpoint: https://steel.apidocumentation.com/api-reference#tag/sessions/post/v1/sessions +- Node SDK: https://github.com/steel-dev/steel-node +- Python SDK: https://github.com/steel-dev/steel-python + +Use this skill for routing, patterns, and gotchas. Use the API reference and SDK types for exact option names, request shapes, response fields, and method signatures. + +## Fetching Steel Docs + +When fetching Steel docs with `curl`, follow redirects. + +Use: + +```bash +curl -sSfL https://docs.steel.dev/llms.txt +curl -sSfL https://docs.steel.dev/llms-full.txt +curl -sSfL https://docs.steel.dev/llms.mdx/overview/sessions-api/quickstart +``` + +Rules: + +- Always include `-L`; docs URLs may redirect. +- Prefer `-sSfL` for agent use: silent progress, fail on HTTP errors, follow redirects. +- Use `llms.txt` for the docs index and quick reference. +- Use `llms-full.txt` for broad offline/context loading. +- Use `/llms.mdx/` for a single exact docs page. +- Use the API reference for exact API shapes. +- Use SDK repos and local installed types for exact SDK method names and option types. + +## API Routing + +| Need | Use | Reference | +| --- | --- | --- | +| Create and release a browser session | Sessions API | https://steel.apidocumentation.com/api-reference#tag/sessions | +| Connect Playwright or Puppeteer | Sessions API plus CDP URL | https://docs.steel.dev/integrations/playwright | +| One-shot scrape, screenshot, or PDF | Browser Tools | https://docs.steel.dev/overview/browser-tools/overview | +| Persist full browser state | Profiles API | https://steel.apidocumentation.com/api-reference#tag/profiles | +| Reuse cookies/localStorage once | Session context | https://docs.steel.dev/overview/sessions-api/reusing-auth-context | +| Secure login injection | Credentials API | https://steel.apidocumentation.com/api-reference#tag/credentials | +| CAPTCHA status and solving | CAPTCHAs API | https://steel.apidocumentation.com/api-reference#tag/captchas | +| Steel-managed or BYOP proxying | Session `useProxy` | https://docs.steel.dev/overview/stealth/proxies | +| Upload/download files | Files API | https://steel.apidocumentation.com/api-reference#tag/files | +| Add Chrome extensions | Extensions API | https://steel.apidocumentation.com/api-reference#tag/extensions | +| Embed live or past session viewers | Session embeds | https://docs.steel.dev/overview/sessions-api/embed-sessions | +| Review agent activity timeline | Agent traces | https://docs.steel.dev/overview/agent-traces/overview | +| Mobile viewport/fingerprints | Mobile mode | https://docs.steel.dev/overview/sessions-api/mobile-mode | +| Region placement | Multi-region | https://docs.steel.dev/overview/sessions-api/multi-region | +| User handoff/control | Human-in-the-loop | https://docs.steel.dev/overview/sessions-api/human-in-the-loop | + +## Sessions + +Use sessions for multi-step browser automation, auth state, file uploads/downloads, extensions, proxies, CAPTCHAs, and any workflow that needs Playwright or Puppeteer control. + +Gotchas: + +- Release sessions in cleanup instead of waiting for timeout. +- Construct the CDP URL explicitly as `wss://connect.steel.dev?apiKey=...&sessionId=...`. +- Reuse the default browser context after connecting. +- Set `timeout` at creation time; do not assume live session timeouts can be edited. +- Use SDK types or the API reference before adding less-common create options. + +References: + +- Create session: https://steel.apidocumentation.com/api-reference#tag/sessions/post/v1/sessions +- Session lifecycle: https://docs.steel.dev/overview/sessions-api/session-lifecycle +- Quickstart: https://docs.steel.dev/overview/sessions-api/quickstart + +## Browser Tools + +Use Browser Tools for one-shot reads or artifacts when you do not need long-running session state. + +Use cases: + +- `client.scrape` for HTML, cleaned HTML, Markdown, Readability, metadata, and links. +- `client.screenshot` for a hosted PNG. +- `client.pdf` for a hosted PDF. +- `client.scrape` with `screenshot: true` or `pdf: true` when content and artifact should be captured together. + +Gotchas: + +- Browser Tools are stateless and rate limited; use sessions for cookies, multi-step navigation, file upload, extensions, or proxy geotargeting. +- `useProxy: true` on Browser Tools requires a plan that supports Steel-managed proxies. +- Use `delay` for hydrated client-rendered pages. + +Reference: https://docs.steel.dev/overview/browser-tools/overview + +## Profiles And Auth Context + +Prefer profiles for reusable browser identity. Use auth context for a lighter transfer of cookies/localStorage between live sessions. + +Profiles: + +- Use `persistProfile` on a first session to create a profile. +- Reuse with `profileId`. +- Pass both `profileId` and `persistProfile` when the session should update stored state. +- Profiles can include cookies, auth state, extensions, credentials, and browser settings. +- Profiles have size and retention limits; check docs for current details. + +Auth context: + +- Capture context before releasing the source session. +- Treat captured context as sensitive data. +- Pass captured context into a new session as `sessionContext`. +- Prefer profiles when you need full browser user data or repeated reuse. + +References: + +- Profiles API: https://docs.steel.dev/overview/profiles-api/overview +- Reusing context and auth: https://docs.steel.dev/overview/sessions-api/reusing-auth-context + +## Credentials + +Use Credentials API when login secrets should not be exposed to the agent, generated code logs, page scripts, or prompts. + +Gotchas: + +- Create credentials once per `origin` and `namespace`. +- Session `namespace` must exactly match the credential namespace. +- Pass `credentials: {}` to enable default injection options. +- Default options include `autoSubmit`, `blurFields`, and `exactOrigin`; consult SDK types/API docs for exact names. +- Navigate to the login page, wait for injection, then assert successful login. +- Use `totpSecret` for TOTP when supported by the target flow. + +Reference: https://docs.steel.dev/overview/credentials-api/overview + +## CAPTCHAs + +Enable CAPTCHA solving only when the plan supports it. + +Patterns: + +- Auto solve: create session with `solveCaptcha: true` / `solve_captcha=True`. +- Detect but manually solve: enable solving but set `stealthConfig.autoCaptchaSolving` false. +- Poll status with `client.sessions.captchas.status(session.id)`. +- Manually solve all detected CAPTCHAs or target a `taskId`, `url`, or `pageId`. +- Use image CAPTCHA solving only when you can provide stable XPath selectors. + +Gotchas: + +- Handle failed statuses and timeouts explicitly. +- CAPTCHA solving may need proxies and natural pacing. +- Do not silently enable paid solving for Hobby plans. + +References: + +- CAPTCHAs API: https://docs.steel.dev/overview/captchas-api/overview +- CAPTCHA solving overview: https://docs.steel.dev/overview/stealth/captcha-solving + +## Proxies + +Start without proxies, then add them when a target blocks Steel's default datacenter IPs or requires geolocation. + +Patterns: + +- Default: no proxy, available on all plans. +- Steel-managed proxy: `useProxy: true` / `use_proxy=True`, plan-gated. +- Geotargeting: prefer country-level before state/city-level targeting. +- BYOP: pass a proxy `server`; this is separate from Steel-managed proxy bandwidth. + +Gotchas: + +- Retry transient proxy errors like `ERR_TUNNEL_CONNECTION_FAILED`. +- If using BYOP, keep proxy credentials in environment variables. +- Some domains may be restricted by proxy provider compliance policies. + +Reference: https://docs.steel.dev/overview/stealth/proxies + +## Files + +Use Files API when a session needs uploaded inputs or must retrieve downloaded outputs. + +Patterns: + +- Upload a file into an active session before setting a file input. +- Upload once to global files, then mount into sessions by path. +- Download one file or the session archive after automation. +- Use CDP or framework-specific file upload APIs with the file path returned by Steel. + +Gotchas: + +- File path formats can differ between SDK helpers and raw HTTP paths; consult the API reference. +- Session files are tied to the session environment and can be promoted/backed up after release. +- Browser Use download flows may need explicit download path handling. + +Reference: https://docs.steel.dev/overview/files-api/overview + +## Extensions + +Use Extensions API when a Chrome extension should be installed into a Steel session. + +Patterns: + +- Upload an extension from `.zip` / `.crx` or a Chrome Web Store URL. +- List extensions to retrieve IDs. +- Start sessions with `extensionIds` / `extension_ids`. +- Use `all_ext` only when all installed extensions should load. + +Gotchas: + +- Upload/update/delete extensions are organization-scoped operations. +- Extensions initialize at session start; create a new session after changing extension configuration. +- Validate extension permissions and manifest before upload. + +Reference: https://docs.steel.dev/overview/extensions-api/overview + +## Session UX APIs + +Use these when building user-facing products on top of Steel sessions. + +Patterns: + +- Use live session viewer URLs for debugging and human takeover. +- Use past session embeds for replay or post-run inspection. +- Use agent traces for timeline/debug views and exports. +- Use human-in-the-loop controls when a user should take over sensitive steps. +- Use mobile mode, dimensions, and multi-region when the target requires a specific device or location profile. + +References: + +- Embed sessions: https://docs.steel.dev/overview/sessions-api/embed-sessions +- Live sessions: https://docs.steel.dev/overview/sessions-api/embed-sessions/live-sessions +- Past sessions: https://docs.steel.dev/overview/sessions-api/embed-sessions/past-sessions +- Agent traces: https://docs.steel.dev/overview/agent-traces/overview +- Human-in-the-loop: https://docs.steel.dev/overview/sessions-api/human-in-the-loop +- Mobile mode: https://docs.steel.dev/overview/sessions-api/mobile-mode +- Multi-region: https://docs.steel.dev/overview/sessions-api/multi-region diff --git a/skills/steel-developer/ECOSYSTEM.md b/skills/steel-developer/ECOSYSTEM.md new file mode 100644 index 0000000..241cb1d --- /dev/null +++ b/skills/steel-developer/ECOSYSTEM.md @@ -0,0 +1,169 @@ +# Steel Ecosystem + +Use this file to route users to the right Steel-supported integration. Keep direct Playwright and Puppeteer as the baseline unless a bigger framework clearly matches the user's goal. + +## Routing Principles + +- Use direct Playwright or Puppeteer for deterministic scripts, backend jobs, tests, and workflows with stable selectors. +- Use Stagehand when a TypeScript user wants natural-language browser actions like `act`, `extract`, and `observe`. +- Use Browser Use when a Python user wants an LLM browser agent loop. +- Use computer-use integrations when the model should reason from screenshots and emit low-level actions. +- Use typed agent frameworks when the user is building a product with tools, typed outputs, state, tracing, or multi-agent orchestration. +- Use the `steel-browser` skill when the agent should browse, extract, screenshot, or fill forms now instead of writing reusable code. +- Ignore Selenium unless the user explicitly asks for it. + +## Source Of Truth + +- Integrations index: https://docs.steel.dev/integrations +- Cookbook index: https://docs.steel.dev/cookbook +- API reference: https://steel.apidocumentation.com/api-reference +- Node SDK: https://github.com/steel-dev/steel-node +- Python SDK: https://github.com/steel-dev/steel-python + +When fetching integration docs with `curl`, use `curl -sSfL` and the `/llms.mdx/` URL form. + +## Choose The Integration + +| User goal | Recommended route | Language | Docs | +| --- | --- | --- | --- | +| Deterministic browser script | Playwright | TypeScript or Python | https://docs.steel.dev/integrations/playwright | +| Deterministic Chrome script | Puppeteer | TypeScript | https://docs.steel.dev/integrations/puppeteer | +| Natural-language browser actions | Stagehand | TypeScript | https://docs.steel.dev/integrations/stagehand | +| LLM browser agent loop | Browser Use | Python | https://docs.steel.dev/integrations/browser-use | +| Typed browser tools and handoffs | OpenAI Agents SDK | TypeScript or Python | https://docs.steel.dev/integrations/openai-agents-sdk | +| Typed streaming agent product | Vercel AI SDK | TypeScript | https://docs.steel.dev/integrations/ai-sdk | +| TypeScript agent registry/studio | Mastra | TypeScript | https://docs.steel.dev/integrations/mastra | +| Python state-machine agent | LangGraph | Python | https://docs.steel.dev/integrations/langgraph | +| Provider-agnostic typed Python agent | Pydantic AI | Python | https://docs.steel.dev/integrations/pydantic-ai | +| Python multi-agent team | Agno or CrewAI | Python | https://docs.steel.dev/integrations/agno | +| TypeScript multi-agent network | AgentKit | TypeScript | https://docs.steel.dev/integrations/agentkit | +| Model-native screenshot/action loop | Claude, OpenAI, or Gemini Computer Use | TypeScript or Python | https://docs.steel.dev/integrations/claude-computer-use | +| Reliable structured browser agent | Notte or Magnitude | Python or TypeScript | https://docs.steel.dev/integrations/notte | +| Coding agent should operate browser now | `steel-browser` skill | CLI | https://docs.steel.dev/overview/steel-cli | + +## Baseline: Playwright And Puppeteer + +Use these first for most reusable workflows. + +Rules: + +- Create a Steel session in application code. +- Construct `wss://connect.steel.dev?apiKey=...&sessionId=...` explicitly. +- Connect over CDP. +- Reuse the default context and page. +- Release the Steel session in cleanup. +- Add profiles, credentials, files, extensions, proxies, and CAPTCHA handling only as needed. + +Cookbook: + +- Playwright: https://docs.steel.dev/cookbook/playwright +- Puppeteer: https://docs.steel.dev/cookbook/puppeteer + +## Stagehand + +Use Stagehand when TypeScript users want natural-language actions and structured extraction without hand-writing every selector. + +Good fit: + +- Sites with changing UI where semantic actions are easier than selectors. +- Extracting structured data from pages. +- Prototypes that may later be hardened with direct Playwright locators. + +Avoid when: + +- The workflow is stable and deterministic enough for direct Playwright. +- The user needs Python. + +References: + +- Integration: https://docs.steel.dev/integrations/stagehand +- Recipe: https://docs.steel.dev/cookbook/stagehand + +## Browser Use + +Use Browser Use when Python users want an autonomous browser agent that can navigate, fill forms, and extract data. + +Good fit: + +- Agentic tasks where the exact path is not known ahead of time. +- Python applications using vision-capable models. +- CAPTCHA-heavy flows where a Browser Use tool can call Steel CAPTCHA status/solve helpers. + +Gotchas: + +- Pass Steel's CDP URL into the Browser Use browser session. +- Keep Steel session lifecycle ownership explicit and release sessions in cleanup. +- For downloads and uploads, consult Files API guidance in [APIS.md](APIS.md). + +References: + +- Integration: https://docs.steel.dev/integrations/browser-use +- Recipe: https://docs.steel.dev/cookbook/browser-use +- CAPTCHA auto recipe: https://docs.steel.dev/cookbook/browser-use-captcha-auto +- CAPTCHA manual recipe: https://docs.steel.dev/cookbook/browser-use-captcha-manual + +## Computer Use Integrations + +Use computer-use integrations when the model should see screenshots and emit click/type/scroll actions rather than using DOM selectors. + +Routes: + +- Claude Computer Use: https://docs.steel.dev/integrations/claude-computer-use +- OpenAI Computer Use: https://docs.steel.dev/integrations/openai-computer-use +- Gemini Computer Use: https://docs.steel.dev/integrations/gemini-computer-use + +Good fit: + +- Visual workflows where DOM selectors are unreliable. +- Mobile or responsive UI tasks. +- Human-like interaction loops that need screenshots. + +Gotchas: + +- Use Steel session dimensions/mobile mode intentionally. +- Route model actions back through Steel's computer/session APIs. +- Preserve a viewer URL or trace for debugging. + +## Typed Agent Frameworks + +Use typed agent frameworks when building production agents with tools, tracing, streaming, structured output, state, or handoffs. + +TypeScript routes: + +- Vercel AI SDK for typed tools and streaming: https://docs.steel.dev/integrations/ai-sdk +- OpenAI Agents SDK for handoffs and guardrails: https://docs.steel.dev/integrations/openai-agents-sdk +- Mastra for TypeScript agents, registry, and local studio: https://docs.steel.dev/integrations/mastra +- AgentKit for TypeScript multi-agent networks: https://docs.steel.dev/integrations/agentkit + +Python routes: + +- LangGraph for explicit state-machine agents: https://docs.steel.dev/integrations/langgraph +- Pydantic AI for provider-agnostic typed agents: https://docs.steel.dev/integrations/pydantic-ai +- Agno for Python agent teams with memory/reasoning: https://docs.steel.dev/integrations/agno +- CrewAI for multi-agent crews: https://docs.steel.dev/integrations/crewai + +Specialized browser-agent routes: + +- Notte for reliable Python navigation and structured outputs: https://docs.steel.dev/integrations/notte +- Magnitude for TypeScript natural-language browser agents: https://docs.steel.dev/integrations/magnitude + +Pattern: + +- Expose Steel operations as framework tools. +- Keep session creation, CDP connection, and release explicit. +- Return `session.id` and `session.sessionViewerUrl` from open-session tools. +- Add a cleanup tool or cleanup path for long-running agents. +- Use agent traces and viewer URLs for debugging productized agents. + +## Coding Agent CLI Integrations + +Use CLI integrations when a coding agent should operate Steel itself from the terminal. + +Routes: + +- Claude Code: https://docs.steel.dev/integrations/claude-code +- Codex: https://docs.steel.dev/integrations/codex +- OpenClaw: https://docs.steel.dev/integrations/openclaw +- Pi Agent: https://docs.steel.dev/integrations/pi-agent + +For reusable code, come back to `steel-developer`. For live browsing by the agent, use `steel-browser`. diff --git a/skills/steel-developer/PYTHON.md b/skills/steel-developer/PYTHON.md new file mode 100644 index 0000000..7882e25 --- /dev/null +++ b/skills/steel-developer/PYTHON.md @@ -0,0 +1,272 @@ +# Steel Python + +Use this file for Python, Playwright Python, Browser Use, and Python browser-agent tasks. + +## Install and configure + +```bash +pip install steel-sdk playwright +``` + +Use `STEEL_API_KEY` from the environment. Do not hardcode API keys or target-site credentials. + +Additional references: + +- Read [APIS.md](APIS.md) before implementing browser tools, files, extensions, auth context, embeds, traces, mobile mode, multi-region, or exact API/SDK option lookups. +- Read [ECOSYSTEM.md](ECOSYSTEM.md) before choosing Browser Use, computer-use integrations, typed agent frameworks, or coding-agent integrations. +- Use the Python SDK repository for exact types and method signatures: https://github.com/steel-dev/steel-python + +## Check the plan before paid stealth features + +Run this before enabling `solve_captcha` or Steel-managed `use_proxy`: + +```bash +curl -sSfL "https://api.steel.dev/v1/details" \ + -H "steel-api-key: $STEEL_API_KEY" | jq -r '.plan' +``` + +If the plan is `hobby`, do not enable `solve_captcha` or Steel-managed `use_proxy`. Use default datacenter networking, BYOP, or ask the user to upgrade. + +## Create a session + +```python +import os +from steel import Steel + +steel_api_key = os.environ["STEEL_API_KEY"] +client = Steel(steel_api_key=steel_api_key) + +session = client.sessions.create( + # Only enable these after checking the plan. + # solve_captcha=True, + # use_proxy=True, +) + +cdp_url = f"wss://connect.steel.dev?apiKey={steel_api_key}&sessionId={session.id}" +print(f"Live view: {session.session_viewer_url}") +``` + +## Connect with Playwright + +Reuse Steel's default context and the page already opened by the session. Do not call `browser.new_context()` unless the user explicitly needs a separate context. + +```python +from playwright.async_api import async_playwright + +playwright = await async_playwright().start() +browser = await playwright.chromium.connect_over_cdp(cdp_url) +context = browser.contexts[0] +page = context.pages[0] if context.pages else await context.new_page() + +try: + await page.goto("https://example.com", wait_until="domcontentloaded") +finally: + await browser.close() + await playwright.stop() + client.sessions.release(session.id) +``` + +Puppeteer is not the Python path. Use Playwright for Python browser control or Browser Use for an agent loop. + +## Build automations + +Prefer stable app semantics over brittle CSS selectors. + +```python +await page.goto("https://app.example.com/login", wait_until="domcontentloaded") +await page.get_by_label("Email").fill("user@example.com") +await page.get_by_label("Password").fill(os.environ["APP_PASSWORD"]) +await page.get_by_role("button", name="Sign in").click() +await page.wait_for_url("**/dashboard") + +rows = await page.locator("[data-row]").evaluate_all( + "elements => elements.map(element => element.textContent.trim()).filter(Boolean)" +) +``` + +For synchronous scripts, use `playwright.sync_api`, but keep the same Steel pattern: create session, connect over CDP, reuse `browser.contexts[0]`, release the session in cleanup. + +## Use profiles for reusable state + +Profiles persist browser user data such as cookies, auth state, extensions, credentials, and settings. + +```python +first_session = client.sessions.create(persist_profile=True) +# Run the login/setup flow, then release so Steel can persist the profile. +client.sessions.release(first_session.id) + +second_session = client.sessions.create( + profile_id=first_session.profile_id, + persist_profile=True, +) +``` + +Use `profile_id` to load state. Add `persist_profile=True` when the session should update the stored profile after it releases. + +Use [APIS.md](APIS.md) when deciding between profiles and session auth context. + +## Use credentials safely + +Create credentials once per `origin` and `namespace`, then create sessions with the same namespace and `credentials` enabled. + +```python +client.credentials.create( + origin="https://app.example.com", + namespace="example:fred", + value={ + "username": "fred@example.com", + "password": os.environ["APP_PASSWORD"], + # "totpSecret": os.environ["APP_TOTP_SECRET"], + }, +) + +session = client.sessions.create( + namespace="example:fred", + credentials={ + "autoSubmit": True, + "blurFields": True, + "exactOrigin": True, + }, +) +``` + +After connecting, navigate to the login page, wait for injection, then assert login success. + +```python +await page.goto("https://app.example.com/login", wait_until="domcontentloaded") +await page.wait_for_timeout(2_000) +await page.wait_for_url("**/dashboard", timeout=30_000) +``` + +## Track CAPTCHA solves + +Enable solving only when the plan supports it. + +```python +session = client.sessions.create( + solve_captcha=True, +) +``` + +Use `sessions.captchas.status` to monitor progress. + +```python +import asyncio +import time + +ACTIVE_STATUSES = {"detected", "validating", "solving"} +FAILED_STATUSES = {"failed_to_detect", "failed_to_solve", "validation_failed"} + + +def field(value, name, default=None): + if isinstance(value, dict): + return value.get(name, default) + return getattr(value, name, default) + + +def summarize_captcha_states(states): + tasks = [task for state in states for task in (field(state, "tasks", []) or [])] + active_tasks = [task for task in tasks if field(task, "status") in ACTIVE_STATUSES] + failed_tasks = [task for task in tasks if field(task, "status") in FAILED_STATUSES] + solved_tasks = [task for task in tasks if field(task, "status") == "solved"] + active_pages = [state for state in states if field(state, "isSolvingCaptcha", False)] + + return { + "pages": len(states), + "active_pages": len(active_pages), + "active_tasks": len(active_tasks), + "solved_tasks": len(solved_tasks), + "failed_tasks": len(failed_tasks), + } + + +async def wait_for_captcha_solution(client, session_id, timeout_seconds=90): + deadline = time.monotonic() + timeout_seconds + + while time.monotonic() < deadline: + states = client.sessions.captchas.status(session_id) + summary = summarize_captcha_states(states) + print(summary) + + if summary["failed_tasks"]: + raise RuntimeError("CAPTCHA solve failed") + + if summary["active_pages"] == 0 and summary["active_tasks"] == 0: + return summary + + await asyncio.sleep(1) + + raise TimeoutError("Timed out waiting for CAPTCHA solving") +``` + +Call it after navigating to pages that may trigger CAPTCHA challenges: + +```python +await page.goto("https://example.com/protected", wait_until="domcontentloaded") +await wait_for_captcha_solution(client, session.id) +``` + +## Stealth and proxies + +Start simple. Add stealth features only when needed and plan-compatible. + +```python +session = client.sessions.create( + use_proxy=True, + solve_captcha=True, +) +``` + +Use broad geotargeting before narrow city-level targeting. + +```python +session = client.sessions.create( + use_proxy={ + "geolocation": {"country": "US"}, + }, +) +``` + +Use BYOP when the user provides their own proxy server or the plan does not include Steel-managed proxies. + +```python +session = client.sessions.create( + use_proxy={ + "server": os.environ["PROXY_SERVER"], + }, +) +``` + +Recommendations: + +- Establish a baseline without proxies first +- Reuse profiles for sites where cookies and reputation help +- Add natural delays instead of rapid repeated actions +- Retry transient proxy errors such as `ERR_TUNNEL_CONNECTION_FAILED` +- Prefer country-level targeting unless location precision is required + +## Bigger Python frameworks + +Use direct Playwright for deterministic tools. Use Browser Use when the user wants an LLM agent loop that navigates, fills forms, and extracts data. + +Use [ECOSYSTEM.md](ECOSYSTEM.md) to route between Browser Use, LangGraph, Pydantic AI, Agno, CrewAI, Notte, and computer-use integrations. + +```python +from browser_use import Agent, BrowserSession +from browser_use.llm import ChatOpenAI + +agent = Agent( + task="Find the latest news on Steel.dev", + llm=ChatOpenAI(model="gpt-5", api_key=os.environ["OPENAI_API_KEY"]), + browser_session=BrowserSession(cdp_url=cdp_url), +) + +result = await agent.run() +``` + +For CAPTCHA-heavy Browser Use flows, expose `wait_for_captcha_solution` as a Browser Use tool and instruct the agent to call it when it sees a CAPTCHA. + +- Browser Use integration: https://docs.steel.dev/integrations/browser-use +- Browser Use recipe: https://docs.steel.dev/cookbook/browser-use +- Browser Use CAPTCHA recipe: https://docs.steel.dev/cookbook/browser-use-captcha-auto +- Playwright integration: https://docs.steel.dev/integrations/playwright diff --git a/skills/steel-developer/README.md b/skills/steel-developer/README.md new file mode 100644 index 0000000..09cea0b --- /dev/null +++ b/skills/steel-developer/README.md @@ -0,0 +1,34 @@ +# Steel Developer Skill + +Skill for building reusable software on Steel cloud browsers with the Steel SDK, REST APIs, Playwright, Puppeteer, Stagehand, Browser Use, credentials, profiles, proxies, and CAPTCHA APIs. + +## Install + +From local checkout: + +```bash +npx skills add ./skills/steel-developer +``` + +From GitHub subdirectory: + +```bash +npx skills add github:steel-dev/cli/skills/steel-developer +``` + +## When to use this skill + +Use this skill when writing, debugging, or explaining application code, scripts, reusable workflows, examples, or docs that run on Steel. + +Use `steel-browser` instead when the agent should operate a live browser itself via `steel scrape`, `steel screenshot`, `steel pdf`, or `steel browser ...`. + +## Contents + +- `SKILL.md`: routing, non-negotiables, and workflow guidance. +- `APIS.md`: first-party API routing, source-of-truth links, docs-fetching guidance, and API gotchas. +- `ECOSYSTEM.md`: Steel ecosystem routing for automation libraries, agent frameworks, computer-use integrations, and coding-agent integrations. +- `TYPESCRIPT.md`: TypeScript, JavaScript, Playwright, Puppeteer, and Stagehand examples. +- `PYTHON.md`: Python, Playwright Python, and Browser Use examples. +- `evals/evals.json`: eval prompts and assertions for routing, API coverage, and non-negotiable implementation patterns. + +The eval file uses `assertions` because `agent-skills-eval` grades that key. Older local fixtures may use `expectations`, but those are ignored by this runner. diff --git a/skills/steel-developer/SKILL.md b/skills/steel-developer/SKILL.md new file mode 100644 index 0000000..91dabb7 --- /dev/null +++ b/skills/steel-developer/SKILL.md @@ -0,0 +1,223 @@ +--- +name: steel-developer +description: Builds software on Steel cloud browsers with SDK/REST sessions, Playwright, Puppeteer, Stagehand, Browser Use, credentials, profiles, proxies, and CAPTCHA APIs. Use ONLY when writing, debugging, or explaining application code, scripts, reusable workflows, examples, or docs that run on Steel. Do not use for live web browsing, extraction, screenshots, or form filling performed by the agent; use the steel-browser CLI skill for that. +license: MIT +compatibility: opencode +metadata: + owner: steel + workflow: education +--- + +# Steel Developer + +## Skill boundary + +- Use this skill when the user wants code or implementation guidance they will run later: SDK setup, Steel sessions, Playwright/Puppeteer connections, Stagehand, Browser Use, credentials, profiles, proxies, CAPTCHA APIs, or reusable automations +- Do not use this skill when the user wants the agent to browse a site now, extract content now, capture a screenshot/PDF now, or interact with a form now; use the `steel-browser` CLI skill for those live tasks +- If a task needs both, use `steel-browser` only for live reconnaissance and use this skill for the final reusable code +- Do not copy SDK examples from the `steel-browser` CLI skill; this skill is the source of truth for building on Steel + +## Route by language + +Load the relevant reference before writing code: + +- TypeScript, JavaScript, Node.js, Playwright, Puppeteer, or Stagehand: read [TYPESCRIPT.md](TYPESCRIPT.md) +- Python, Playwright Python, Browser Use, or Python agents: read [PYTHON.md](PYTHON.md) +- First-party Steel APIs, exact docs/API/SDK lookup, browser tools, files, extensions, auth context, embeds, traces, mobile mode, or multi-region: read [APIS.md](APIS.md) +- Framework choice, Stagehand, Browser Use, computer-use integrations, typed agent frameworks, or coding-agent integrations: read [ECOSYSTEM.md](ECOSYSTEM.md) +- Mixed-language or ambiguous stack: inspect the repo, then read the matching file or ask one short question + +## Non-negotiables + +- Auth env var is `STEEL_API_KEY` +- Node examples import the SDK package as `import Steel from "steel-sdk"`; the SDK source repo is `steel-dev/steel-node` +- Node SDK constructor uses `steelAPIKey` +- Python SDK constructor uses `steel_api_key` +- Always release sessions in cleanup +- Construct the WebSocket URL explicitly as `wss://connect.steel.dev?apiKey=...&sessionId=...` +- Reuse the default browser context after connecting; do not create a new context unless the user explicitly needs isolation +- Do not leak raw credentials into prompts, page scripts, logs, or generated examples when Steel Credentials can be used instead +- For exact API fields, response shapes, and SDK method signatures, consult the API reference or SDK types; do not guess from memory +- When retrieving Steel docs via shell, use `curl -sSfL` so redirects are followed and HTTP failures are visible +- Never use stale SDK patterns: `steelClient`, `client.createSession`, `client.releaseSession`, `Steel(api_key=...)`, `session.websocketUrl`, `session.websocket_url`, Playwright `chromium.connect({ wsEndpoint })`, or top-level `browser.newPage()` for the default Steel session +- For API helpers beyond basic sessions, include a short source-of-truth note: "Check the Steel API reference or SDK types for exact method signatures and response fields." + +## Plan gate + +Before enabling `solveCaptcha` or Steel-managed `useProxy`, determine the plan: + +```bash +curl -sSfL "https://api.steel.dev/v1/details" \ + -H "steel-api-key: $STEEL_API_KEY" | jq -r '.plan' +``` + +- Hobby plans do not have CAPTCHA solves or Steel-managed proxies +- If the plan is Hobby, do not silently enable `solveCaptcha` or `useProxy: true`; use default datacenter networking, BYOP if appropriate, or ask the user to upgrade +- BYOP is separate from Steel-managed proxy bandwidth and can be used when the user provides their own proxy server + +## Workflows + +### 1. Choose the right Steel primitive + +- One-shot live web task for the agent: use `steel-browser`, not this skill +- Scrape-only feature inside an app or script: use REST scrape or SDK scrape +- Browser automation task: create a session, connect Playwright or Puppeteer, do the work, release the session +- Reusable authenticated state: use profiles +- Secure login without exposing secrets to the agent: use credentials +- File upload/download workflows: use Steel Files APIs and a session-backed browser +- Browser extension workflows: upload/manage extensions, then start sessions with extension IDs +- Anti-bot mitigation: start with Steel defaults, then add profiles, natural pacing, managed proxies or BYOP, and CAPTCHA solving when the plan allows it + +### 2. Build a session-based browser tool + +- Create a Steel client +- Create a session with only the options needed, such as `timeout`, `useProxy`, `solveCaptcha`, `profileId`, `persistProfile`, `namespace`, or `credentials` +- Connect the browser library to `wss://connect.steel.dev?apiKey=&sessionId=` +- Reuse the default context and existing page +- Close the browser and release the Steel session in cleanup + +TypeScript Playwright shape: + +```ts +const client = new Steel({ steelAPIKey }); +const session = await client.sessions.create(); +const cdpUrl = `wss://connect.steel.dev?apiKey=${steelAPIKey}&sessionId=${session.id}`; +const browser = await chromium.connectOverCDP(cdpUrl); +const context = browser.contexts()[0]; +const page = context.pages()[0] ?? (await context.newPage()); +try { + await page.goto("https://example.com"); +} finally { + await browser.close(); + await client.sessions.release(session.id); +} +``` + +Python Playwright shape: + +```python +steel_api_key = os.environ["STEEL_API_KEY"] +client = Steel(steel_api_key=steel_api_key) +session = client.sessions.create() +cdp_url = f"wss://connect.steel.dev?apiKey={steel_api_key}&sessionId={session.id}" +browser = await playwright.chromium.connect_over_cdp(cdp_url) +context = browser.contexts[0] +page = context.pages[0] if context.pages else await context.new_page() +try: + await page.goto("https://example.com") +finally: + await browser.close() + await playwright.stop() + client.sessions.release(session.id) +``` + +### 3. Use profiles correctly + +- First session: set `persistProfile: true` to create and save a profile +- Reuse later: pass `profileId` +- Keep evolving stored state: pass both `profileId` and `persistProfile: true` +- Use profiles for cookies, auth state, extensions, settings, and browser context reuse across sessions +- `sessionContext` is lighter cookie/localStorage transfer; capture it with `client.sessions.context(session.id)` before releasing the source session, then pass `sessionContext` into a new session + +Profile shape: + +```ts +const firstSession = await client.sessions.create({ persistProfile: true }); +// Log in or configure browser state, then release so Steel persists it. +await client.sessions.release(firstSession.id); + +const secondSession = await client.sessions.create({ profileId: firstSession.profileId }); +const updatedSession = await client.sessions.create({ + profileId: firstSession.profileId, + persistProfile: true, +}); +``` + +Auth context shape: + +```ts +const sessionContext = await client.sessions.context(sourceSession.id); +await client.sessions.release(sourceSession.id); +const nextSession = await client.sessions.create({ sessionContext }); +``` + +### 4. Use credentials correctly + +- Store credentials once per origin and namespace +- Start the session with matching `namespace` and `credentials: {}` +- Navigate to the login page and allow time for injection +- Verify login success with a page assertion instead of assuming it worked +- Credential values must come from environment variables such as `APP_PASSWORD` and optional `APP_TOTP_SECRET`; never write placeholders like `your_password_here` into examples +- Do not fill username/password/TOTP with Playwright when using Steel Credentials; create credentials, enable them on the session, navigate, wait, and assert success +- Do not invent a TOTP retrieval helper; store `totpSecret` in `client.credentials.create` and let Steel inject it + +Credential shape: + +```ts +await client.credentials.create({ + origin: "https://app.example.com", + namespace: "example:fred", + value: { + username: process.env.APP_USERNAME!, + password: process.env.APP_PASSWORD!, + totpSecret: process.env.APP_TOTP_SECRET, + }, +}); + +const session = await client.sessions.create({ + namespace: "example:fred", + credentials: {}, +}); +``` + +## API shortcuts + +- Browser Tools are the correct choice for one-shot scrape/screenshot/PDF with no reusable browser state; do not create a session for these +- Node one-shot scrape shape: `await client.scrape({ url, format: ["markdown"], screenshot: true, pdf: true })`, then read `result.metadata.title`; always say "Check the Steel API reference or SDK types for exact response fields." +- Files: upload or mount files into the Steel session first, then use the Steel-returned file path for Playwright/Puppeteer file inputs; always say "Check the Steel API reference or SDK types for exact Files API method signatures." +- Extensions: Chrome extensions are not Files API uploads; use Extensions API (`client.extensions.upload` or list existing extensions) to get a specific `extensionId`, then start a session with `extensionIds: [extensionId]`; explicitly say extensions are organization-scoped, initialize at session start, and exact method signatures come from the Steel API reference or SDK types; do not use `all_ext` unless explicitly requested +- CAPTCHAs: show the `curl -sSfL https://api.steel.dev/v1/details` plan check first; after plan gating, create the session with `solveCaptcha: true` and `stealthConfig: { autoCaptchaSolving: false }` for manual mode; poll `client.sessions.captchas.status(session.id)`, solve targeted tasks with `client.sessions.captchas.solve(session.id, { taskId })`, and handle failed statuses/timeouts +- Hobby plus BYOP: use `useProxy: { server: process.env.PROXY_SERVER! }`, not Steel-managed `useProxy: true`; BYOP is separate from Steel-managed proxy bandwidth; always say to check plan before enabling Steel-managed proxies and to retry/fallback on transient proxy errors such as `ERR_TUNNEL_CONNECTION_FAILED` + +Manual CAPTCHA shape: + +```ts +const session = await client.sessions.create({ + solveCaptcha: true, + stealthConfig: { autoCaptchaSolving: false }, +}); +const states = await client.sessions.captchas.status(session.id); +await client.sessions.captchas.solve(session.id, { taskId }); +``` + +## Source-of-truth links + +- API reference: https://steel.apidocumentation.com/api-reference +- Sessions create: https://steel.apidocumentation.com/api-reference#tag/sessions/post/v1/sessions +- Node SDK/types: https://github.com/steel-dev/steel-node +- Python SDK/types: https://github.com/steel-dev/steel-python +- Docs index: `curl -sSfL https://docs.steel.dev/llms.txt` +- Full docs bundle: `curl -sSfL https://docs.steel.dev/llms-full.txt` +- Single docs page: `curl -sSfL https://docs.steel.dev/llms.mdx/` +- In answers about exact API or SDK lookup, include those `llms.txt`, `llms-full.txt`, or `/llms.mdx/` docs-fetching options by name, not just the API reference + +## Ecosystem routing + +- Direct Playwright/Puppeteer is the deterministic baseline +- Stagehand is the TypeScript path for natural-language browser actions; every Stagehand recommendation should also mention that direct Playwright/Puppeteer remains the deterministic baseline; docs: https://docs.steel.dev/integrations/stagehand and https://docs.steel.dev/cookbook/stagehand +- Browser Use is the Python path for an LLM browser-agent loop; pass the explicit Steel CDP URL into `BrowserSession`, expose CAPTCHA status helpers when needed, and release the Steel session in cleanup +- For typed agent products, route to Vercel AI SDK, OpenAI Agents SDK, Mastra, AgentKit, LangGraph, Pydantic AI, Agno, CrewAI, Notte, or Magnitude based on language and product needs; keep Steel session lifecycle explicit + +## Output guidance + +- Default to TypeScript examples for Puppeteer +- Default to TypeScript or Python examples for Playwright based on the user's stack +- Mention Browser Use for Python agent loops and Stagehand for TypeScript natural-language browser actions, but keep direct Playwright/Puppeteer examples as the baseline +- When the user asks "what can Steel do", explain scrape, browser sessions, proxies, CAPTCHA solving, profiles, credentials, extensions, files, and agent integrations +- When asked to implement, emit minimal runnable code with cleanup +- When the user asks for docs/source-of-truth lookup, mention `llms.txt`, `llms-full.txt`, and `/llms.mdx/` with `curl -sSfL` +- When recommending Stagehand, also mention direct Playwright/Puppeteer as the deterministic baseline and include the Stagehand integration/cookbook links +- When explaining profiles, always show all three profile calls: create with `persistProfile`, reuse with `profileId`, and update with both `profileId` plus `persistProfile`; also state `sessionContext` must be captured before releasing the source session +- When answering Browser Tools, Files, or Extensions questions, always include one sentence pointing to the Steel API reference or SDK types for exact fields/method signatures +- When answering BYOP/proxy questions, always mention plan-checking before Steel-managed proxies and retry/fallback for `ERR_TUNNEL_CONNECTION_FAILED` +- When the user asks the agent to browse a site now, do not offer a reusable script; route to `steel-browser` and suggest `steel scrape ` or `steel browser ...` diff --git a/skills/steel-developer/TYPESCRIPT.md b/skills/steel-developer/TYPESCRIPT.md new file mode 100644 index 0000000..998fa9b --- /dev/null +++ b/skills/steel-developer/TYPESCRIPT.md @@ -0,0 +1,284 @@ +# Steel TypeScript and JavaScript + +Use this file for Node.js, TypeScript, JavaScript, Playwright, Puppeteer, and Stagehand tasks. + +## Install and configure + +```bash +npm install steel-sdk playwright puppeteer-core +``` + +Use `STEEL_API_KEY` from the environment. Do not hardcode API keys or target-site credentials. + +Additional references: + +- Read [APIS.md](APIS.md) before implementing browser tools, files, extensions, auth context, embeds, traces, mobile mode, multi-region, or exact API/SDK option lookups. +- Read [ECOSYSTEM.md](ECOSYSTEM.md) before choosing Stagehand, Browser Use, computer-use integrations, typed agent frameworks, or coding-agent integrations. +- Use the Node SDK repository for exact types: https://github.com/steel-dev/steel-node + +## Check the plan before paid stealth features + +Run this before enabling `solveCaptcha` or Steel-managed `useProxy`: + +```bash +curl -sSfL "https://api.steel.dev/v1/details" \ + -H "steel-api-key: $STEEL_API_KEY" | jq -r '.plan' +``` + +If the plan is `hobby`, do not enable `solveCaptcha` or Steel-managed `useProxy`. Use default datacenter networking, BYOP, or ask the user to upgrade. + +## Create a session + +```ts +import Steel from "steel-sdk"; + +const steelAPIKey = process.env.STEEL_API_KEY; +if (!steelAPIKey) throw new Error("STEEL_API_KEY is required"); + +const client = new Steel({ steelAPIKey }); + +const session = await client.sessions.create({ + timeout: 10 * 60 * 1000, + // Only enable these after checking the plan. + // solveCaptcha: true, + // useProxy: true, +}); + +const cdpUrl = `wss://connect.steel.dev?apiKey=${steelAPIKey}&sessionId=${session.id}`; +console.log(`Live view: ${session.sessionViewerUrl}`); +``` + +## Connect with Playwright + +Reuse Steel's default context and the page already opened by the session. Do not call `browser.newContext()` unless the user explicitly needs a separate context. + +```ts +import { chromium } from "playwright"; + +const browser = await chromium.connectOverCDP(cdpUrl); +const context = browser.contexts()[0]; +const page = context.pages()[0] ?? (await context.newPage()); + +try { + await page.goto("https://example.com", { waitUntil: "domcontentloaded" }); +} finally { + await browser.close(); + await client.sessions.release(session.id); +} +``` + +## Connect with Puppeteer + +Use the default browser context. Avoid `createBrowserContext()` for normal Steel sessions. + +```ts +import puppeteer from "puppeteer-core"; + +const browser = await puppeteer.connect({ browserWSEndpoint: cdpUrl }); +const context = browser.defaultBrowserContext(); +const page = (await context.pages())[0] ?? (await context.newPage()); + +try { + await page.goto("https://example.com", { waitUntil: "domcontentloaded" }); +} finally { + await browser.close(); + await client.sessions.release(session.id); +} +``` + +## Build automations + +Prefer stable app semantics over brittle CSS selectors. + +### Playwright automation + +```ts +await page.goto("https://app.example.com/login", { waitUntil: "domcontentloaded" }); +await page.getByLabel(/email/i).fill("user@example.com"); +await page.getByLabel(/password/i).fill(process.env.APP_PASSWORD!); +await page.getByRole("button", { name: /sign in/i }).click(); +await page.waitForURL(/dashboard|home/); + +const rows = await page.locator("[data-row]").evaluateAll((elements) => + elements.map((element) => element.textContent?.trim()).filter(Boolean), +); +``` + +### Puppeteer automation + +```ts +await page.goto("https://app.example.com/login", { waitUntil: "networkidle2" }); +await page.waitForSelector("input[name=email]"); +await page.type("input[name=email]", "user@example.com"); +await page.type("input[name=password]", process.env.APP_PASSWORD!); +await Promise.all([ + page.waitForNavigation({ waitUntil: "networkidle2" }), + page.click("button[type=submit]"), +]); + +const title = await page.$eval("h1", (element) => element.textContent?.trim()); +``` + +## Use profiles for reusable state + +Profiles persist browser user data such as cookies, auth state, extensions, credentials, and settings. + +```ts +const firstSession = await client.sessions.create({ persistProfile: true }); +// Run the login/setup flow, then release so Steel can persist the profile. +await client.sessions.release(firstSession.id); + +const secondSession = await client.sessions.create({ + profileId: firstSession.profileId, + persistProfile: true, +}); +``` + +Use `profileId` to load state. Add `persistProfile: true` when the session should update the stored profile after it releases. + +Use [APIS.md](APIS.md) when deciding between profiles and session auth context. + +## Use credentials safely + +Create credentials once per `origin` and `namespace`, then create sessions with the same namespace and `credentials` enabled. + +```ts +await client.credentials.create({ + origin: "https://app.example.com", + namespace: "example:fred", + value: { + username: "fred@example.com", + password: process.env.APP_PASSWORD!, + // totpSecret: process.env.APP_TOTP_SECRET, + }, +}); + +const session = await client.sessions.create({ + namespace: "example:fred", + credentials: { + autoSubmit: true, + blurFields: true, + exactOrigin: true, + }, +}); +``` + +After connecting, navigate to the login page, wait for injection, then assert login success. + +```ts +await page.goto("https://app.example.com/login", { waitUntil: "domcontentloaded" }); +await page.waitForTimeout(2_000); +await page.waitForURL(/dashboard|home/, { timeout: 30_000 }); +``` + +## Track CAPTCHA solves + +Enable solving only when the plan supports it. + +```ts +const session = await client.sessions.create({ + solveCaptcha: true, + timeout: 30 * 60 * 1000, +}); +``` + +Use `sessions.captchas.status` to monitor progress. + +```ts +type CaptchaTask = { status?: string }; +type CaptchaState = { + url?: string; + isSolvingCaptcha?: boolean; + tasks?: CaptchaTask[]; +}; + +const activeStatuses = new Set(["detected", "validating", "solving"]); +const failedStatuses = new Set(["failed_to_detect", "failed_to_solve", "validation_failed"]); + +async function waitForCaptchaSolution(sessionId: string, timeoutMs = 90_000) { + const startedAt = Date.now(); + + while (Date.now() - startedAt < timeoutMs) { + const states = (await client.sessions.captchas.status(sessionId)) as CaptchaState[]; + const tasks = states.flatMap((state) => state.tasks ?? []); + const failed = tasks.filter((task) => failedStatuses.has(task.status ?? "")); + const active = states.filter( + (state) => + state.isSolvingCaptcha || + (state.tasks ?? []).some((task) => activeStatuses.has(task.status ?? "")), + ); + + console.log({ + pages: states.length, + activePages: active.length, + solvedTasks: tasks.filter((task) => task.status === "solved").length, + failedTasks: failed.length, + }); + + if (failed.length > 0) throw new Error("CAPTCHA solve failed"); + if (active.length === 0) return states; + + await new Promise((resolve) => setTimeout(resolve, 1_000)); + } + + throw new Error("Timed out waiting for CAPTCHA solving"); +} +``` + +Call it after navigating to pages that may trigger CAPTCHA challenges: + +```ts +await page.goto("https://example.com/protected", { waitUntil: "domcontentloaded" }); +await waitForCaptchaSolution(session.id); +``` + +## Stealth and proxies + +Start simple. Add stealth features only when needed and plan-compatible. + +```ts +const session = await client.sessions.create({ + useProxy: true, + solveCaptcha: true, + timeout: 30 * 60 * 1000, +}); +``` + +Use broad geotargeting before narrow city-level targeting. + +```ts +const session = await client.sessions.create({ + useProxy: { + geolocation: { country: "US" }, + }, +}); +``` + +Use BYOP when the user provides their own proxy server or the plan does not include Steel-managed proxies. + +```ts +const session = await client.sessions.create({ + useProxy: { + server: process.env.PROXY_SERVER!, + }, +}); +``` + +Recommendations: + +- Establish a baseline without proxies first +- Reuse profiles for sites where cookies and reputation help +- Add natural delays instead of rapid repeated actions +- Retry transient proxy errors such as `ERR_TUNNEL_CONNECTION_FAILED` +- Prefer country-level targeting unless location precision is required + +## Bigger TypeScript frameworks + +Use direct Playwright or Puppeteer for deterministic tools. Use Stagehand when the user wants natural-language browser actions like `act`, `extract`, and `observe`. + +Use [ECOSYSTEM.md](ECOSYSTEM.md) to route between Stagehand, Vercel AI SDK, OpenAI Agents SDK, Mastra, AgentKit, Magnitude, and computer-use integrations. + +- Stagehand integration: https://docs.steel.dev/integrations/stagehand +- Stagehand recipe: https://docs.steel.dev/cookbook/stagehand +- Puppeteer integration: https://docs.steel.dev/integrations/puppeteer +- Playwright integration: https://docs.steel.dev/integrations/playwright diff --git a/skills/steel-developer/evals/evals.json b/skills/steel-developer/evals/evals.json new file mode 100644 index 0000000..942b83c --- /dev/null +++ b/skills/steel-developer/evals/evals.json @@ -0,0 +1,186 @@ +{ + "skill_name": "steel-developer", + "evals": [ + { + "id": 1, + "prompt": "Write a minimal TypeScript script that creates a Steel session, connects Playwright, opens https://example.com, prints the page title, and cleans up.", + "expected_output": "A runnable TypeScript Playwright example that uses STEEL_API_KEY, new Steel({ steelAPIKey }), explicitly constructs wss://connect.steel.dev?apiKey=...&sessionId=..., reuses the default context/page, closes the browser, and releases the Steel session.", + "files": [], + "assertions": [ + "Uses process.env.STEEL_API_KEY and validates it is present", + "Constructs the CDP URL explicitly with wss://connect.steel.dev?apiKey=...&sessionId=...", + "Does not use session.websocketUrl or session.websocket_url", + "Uses browser.contexts()[0] and the existing page when available", + "Does not call browser.newContext()", + "Closes the browser and releases the Steel session in a cleanup path" + ] + }, + { + "id": 2, + "prompt": "Write a Python script using Steel and Playwright to open a cloud browser, navigate to https://example.com, print the title, and release the session.", + "expected_output": "A runnable Python Playwright example that uses STEEL_API_KEY, Steel(steel_api_key=...), explicitly constructs wss://connect.steel.dev?apiKey=...&sessionId=..., reuses browser.contexts[0], and releases the session in cleanup.", + "files": [], + "assertions": [ + "Uses os.environ['STEEL_API_KEY'] or equivalent environment lookup", + "Uses Steel(steel_api_key=...) and not api_key", + "Constructs the CDP URL explicitly with wss://connect.steel.dev?apiKey=...&sessionId=...", + "Does not suggest Puppeteer for Python", + "Uses browser.contexts[0] and the existing page when available", + "Stops Playwright, closes the browser, and releases the Steel session in cleanup" + ] + }, + { + "id": 3, + "prompt": "Build a Node.js utility that uses Steel to scrape https://docs.steel.dev as markdown, also captures a screenshot and PDF, and prints the page title. This should be one-shot and not need a reusable browser session.", + "expected_output": "A Node.js example that uses Steel Browser Tools, not a session, with client.scrape or equivalent one-shot API. It requests markdown plus screenshot/PDF artifacts, prints metadata.title, and points to API docs/SDK types for exact response fields if needed.", + "files": [], + "assertions": [ + "Does not create a Steel session for this one-shot scrape/screenshot/PDF workflow", + "Uses client.scrape or the one-shot scrape API", + "Requests markdown output", + "Includes screenshot and PDF capture in the scrape call or explains the equivalent screenshot/pdf Browser Tools", + "Does not connect Playwright or Puppeteer unnecessarily", + "Mentions API reference or SDK types as source of truth for exact response shapes" + ] + }, + { + "id": 4, + "prompt": "Show me how to build a Steel login automation using stored credentials for https://app.example.com. Use namespace example:fred, support TOTP if configured, and avoid exposing the password to the browser agent.", + "expected_output": "An example that creates credentials for origin https://app.example.com and namespace example:fred, stores password/TOTP from environment variables, creates a session with matching namespace and credentials enabled, navigates to the login page, waits for injection, and verifies login success.", + "files": [], + "assertions": [ + "Uses client.credentials.create with origin and namespace", + "Uses environment variables for password and optional totpSecret", + "Creates a session with the exact same namespace", + "Passes credentials: {} or explicit credential options to the session", + "Does not hardcode raw secrets in generated code", + "Waits for injection and asserts login success after navigation" + ] + }, + { + "id": 5, + "prompt": "I need to keep a user logged in across repeated Steel sessions. Show the recommended profile-based pattern, and briefly explain when I would use sessionContext instead.", + "expected_output": "Guidance and code showing persistProfile on the first session, reusing profileId on later sessions, using profileId plus persistProfile when updating stored state, and using sessionContext only for lighter one-off cookie/localStorage transfer captured before releasing a live session.", + "files": [], + "assertions": [ + "Uses persistProfile to create a profile", + "Uses profileId to reuse a profile", + "Uses profileId plus persistProfile to update stored profile state", + "Explains that sessionContext captures cookies/localStorage rather than full browser user data", + "States that sessionContext must be captured before releasing the source session", + "Mentions profiles are preferred for repeated reusable browser identity" + ] + }, + { + "id": 6, + "prompt": "Write a CAPTCHA-aware Steel browser workflow where I can detect CAPTCHAs but trigger solving manually for a specific task ID after checking status. Include the plan check before enabling solving.", + "expected_output": "A workflow that checks the Steel plan first, creates a session with CAPTCHA detection enabled and autoCaptchaSolving disabled, polls client.sessions.captchas.status(session.id), and calls a manual solve method with taskId when appropriate. It should handle failed statuses and timeouts.", + "files": [], + "assertions": [ + "Includes curl -sSfL https://api.steel.dev/v1/details with steel-api-key header or equivalent plan check", + "Does not silently enable CAPTCHA solving on Hobby plans", + "Uses solveCaptcha true or solve_captcha=True only after plan gating", + "Sets stealthConfig.autoCaptchaSolving to false or explains the manual-solving configuration", + "Uses client.sessions.captchas.status(session.id)", + "Shows targeted manual solve with taskId and handles failed statuses/timeouts" + ] + }, + { + "id": 7, + "prompt": "I'm on the Hobby plan but I have my own proxy server in PROXY_SERVER. Show how to use Steel with that proxy safely, and explain why this is different from Steel-managed proxy bandwidth.", + "expected_output": "Guidance and code that uses BYOP via useProxy/use_proxy server from an environment variable, does not enable Steel-managed useProxy: true on Hobby, explains BYOP is separate from Steel-managed proxy bandwidth, and includes retry guidance for transient proxy errors.", + "files": [], + "assertions": [ + "Does not use bare Steel-managed useProxy: true or use_proxy=True as the Hobby-plan solution", + "Uses useProxy/use_proxy with a server value read from PROXY_SERVER", + "States BYOP is separate from Steel-managed proxy bandwidth", + "Keeps proxy credentials out of generated logs and prompts", + "Mentions transient proxy errors like ERR_TUNNEL_CONNECTION_FAILED and retry/fallback handling", + "Recommends checking plan before using Steel-managed proxies" + ] + }, + { + "id": 8, + "prompt": "Build a TypeScript example that uploads ./data.csv into a Steel session and uses Playwright to set it on an input[type=file] before submitting a form.", + "expected_output": "A TypeScript example using the Files API to upload or mount data.csv into the session, then using the returned session file path with Playwright file upload or CDP, with cleanup for browser and session. It should point to the API reference/SDK types for exact file method signatures.", + "files": [], + "assertions": [ + "Uses Steel Files APIs rather than assuming the local file path exists inside the cloud browser", + "Uploads the file to the active session or uploads globally and mounts it into the session", + "Uses the Steel-returned file path for the browser file input", + "Connects Playwright using the explicit Steel CDP URL if browser control is needed", + "Releases the session in cleanup", + "Mentions API reference or SDK types for exact Files API method signatures" + ] + }, + { + "id": 9, + "prompt": "Show how to upload a Chrome extension to Steel and start a session with only that extension enabled. Do not load every extension in the organization.", + "expected_output": "Guidance and code that uploads or lists an extension to obtain its extensionId, creates a session with extensionIds containing that specific ID, and explains extension changes apply at session start. It should not use all_ext unless explicitly requested.", + "files": [], + "assertions": [ + "Uses the Extensions API to upload or list extensions", + "Uses a specific extensionId in session creation", + "Does not use all_ext", + "Mentions extensions are organization-scoped", + "Mentions extensions initialize at session start", + "Points to API reference or SDK types for exact extension method signatures" + ] + }, + { + "id": 10, + "prompt": "I want a TypeScript workflow where the browser actions can be written in natural language, but still run on Steel. Which integration should I use and what should the Steel session lifecycle look like?", + "expected_output": "Recommendation to use Stagehand for TypeScript natural-language browser actions, with Steel creating the session, explicit CDP URL construction, default context reuse if applicable, and session release. It should keep direct Playwright/Puppeteer as the baseline alternative for deterministic workflows.", + "files": [], + "assertions": [ + "Chooses Stagehand for TypeScript natural-language browser actions", + "Mentions direct Playwright/Puppeteer as the deterministic baseline", + "Creates a Steel session and constructs the CDP URL explicitly", + "Keeps session lifecycle ownership clear", + "Releases the Steel session in cleanup", + "Links or points to Stagehand integration/cookbook docs" + ] + }, + { + "id": 11, + "prompt": "Build a Python Browser Use agent that runs on a Steel cloud browser and knows how to wait for Steel CAPTCHA solving when it sees a CAPTCHA.", + "expected_output": "A Python Browser Use example that creates a Steel session, constructs an explicit CDP URL, passes it into BrowserSession, defines or exposes a helper that calls client.sessions.captchas.status(session.id), and releases the session in cleanup.", + "files": [], + "assertions": [ + "Chooses Browser Use for a Python browser-agent loop", + "Constructs the Steel CDP URL explicitly instead of using session.websocket_url", + "Passes the CDP URL into BrowserSession or the current Browser Use equivalent", + "Uses client.sessions.captchas.status(session.id) for CAPTCHA tracking", + "Instructs the agent/tool to wait for CAPTCHA solving when needed", + "Releases the Steel session in cleanup" + ] + }, + { + "id": 12, + "prompt": "I need exact request fields for POST /v1/sessions and exact Node SDK option types. How should I look them up from the terminal and where are the sources of truth?", + "expected_output": "Guidance that says to use the API reference for exact schemas, the steel-node repository/types for SDK option types, and curl -sSfL when fetching docs pages because docs URLs may redirect. It should avoid inventing a full schema from memory.", + "files": [], + "assertions": [ + "References https://steel.apidocumentation.com/api-reference#tag/sessions/post/v1/sessions", + "References https://github.com/steel-dev/steel-node", + "Uses curl -sSfL for fetching docs pages", + "Mentions llms.txt, llms-full.txt, or /llms.mdx/ for docs lookup", + "Does not fabricate a complete POST /v1/sessions schema", + "Says to use API reference and SDK types for exact fields and method signatures" + ] + }, + { + "id": 13, + "prompt": "Use Steel to go to https://example.com right now and tell me the H1 text. Do not write a reusable script.", + "expected_output": "The agent should route this to the steel-browser CLI skill rather than steel-developer, because the user wants live browsing by the agent, not reusable application code.", + "files": [], + "assertions": [ + "Does not write a Steel SDK application script as the primary answer", + "Recognizes this as a live agent-operated browser task", + "Uses or recommends the steel-browser skill/CLI path", + "If executed, uses steel scrape or steel browser commands rather than Playwright/Puppeteer code", + "Does not create reusable code unless the user asks afterward" + ] + } + ] +}