npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@olib-ai/owl-browser-sdk

v2.1.6

Published

Node.js SDK for Owl Browser automation - Async-first with dynamic OpenAPI method generation

Readme

@olib-ai/owl-browser-sdk

Node.js / TypeScript client for the Owl Browser HTTP server: 185 browser tools, one class, ESM and async throughout.

Install

npm install @olib-ai/owl-browser-sdk

Node 18 or newer. The package is ESM only ("type": "module").

Quick start

import { OwlBrowser } from '@olib-ai/owl-browser-sdk';

const browser = new OwlBrowser({
  url: 'http://127.0.0.1:8080',
  token: 'your-secret-token',
  apiPrefix: '',            // '' when talking to the server directly, '/api' behind nginx
});

await browser.connect();

const contextId = await browser.createContext();
await browser.navigate({ context_id: contextId, url: 'https://example.com' });

const markdown = await browser.getMarkdown({ context_id: contextId });
console.log(markdown);

await browser.closeContext({ context_id: contextId });
await browser.close();

createContext() returns the context ID as a string. Every other tool takes it as the context_id parameter.

Configuration

The OwlBrowser constructor takes one RemoteConfig object.

| Field | Type | Default | Notes | |---|---|---|---| | url | string | required | Server base URL. Trailing slashes are stripped. | | token | string | required for token auth | Bearer token. The transport throws if it is missing and JWT auth is not configured. | | authMode | AuthMode | AuthMode.TOKEN | AuthMode.JWT switches to RS256 JWT auth. jwt must also be set, otherwise the client falls back to token auth. | | jwt | JWTConfig | none | See below. | | transport | TransportMode | TransportMode.HTTP | TransportMode.WEBSOCKET for the WebSocket transport. | | timeout | number | 30 | Request timeout in seconds. Long running tools get max(120s, timeout * 4) on the HTTP transport. | | maxConcurrent | number | 10 | In-process cap on concurrent HTTP requests. Extra calls queue. | | retry | RetryConfig | see below | HTTP transport only. | | verifySsl | boolean | true | Accepted for parity with the Python SDK. The Node transports do not currently act on it. | | apiPrefix | string | '/api' | Prefixed to every path. Pass '' for a direct connection to the browser server. |

RetryConfig defaults: maxRetries: 3 (total attempts, not extra attempts), initialDelayMs: 100, maxDelayMs: 10000, backoffMultiplier: 2.0, jitterFactor: 0.1. Retries apply to connection level failures only. Timeouts, auth failures, rate limits and IP blocks are raised immediately.

JWT auth:

import { OwlBrowser, AuthMode } from '@olib-ai/owl-browser-sdk';

const browser = new OwlBrowser({
  url: 'http://127.0.0.1:8080',
  authMode: AuthMode.JWT,
  jwt: {
    privateKeyPath: '/path/to/private.pem',  // path or an inline PEM string
    expiresIn: 3600,                          // seconds, default 3600
    refreshThreshold: 300,                    // re-sign this many seconds before expiry
    issuer: 'my-app',
    subject: 'automation',
    audience: 'owl-browser',
    claims: { team: 'growth' },
  },
});

Calling tools

execute(toolName, params, options?) calls any tool by its full name and returns the unwrapped result field of the server response.

const result = await browser.execute('browser_navigate', {
  context_id: contextId,
  url: 'https://example.com',
  wait_until: 'load',
});

options.signal accepts an AbortSignal to cancel an in-flight request.

Every tool in the bundled OpenAPI schema also gets a generated method, under both its camelCase and its snake_case name, with the browser_ prefix removed. These are exactly equivalent to execute():

await browser.getMarkdown({ context_id: contextId });
await browser.get_markdown({ context_id: contextId });
await browser.execute('browser_get_markdown', { context_id: contextId });

Parameters are passed through as the server names them (context_id, not contextId). Fields typed as integer in the schema are floored before sending.

Introspection:

browser.listTools();            // 185 full tool names: 'browser_create_context', ...
browser.listMethods();          // 353 generated method names, camelCase and snake_case
browser.hasMethod('getMarkdown');  // true

const def = browser.getTool('browser_navigate');
def.name;             // 'browser_navigate'
def.description;
def.requiredParams;   // ['context_id', 'url']
def.parameters;       // { context_id: {name, type, required, description, enumValues, default}, ... }

