Skip to content

NodeTool Cloud is in alpha. Try it →

Open-source library · MIT

@nodetool-ai/browser

Drive the Chrome you are already signed in to.

A small TypeScript library for driving one Chrome page over the DevTools Protocol. Point it at a headless Chrome it launches, or at a tab in your everyday browser through a Chrome extension. In that tab your cookies, sessions and 2FA are already in place, so sites that block headless browsers still load.

$ npm install @nodetool-ai/browser
  • TypeScript
  • Node 22+
  • chrome-remote-interface
  • zod
import {
  browserNavigate,
  browserView,
  browserInput,
} from "@nodetool-ai/browser";

await browserNavigate({ url: "https://news.ycombinator.com" });

// Every interactive element comes back numbered,
// with its tag, role, text, attributes and bounding box.
const { elements, screenshot_png_b64 } = await browserView({
  include_screenshot: true,
});

const search = elements.find((el) => el.attributes.name === "q");
await browserInput({ index: search!.index, text: "quickjs", press_enter: true });

Two transports

One action loop, headless or in your own tab.

Only browserStatus and browserRestart know which transport is live. Element indexing, clicks, typing and screenshots run the same code against both, so a script written headless runs unchanged in your signed-in tab.

transport: "local"

  your Node process ──CDP over WebSocket──▶ headless Chrome it launched


transport: "extension"

  your Node process
        │  CDP commands and events, NDJSON
        │  unix socket, mode 0600 in a 0700 directory
        ▼
  native host            started by Chrome when the extension connects
        │  Chrome native messaging, length-prefixed JSON
        ▼
  extension service worker
        │  chrome.debugger.sendCommand / onEvent
        ▼
  the tab you attached   your cookies, sessions and 2FA
The extension relays CDP. It originates no commands, and no server sits in the path.
import { browserRestart, browserStatus } from "@nodetool-ai/browser";

// Same calls as above, now against the tab you attached in your own Chrome.
await browserRestart({ transport: "extension" });
console.log(await browserStatus());

Setting NODETOOL_BROWSER_TRANSPORT=extension before the process starts has the same effect.

Why an extension

Your real profile, without restarting Chrome.

No debug flags on your browser
You do not relaunch Chrome with --remote-debugging-port. Since Chrome 136 that flag is ignored for the default profile, so it cannot reach the profile you are signed in to anyway.
Attach is a click
The extension never attaches on page load. You press Attach to this tab in its popup, and Chrome shows its "is debugging this browser" banner until you detach or close the tab.
One host, several clients
The native host rewrites command ids per client and fans events out to all of them. The debugger stays attached until the last client disconnects.
Chromium browsers
installNativeHost() writes the host manifest for Chrome, Chromium, Brave, Edge and Arc on macOS and Linux.

API

Fourteen actions.

Each takes one plain object and returns one. Address elements by the index from the last browserView or by viewport coordinates. Lower-level access is exported as CdpPage, launchBrowser and withPage.

FunctionWhat it does
browserViewIndex interactive elements, optionally with a PNG screenshot.
browserNavigateGo to a URL and wait for load, DOMContentLoaded or network idle.
browserClickClick an element by index or by viewport coordinates.
browserInputType into an element, optionally pressing Enter.
browserPressKeyPress a key, such as Enter or Escape.
browserSelectOptionPick an option in a select element.
browserScrollScroll by pixels, or to the top or bottom.
browserMoveMouseMove the pointer to viewport coordinates.
browserConsoleExecEvaluate JavaScript in the page.
browserConsoleViewRead the page's console output.
browserCaptureMediaPull an image, video or audio file out of the page.
browserUploadAssetPut bytes into a file input.
browserStatusReport which transport is live.
browserRestartTear the session down, optionally switching transport.

Design

It knows nothing about the app that calls it.

Plain data in and out
Inputs are plain values. A screenshot comes back as base64, not a file on disk. Your code decides what to keep.
Zod schemas for every action
Each action's input and output is a Zod schema exported from the package. That is the shape an LLM tool definition needs.
Nothing loads on import
Chrome and the CDP client are dynamic imports. Importing the package starts no process until the first action runs.
Media capture with a fallback
browserCaptureMedia reads the response body over CDP first, then retries with an in-page fetch when the body is gone.

Questions

Limits and trade-offs.

Why not Playwright or Puppeteer?
Use them for test suites and scripted scraping. They are far more complete. This package covers a narrower case: an agent or a script acting inside the browser profile you already use, through one small action loop that works the same headless or attached. Element indexing returns numbered elements so a model can say click 14 instead of writing a selector.
Who can drive my browser once the host is installed?
Any process running as your OS user can connect to the host socket. The socket's file permissions are the only access control. If a client sends an attach frame while no tab is attached, the extension attaches the active tab. Detach when you are done, and do not install the host on a shared account.
Can two callers use it at once?
They share one page. The session is a process-wide singleton, so concurrent callers drive the same tab. Run separate processes if you need separate pages.
Does the extension come from the Chrome Web Store?
No. Build it from chrome-extension/ in the repository and load it unpacked. Its ID is pinned by the key in its manifest, so the host manifest matches it.
Windows?
The headless transport works. The extension transport does not yet: installing the native host on Windows needs a registry entry, and installNativeHost() throws there.
How is it tested?
npm test covers the wire protocol, the RPC client and transport resolution without a browser. npm run test:integration builds the extension, loads it in a real headless Chrome and drives the extension transport against local fixtures. It needs Chrome, so it runs by hand, not in CI.

This package came out of NodeTool, an open-source workspace for AI workflows. NodeTool's agent browser tools and its Screenshot node both call it. The library itself has no NodeTool runtime in it beyond a logger, and it is MIT licensed.