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

@husk-ai/browser

v0.1.4

Published

A real Chromium inside a Husk computer, driven over CDP.

Downloads

305

Readme

@husk-ai/browser

A real Chromium, running inside the agent's computer, driven over the Chrome DevTools Protocol.

browseInComputer in @husk-ai/core fetches HTML with urllib and strips the tags. That is the right zero-dependency floor and it is not going anywhere — but it cannot see a page that only exists after JavaScript runs, it cannot log in, and it cannot click. This can.

The browser lives in the same machine as the shell and the filesystem, for the same reason the fetch does: a browser on the host would have a different IP, a different DNS view, and an egress path the computer's network policy does not govern.

The driver lives there too. Chromium's --remote-debugging-port binds loopback, and the answer to that is not to widen the bind address — an unauthenticated CDP port is a remote-code-execution primitive. So husk does not connect to the browser from the host, and does not publish the port: it runs a small Python CDP client in the computer and reads one JSON object back off stdout. Nothing here calls exposePort, and --remote-debugging-address does not appear anywhere in this package.

A round trip is one process start plus one websocket handshake — about 165 ms on the local provider on WSL2, of which roughly 145 ms is that provider's own cost of starting any process at all. Each Page method is one driver invocation wherever the CDP sequence allows it, which is the lever if that ever stops being fast enough.

Example

import { LocalProvider } from '@husk-ai/runtime';
import { browserFor } from '@husk-ai/browser';

const computer = await new LocalProvider().create({
  name: 'browsing',
  network: { mode: 'full' },
});

const browser = browserFor(computer);

// Provisions Chromium on first use: system binary -> cached download -> CDN.
await browser.goto('https://example.com');

const page = await browser.activePage();
for (const node of await page.snapshot()) {
  console.log(node.ref, node.role, node.name);
}

await page.click('e42');          // refs come from the snapshot, never pixels
await page.type('e17', 'husk');
await page.press('Enter');
console.log(await page.text());

await browser.close();

What it does

  • provision.ts — finds a system chromium / google-chrome, else a copy already downloaded into the computer, else downloads one for the detected architecture. On x86-64 that is Chrome for Testing's chrome-headless-shell-linux64.zip, which unpacks to chrome-headless-shell-linux64/chrome-headless-shell. On arm64 Chrome for Testing publishes nothing at all, so it is Playwright's CDN, which unpacks to chrome-linux/headless_shell — a different directory and a different binary name. That mapping is the thing most likely to rot silently, so it is data with a unit test rather than an if in the install path.
  • driver.py — the CDP client, executed inside the computer. One process per command: connect to Chromium on the machine's own loopback, run a short list of steps, print one JSON object, exit. It needs nothing but python3, which every flavour has.
  • cdp.ts — the transport. Runs driver.py through Computer.exec and turns its JSON into results or a HuskError, keeping apart the failures that need different fixes: the browser is not listening, it died holding your command, the page never answered, Chromium refused the command, the driver could not run at all.
  • page.ts — goto, snapshot, click, type, press, screenshot, evaluate, content, text, waitForLoad.
  • session.ts — one browser per computer, launched lazily and closed after five idle minutes, because a resident Chromium is a few hundred megabytes of RSS and a machine with four of them gets OOM-killed. The user-data-dir is persisted, so a login survives the relaunch.

Refs, not coordinates

snapshot() flattens Accessibility.getFullAXTree into { ref, role, name, value }. A ref of e42 is Chromium's backend node id, so it identifies the same element across snapshots for as long as the element is in the document. There is deliberately no click(x, y): a model that infers a pixel from a screenshot will eventually click the wrong thing, and the wrong thing is sometimes "Delete account".

Nodes with no DOM element get an a-prefixed ref, and click refuses those explicitly rather than resolving them to something nearby.

Network policy

Every navigation goes through assertUrlAllowed from @husk-ai/core, with the same loopbackIsOwn rule browseInComputer uses: docker, podman and fly own their loopback, local and ssh do not. mode: 'full' still refuses 169.254.169.254. A real browser — which will follow a redirect chain a prompt-injected page hands it — makes this matter more, not less.

Not sandboxed by itself

Chromium runs with --no-sandbox. The computer is the sandbox; Chromium's own namespace sandbox cannot nest inside an unprivileged container, and on the local provider there is no kernel boundary to begin with. Run untrusted pages in a docker or podman computer.

One caveat measured rather than assumed: on local under WSL2, Windows' loopback relay forwards a WSL 127.0.0.1 listener to the Windows host, so the debug port is reachable from the host there even though husk never publishes it. That is WSL's networking, not husk's — and it is one more reason the local provider reports isolationKind: 'guardrails' rather than claiming isolation. On docker, podman and fly, container loopback is not reachable from the host, and nothing in this package needs it to be.