listTools() and getTool() take full tool names (browser_navigate). listMethods() and hasMethod() take generated method names (navigate). Both read the schema bundled with the package, so they work before connect() and without a server.

A handful of methods are hand written for better types and behaviour and take precedence over the generated ones: createContext, create_context, closeContext, closeAllContexts, navigate, click, type, screenshot, waitForSelector, getHtml, getMarkdown, evaluate.

healthCheck() returns the server's health JSON. It is HTTP only.

Long running work

An agentic run (browser_nla) routinely takes minutes. Run synchronously it exceeds the server's default 30 second request timeout, the connection is dropped, and the result is lost even though the browser finished the work. The SDK submits it as a background job and polls instead.

runTask

This is the one to use. One call submits the job, polls it, and returns the unwrapped answer.

import { OwlBrowser } from '@olib-ai/owl-browser-sdk';

const browser = new OwlBrowser({ url: 'http://127.0.0.1:8080', token: 't', apiPrefix: '' });
await browser.connect();

// browser_nla needs an LLM on the context: the built-in model, or your own.
const contextId = await browser.createContext({
  render_mode: 'agent',
  llm_is_third_party: true,
  llm_use_builtin: false,
  llm_endpoint: 'http://127.0.0.1:1234',
  llm_model: 'qwen/qwen3.6-27b',
  llm_api_key: process.env.LLM_API_KEY,
});

await browser.navigate({
  context_id: contextId,
  url: 'https://books.toscrape.com/catalogue/category/books/travel_2/index.html',
});

const answer = await browser.runTask({
  contextId,
  command: "Open the product page for 'The Great Railway Bazaar' and report how "
         + 'many copies are in stock. Answer with just the number.',
});

console.log(answer);   // the tool's own result, envelopes already stripped

await browser.closeContext({ context_id: contextId });
await browser.close();

runTask({ contextId, command, timeoutMs?, pollIntervalMs?, ...rest })

  • contextId and command map to the context_id and command parameters of browser_nla.
  • timeoutMs defaults to 600000 (10 minutes). It bounds the client's polling, not the server. On expiry runTask rejects and the job keeps running server side.
  • pollIntervalMs defaults to 2000.
  • Any other key is passed straight through to the tool.
  • Rejects with OwlBrowserError if the job fails or is cancelled.

The job API

Use these when you need the job ID itself, for example to report progress or to cancel from elsewhere.

const jobId = await browser.submitJob('browser_nla', {
  context_id: contextId,
  command: 'How many books are listed on this page? Answer with just the number.',
});

const job = await browser.getJob(jobId);
// { job_id, state, tool, created_ms, updated_ms, result?, error? }

const answer = await browser.waitForJob(jobId, { timeoutMs: 600000, pollIntervalMs: 2000 });
  • submitJob(toolName, params) returns a job ID string. It works for any tool, not only browser_nla.
  • getJob(jobId) returns the raw job record. Rejects with OwlBrowserError if the ID is unknown.
  • listJobs() returns every live job on the server as an array, without result payloads.
  • cancelJob(jobId) requests cancellation and returns the job record.
  • waitForJob(jobId, { timeoutMs, pollIntervalMs }) polls until the job reaches a terminal state and returns the unwrapped result. Same defaults as runTask.

waitForJob and runTask peel the transport envelopes off the stored result, so you get the tool's answer rather than {success, result} wrapping a JSON string.

Job states

| State | Meaning | |---|---| | queued | Accepted, not started. | | running | Executing. | | done | Finished, result is populated. | | failed | Finished, error is populated. | | cancelled | Stopped before completion. | | cancelling | Cancel requested on a job that is already inside a browser call. |

A cancel cannot interrupt an in-flight browser call. Cancelling a queued job moves it straight to cancelled. Cancelling a running job reports cancelling until that call returns, then cancelled. Keep polling through cancelling, it is not terminal.

Finished jobs are kept for 30 minutes, then reclaimed.

Transport requirement

The job endpoints are plain REST and exist only on the HTTP transport. A client constructed with transport: TransportMode.WEBSOCKET rejects with OwlBrowserError from getJob, listJobs, cancelJob, waitForJob and runTask.

Errors

All errors extend OwlBrowserError, which extends Error.

