omp packages

@agntn/browsers0.6.1

Unified browser-as-a-service provider for AI agents and CLI.

GitHub Actions Published 15 days ago MIT ~1.1K downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

@agntn/browsers

npm version npm downloads license Ask DeepWiki

🌐 Eight vendors. One scrape call. JavaScript already ran.

Docs, a page per provider and one real scrape through each of them: browsers.agntn.dev.

Why?

Steel scrapes in one HTTP call. Kernel wants a live session first. Browserbase hands you CDP. A model will mix those three up, so this is one scrape() over eight backends.

✨ Features

  • 🧩 Eight backends, one contract. Steel, Browserbase, Kernel, Browserless, Hyperbrowser, Anchor, Cloudflare and Playwright. Same objects on your side. Anchor still has no scrape.
  • 🖥️ Playwright is local. No key. Chromium on the machine you already have.
  • ⚡ Stateless where the vendor is. Steel, Browserbase, Browserless, Hyperbrowser, Cloudflare and Playwright scrape without you opening a session. Kernel will not.
  • 📸 The extras follow the backend. PDFs, crawls, links, AI extract, search. Anchor will not scrape. Hyperbrowser will search.
  • 🏷️ capabilities() is the list. Navigate is true on Kernel, Browserless and Playwright. False on Steel. Read the flag.
  • 🤖 Five surfaces, twelve tools. CLI, library, MCP, Pi, OMP. They share the executors.
  • 📏 Agent scrape and crawl have a ceiling. 20 000 characters unless you pass maxChars, 200 000 at most. A crawl shares it across pages.
  • ⏳ A slow crawl keeps its job. Cloudflare and Hyperbrowser wait two minutes, then hand back the job ID. Pass it as jobId with the same provider and browser, or --job in the CLI, and you wait for that job again. No second crawl.
  • 🔗 Agent links come in pages. 500 per call unless you pass limit, 5 000 at most. The answer counts the rest and names the offset to ask for next. The CLI still prints them all.
  • ♿ A form reads as its fields. The accessibility tree gives one line per field, button or heading, with its state. The httpbin pizza form comes to about a thousand characters. Cloudflare only for now.
  • 🔐 Keys in a URL get scrubbed. Query params named token or api_key land in errors as [REDACTED].

📦 Install

pnpm add @agntn/browsers

Node.js 26 or newer.

🚀 First call

npx @agntn/browsers scrape https://example.com --provider playwright
ℹ Scraping via playwright...
This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.

هذا النطاق مُخصص للاستخدام في أمثلة التوثيق دون الحاجة إلى إذن. هذه ليست خدمة، يُرجى تجنب الاعتماد عليها لأغراض الاختبار والمراقبة.

该域名仅用于文档示例,无需获得许可。这并非一项服务,请勿将其用于测试和监控目的。

L’usage de ce domaine est réservé à des exemples de documentation, sans autorisation préalable. Il ne s’agit pas d’un service ; son utilisation à des fins de test ou de surveillance est à éviter.

Данный домен предназначен для использования в примерах документации без необходимости получения предварительного разрешения. Это не сервис; не рекомендуется его использование для тестирования и мониторинга.

Este dominio está destinado al uso en ejemplos de documentación sin necesidad de permiso. Esto no es un servicio; evitar utilizarlo para realizar pruebas o monitoreos.

Learn more

Six languages now, same notice. No key, no account. Playwright runs Chromium here. After pnpm add, that command is pnpm exec browsers, or install it once with pnpm add -g @agntn/browsers. Skip --provider and the first configured backend that can run the command wins. Steel if that key is set. Playwright if nothing else is. links on a Steel setup skips Steel, which has no links endpoint.

The scrape said "Learn more" and dropped the href. This did not:

browsers links https://example.com --provider playwright
https://iana.org/help/example-domains

Same page, different door. A form wants a third one, the accessibility tree:

browsers accessibility https://httpbin.org/forms/post --root fieldset
- group "Pizza Size"
  - Legend
    - StaticText "Pizza Size"
      - InlineTextBox
  - paragraph
    - none
      - radio "Small" checked=false
      - none
  - paragraph
    - none
      - radio "Medium" checked=false
      - none
  - paragraph
    - none
      - radio "Large" checked=false
      - none

--root keeps every node of that subtree, the boring ones too. Leave it out and you get the whole page, trimmed. Cloudflare answers this one.

Who is even configured?

browsers providers
● steel           scrape screenshot(sess) sessions cdp
● browserbase     scrape sessions cdp
● kernel          scrape(sess) screenshot(sess) navigate evaluate sessions cdp
● browserless     scrape screenshot element navigate evaluate sessions cdp pdf
● hyperbrowser    scrape screenshot sessions cdp crawl search extract
● anchor          screenshot(sess) sessions cdp
● cloudflare      scrape screenshot element sessions cdp crawl pdf links extract accessibility
● playwright      scrape screenshot(sess) element navigate evaluate sessions crawl pdf links

Filled dot means that key was in the env on this run. Playwright is always filled. A few more:

