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

chromiumfish

v0.2.8

Published

Stealth Chromium build with a drop-in Playwright harness — fetches and launches the ChromiumFish browser.

Readme

chromiumfish (Node)

Stealth Chromium with a drop-in Playwright harness.

npm install chromiumfish playwright-core
npx chromiumfish fetch        # download + cache the browser build

Usage

import { ChromiumFish } from "chromiumfish";

const browser = await ChromiumFish({ personaSeed: "alpha-7", headless: true });
const page = await browser.newPage();
await page.goto("https://example.com");
await page.screenshot({ path: "fp.png" });
await browser.close();

ChromiumFish() returns a standard Playwright Browser, so newContext, newPage, routing, tracing, etc. all work as usual.

Options

| Option | Default | Description | |--------|---------|-------------| | personaSeed | — | String id for a stable, internally-consistent fingerprint persona (any string; a number works too). | | headless | true | Run headless (SwiftShader). | | proxy | — | Playwright proxy object, e.g. { server, username, password }. | | windowSize | [1920, 1080] | Window dimensions (null to omit). | | version | pinned | Override the browser build version. | | download | true | Auto-download the build if missing. | | timezone | — | "auto" resolves the egress IP's IANA timezone via the downloadable ip2tz DB and sets the browser's TZ; an IANA string (e.g. "Europe/Berlin") is used verbatim. | | args | — | Extra Chromium flags. | | ...rest | — | Forwarded to chromium.launch(). |

IP-to-Timezone

timezone: "auto" aligns the browser clock with the egress IP (handy behind a proxy). It uses a compact ip2tz database downloaded once and cached; you can also query it directly:

import { lookupTimezone, resolveTimezone } from "chromiumfish";

await lookupTimezone("8.8.8.8");   // -> "America/Los_Angeles"
await resolveTimezone();           // own egress IP -> timezone

The DB auto-updates: it tracks the latest monthly build (cached, re-checked weekly), so you get fresh data without upgrading the SDK. Pin a fixed version with CHROMIUMFISH_GEOIP_VERSION=2026.06 for reproducibility.

Geolocation (GPS)

Pin a static GPS position with two flags through args. ChromiumFish auto-grants the Geolocation permission (no prompt) and reports those exact coordinates to every frame, never querying the real location providers:

const browser = await ChromiumFish({
  personaSeed: "alpha-7",
  args: ["--persona-lat=48.8584", "--persona-lng=2.2945"], // optional: --persona-accuracy=<m> (default 40)
});

Keep the location aligned with the exit IP and timezone. More: chromiumfish.com/personas#geolocation-gps.

Mobile / OS persona

By default the persona is Windows desktop Chrome. --persona-os (through args) switches the whole OS family — win (default), mac, or android, a seed-driven Pixel-family phone (mobile UA/hints, touch, phone screen, Mali WebGL):

const browser = await ChromiumFish({ personaSeed: "alpha-7", args: ["--persona-os=android"] });
// navigator.userAgentData.mobile === true

The Android persona is coherent but not airtight — fonts, exact-match canvas, and locale/timezone still carry desktop tells, so pair it with a mobile-region proxy + timezone and don't rely on it against the strictest detectors. More: chromiumfish.com/personas#mobile-and-os-persona.

AI agent

ChromiumFish ships a native in-browser agent (perceive → think → act, driven by an OpenAI-compatible LLM). launchAgent starts the browser with the agent layer and connects over CDP; runTask drives it from a plain-language goal.

import { withAgent } from "chromiumfish";

// LLM config: a nearby .env (OPENAI_API_*), or pass apiKey/apiBase/model here.
const result = await withAgent({ typing: "human" }, (agent) =>
  agent.runTask("Search DuckDuckGo for 'chromiumfish' and give me the first result's URL."),
);
console.log(result.finalText);

withAgent shuts the browser down for you; use launchAgent directly if you want to manage the lifecycle (const { agent, close } = await launchAgent()).

| Option | Default | Description | |--------|---------|-------------| | typing | "human" | Typing speed: "human" (~75 WPM, natural), "fast", "instant", or a custom [keyDown, keyUp, longMultiplier] triple (numbers = ms). | | model | env | Model for the session (overrides OPENAI_API_MODEL); runTask({ model }) overrides per task. | | apiKey / apiBase | env | LLM key / base URL (override OPENAI_API_KEY / OPENAI_API_BASE). | | chrome | CHROME_BIN / cached build | Path to the ChromiumFish binary. | | port | 9222 | DevTools remote-debugging port. | | extraArgs | — | Extra Chromium flags. |

Needs a WebSocket: the Node 22+ global WebSocket is used automatically; on Node <22 install the optional ws package (npm install ws).

External agents

Prefer a third-party framework (Hermes, OpenClaw, browser-use, Playwright, …)? chromiumfish serve exposes a plain CDP endpoint — with your persona/proxy/timezone active — for any of them to attach to:

npx chromiumfish serve --persona-seed alice        # -> http://127.0.0.1:9222
# e.g. Hermes ~/.hermes/config.yaml: browser: { cdp_url: "http://127.0.0.1:9222" }

Or run it as an MCP server for Claude Code/Desktop, Cursor, etc.:

npx chromiumfish mcp --persona-seed alice          # exposes browser tools over MCP (stdio)

Full guide: chromiumfish.com/agents.

CLI

npx chromiumfish fetch [--browser-version X] [--force]   # download + cache
npx chromiumfish path                                     # print binary path
npx chromiumfish serve [--port 9222] [--persona-seed S]  # CDP endpoint for external agents
                       [--proxy URL] [--window-size WxH] [--timezone Z] [--headless]
                       [--browser-version X] [--extra-args ARGS] [--timeout S]
npx chromiumfish mcp   [--persona-seed S] [--headed]      # MCP server (Claude, Cursor, ...)
                       [--proxy URL] [--window-size WxH] [--port N] [--typing T] [--llm-key K]
npx chromiumfish clear                                    # wipe the cache
npx chromiumfish --version

Builds are cached under ~/.cache/chromiumfish/<version>/ (override with CHROMIUMFISH_CACHE_DIR). Pin a build with CHROMIUMFISH_VERSION.

Attribution

IP Geolocation by DB-IP — the ip2tz timezone database is derived from DB-IP City Lite, used under CC BY 4.0.

License

MIT © Arman Hossain. See the repository.