| Error | Raised when | |---|---| | OwlBrowserError | Base class. Also raised directly for job failures, waitForJob client timeout, unknown job IDs, job calls on a WebSocket client, and a browser_create_context response with no usable ID. | | ConnectionError | HTTP retries exhausted, a non-tool request failed at the network level, or a WebSocket connection failed or closed with requests pending. Carries cause. | | AuthenticationError | HTTP 401, or a WebSocket handshake error mentioning 401. Carries reason and statusCode = 401. | | ToolExecutionError | HTTP 4xx/5xx other than 401/403/429, a JSON body with success: false, or a WebSocket error frame. Carries toolName, status, result. | | TimeoutError | The per-request transport timeout elapsed. Carries timeoutMs. | | RateLimitError | HTTP 429. Carries retryAfter, limit, remaining, statusCode = 429. | | IPBlockedError | HTTP 403. Carries ipAddress and statusCode = 403. | | OpenAPISchemaError | The bundled schema is missing or unparseable. Carries cause. | | RequestAbortedError | An options.signal you supplied was aborted. Not re-exported from the package root and not reachable through the package's exports map, so match on e.name === 'RequestAbortedError' or catch it as OwlBrowserError. |

import {
  OwlBrowserError,
  ToolExecutionError,
  TimeoutError,
  RateLimitError,
  AuthenticationError,
} from '@olib-ai/owl-browser-sdk';

try {
  await browser.click({ context_id: contextId, selector: '#nonexistent' });
} catch (e) {
  if (e instanceof ToolExecutionError) {
    console.log(e.toolName, e.status, e.message);
  } else if (e instanceof TimeoutError) {
    console.log('timed out after', e.timeoutMs, 'ms');
  } else if (e instanceof RateLimitError) {
    console.log('retry after', e.retryAfter, 'seconds');
  } else if (e instanceof AuthenticationError) {
    console.log(e.message);
  }
}

A failing tool surfaces as ToolExecutionError. ElementNotFoundError, NavigationError, ContextLimitError, FlowExecutionError and ExpectationError are exported but the transports never raise them. To get the first two, run a result through the exported helper yourself:

import { raiseForActionResult, ElementNotFoundError } from '@olib-ai/owl-browser-sdk';

const result = await browser.click({ context_id: contextId, selector: '#maybe' });
try {
  raiseForActionResult(result);   // no-op unless result is a failed ActionResult
} catch (e) {
  if (e instanceof ElementNotFoundError) console.log(e.selector);
}

Transports

HTTP is the default and the fully featured one. WebSocket keeps a single connection open and multiplexes requests over it by numeric ID.

import { OwlBrowser, TransportMode } from '@olib-ai/owl-browser-sdk';

const browser = new OwlBrowser({
  url: 'http://127.0.0.1:8080',
  token: 'your-secret-token',
  apiPrefix: '',
  transport: TransportMode.WEBSOCKET,   // connects to ws://127.0.0.1:8080/ws
});

await browser.connect();

| | HTTP | WebSocket | |---|---|---| | Endpoint | POST {url}{apiPrefix}/execute/{tool} | ws(s)://{host}{apiPrefix}/ws | | Connection | Per request, connect() only marks the client ready | One persistent socket, opened by connect() | | Timeout | timeout, raised to max(120s, timeout * 4) for ~30 known long running tools | timeout for every call | | Retry | Exponential backoff with jitter | None | | Concurrency cap | maxConcurrent | Unbounded, limited by the server | | healthCheck() | Supported | Throws OwlBrowserError | | Async jobs | Supported | Throws OwlBrowserError | | Error detail | 401 / 403 / 429 mapped to distinct classes | All server errors arrive as ToolExecutionError with toolName: 'unknown' |

Auth differs too: HTTP sends the bearer token on every request, so a JWT is re-signed as it approaches expiry. WebSocket sends it once in the handshake, so a long lived socket outlives its token unless you reconnect.

Pick WebSocket for many short calls where per-request overhead matters. Pick HTTP for everything else, and always for long running work.

Also in the package

Three optional layers ship alongside the tool client. Each is independent, and none of them is needed for the sections above.

| Import | What it is | |---|---| | import { FlowExecutor } from '@olib-ai/owl-browser-sdk' | Runs declarative JSON flows: steps, captured variables, for_each, retries, expectations. See docs/FLOW_EXECUTOR.md. | | import { Extractor } from '@olib-ai/owl-browser-sdk' | Field specs to pull structured records out of a page. | | import { chromium } from '@olib-ai/owl-browser-sdk/playwright' | A Playwright-shaped facade (Browser, BrowserContext, Page) over the same client, for porting existing scripts. |