browsers scrape https://example.com --provider playwright --format html
browsers scrape https://example.com --provider playwright --waitFor p
browsers scrape https://example.com --browser kitesurf
browsers screenshot https://example.com -o page.png
browsers screenshot https://example.com -o notice.png --selector p -p browserless
browsers pdf https://example.com -o page.pdf --provider playwright
browsers crawl https://example.com --maxPages 5 --provider playwright
browsers crawl --job <job-id> --provider cloudflare
browsers search "browser automation" --provider hyperbrowser
browsers extract https://example.com --prompt "Extract the heading"
browsers session create --provider kernel

search is Hyperbrowser. extract is Cloudflare or Hyperbrowser. The rest take -p when you do not want the default. --browser kitesurf picks Cloudflare and opts into Kitesurf. Leave it out and Cloudflare stays on Chromium.

Commands

Command What it does Example
scrape Rendered page, markdown if it exists browsers scrape https://example.com -p playwright
screenshot Image to a file browsers screenshot https://example.com -o page.png
crawl Follow links, cap with --maxPages browsers crawl https://example.com --maxPages 5
pdf URL to a PDF file browsers pdf https://example.com -o page.pdf
links Unique hrefs browsers links https://example.com -p playwright
accessibility Roles, names and states, one per line browsers accessibility https://example.com
search Hyperbrowser web search browsers search "browser automation"
extract Structured extract with a prompt browsers extract https://example.com --prompt "..."
session create, list, release browsers session create -p kernel
providers Who is configured, what they can do browsers providers
mcp MCP server on stdio browsers mcp

🧠 Library

import { create } from "@agntn/browsers";

const provider = await create("playwright");
const page = await provider.scrape("https://example.com");
console.log(page.markdown ?? page.text ?? page.html);

const caps = provider.capabilities();
console.log(caps.statelessScrape, caps.pdf, caps.cdp);

That's most of it, really. create("steel") if you have STEEL_API_KEY. resolveProvider() picks the first one that does. Want Kitesurf on Cloudflare? Use create("cloudflare", { browser: "kitesurf" }). Without that option it stays on Chromium. Cloudflare scrape also fills title and statusCode. Ask it for html and markdown together, or add screenshot: true, and all of them come from one render. Kernel scrape wants a session object. The agent tools and the CLI scrape command open one and close it.

create() is async because that first call is where the provider module gets imported. Importing the package loads no provider and no HTTP client, providers() and has() answer from a manifest, and @agntn/browsers/providers/steel gives you one provider's factory directly.

🗺️ Providers

Provider Auth Capabilities
steel STEEL_API_KEY scrape, screenshot (session), sessions, CDP
browserbase BROWSERBASE_API_KEY scrape, sessions, CDP
kernel KERNEL_API_KEY scrape (session), screenshot (session), navigate, evaluate, sessions, CDP
browserless BROWSERLESS_API_KEY scrape, screenshot, element screenshot, navigate, evaluate, sessions, CDP, PDF
hyperbrowser HYPERBROWSER_API_KEY scrape, screenshot, sessions, CDP, crawl, search, extract
anchor ANCHOR_API_KEY screenshot (session), sessions, CDP
cloudflare CF_API_TOKEN + CF_ACCOUNT_ID scrape, screenshot, element screenshot, sessions, CDP, crawl, PDF, links, extract, accessibility tree
playwright none, local scrape, screenshot (session), element screenshot, navigate, evaluate, sessions, crawl, PDF, links

Anchor does not scrape. The row stays, because someone will look for it. Cloudflare also accepts CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.

🤖 Agents

browsers mcp
pi install git:github.com/agntn/browsers
omp install @agntn/browsers
{
  "mcpServers": {
    "browsers": { "command": "browsers", "args": ["mcp"] }
  }
}

Twelve tools, browsers_scrape through browsers_capabilities, the same twelve on MCP, Pi and OMP. Cloudflare calls backed by Browser Run take browser: "kitesurf". No provider needed then. They do not drive a session you already opened. browsers_screenshot hands the image back to the model. A full page too big for that goes to a file: pass path, and it refuses to overwrite one that exists. Pass selector and you get one element instead of the page. Cloudflare, Browserless and Playwright can do that. Leave provider out and one of them gets picked; name another and it says no rather than hand back the whole page. browsers_pdf always writes to path, under the same rule, since a PDF cannot go back to the model.

🚫 What this does not do

Need a fetch that is not a browser? @agntn/web. Need last year's page? @agntn/archives. Need to click through a login? Not this package. The tools never grew navigate or evaluate.

🧩 Adding a provider

Want a ninth? A class that implements BrowserProvider, an exported factory, a manifest entry in src/providers/index.ts and the name list in src/tool-contract.ts. Nonstandard env keys go in src/core/resolve.ts. Outside the package, register(name, defaultURL, factory) adds one at runtime.

🛠️ Development

pnpm install
pnpm lint         # builds first, then vp lint and vp fmt --check
pnpm lint:fix
pnpm typecheck    # tsc, the build config, a build, then the extensions
pnpm test:run
pnpm build        # obuild

💛 Thanks

Work on this library ran through two OSS programs, Claude for Open Source and Codex for Open Source. Thank you <3

📄 License

MIT