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

stealth-cli

v0.9.0

Published

Anti-detection browser CLI powered by Camoufox. Browse, search, scrape, and crawl the web with C++ level fingerprint spoofing.

Readme


Why

Headless Chrome gets fingerprinted. Playwright gets blocked. Stealth plugins become the fingerprint.

stealth-cli uses Camoufox — a Firefox fork that patches fingerprint generation at the C++ implementation level. No JavaScript shims, no wrappers, no tells. The browser reports spoofed values natively.

Wrap that in a developer-friendly CLI with 17 commands, and you get a tool that both humans and AI agents can use.

How it compares

| Approach | Detection Risk | Why | |----------|:---:|------| | Puppeteer + stealth plugin | 🔴 High | JS-level patches are detectable; Chromium TLS fingerprint is a giveaway | | Playwright + custom args | 🟡 Medium | Better, but navigator.webdriver workarounds are fragile | | undetected-chromedriver | 🟡 Medium | Patches Chrome binary, but still Chromium-based fingerprint | | stealth-cli (Camoufox) | 🟢 Low | Firefox fork with C++ native spoofing; no JS shims to detect |

Install

npm install -g stealth-cli

The install fetches the Camoufox release this version is verified against (~300 MB, once; scripts/fetch-camoufox.mjs, which the postinstall runs). Subsequent runs are instant.

git clone https://github.com/Youhai020616/stealth-browser.git stealth-browser
cd stealth-browser
npm ci                       # Installs workspaces + downloads Camoufox
npm start -- --help            # Run from the repository root
cd packages/stealth-cli
npm link                      # Optional: make the stealth command global

Quick Start

stealth browse https://example.com                          # Visit a page
stealth screenshot https://example.com -o page.png          # Screenshot
stealth search google "best coffee beans" -f json           # Search Google
stealth extract https://example.com --links                 # Extract links
stealth crawl https://example.com -d 2 -l 50 -o out.jsonl  # Crawl
stealth profile create work --preset us-laptop              # Create a saved identity once
stealth open https://example.com --profile work              # Human login flow
stealth interactive --url https://example.com               # REPL mode

How Anti-Detection Works

stealth-cli (Node.js CLI)
  └── camoufox-js (npm binding)
       └── Camoufox (C++ Firefox fork)
            └── Fingerprint spoofing at the native level

| Fingerprint Vector | Approach | |---|---| | navigator.hardwareConcurrency | Spoofed in C++ | | navigator.webdriver | Always false | | WebGL renderer / vendor | Spoofed in C++ | | AudioContext fingerprint | Spoofed in C++ | | Canvas fingerprint | Spoofed in C++ | | Screen geometry | Spoofed in C++ | | WebRTC leak | Built-in protection | | TLS fingerprint | Firefox native (not Chromium) |

No JavaScript shims. No detectable wrappers. The browser natively reports spoofed values.

Commands

Core (12)

| Command | Description | |---------|-------------| | browse <url> | Visit URL, print content (text/json/snapshot/markdown) | | screenshot <url> | Screenshot (PNG/JPEG, full page, custom viewport; --annotate labels every interactive element with its ref) | | search <engine> <query> | Search 14 engines with anti-detection | | extract <url> | Extract links, images, meta, headings, CSS selectors | | crawl <url> | Recursive crawling with depth/filter/delay control | | open [url] | Headed browser for human authentication; exits when all windows close | | interactive | Stdin-driven REPL with 20+ commands (goto, click, type, eval...) | | pdf <url> | Save a rasterized screenshot-based PDF (Firefox/Camoufox) | | batch <file> | Batch process URLs from file | | monitor <url> | Watch for changes (price drops, stock alerts) | | fingerprint | Check fingerprint & anti-detection status | | serve | HTTP API server with Bearer token auth |

Management (5)

| Command | Description | |---------|-------------| | daemon start/stop/status | Background browser (~1s vs ~6s startup) | | profile create/list/delete | Persistent identities (8 presets + random) | | proxy add/list/test | Proxy pool with rotation & health checking | | config set/get/list/reset | Global defaults (~/.stealth/config.json) | | mcp | MCP server for Claude Desktop / Cursor |


Usage Examples

Browse

stealth browse https://example.com                       # Text output
stealth browse https://example.com -f json               # JSON with metadata
stealth browse https://example.com -f snapshot            # Accessibility tree
stealth browse https://example.com --humanize             # Human behavior simulation
stealth profile create work --preset us-desktop           # Create a saved identity once
stealth browse https://example.com --profile work         # Reuse the saved identity
stealth browse https://example.com --proxy http://proxy:8080

Search

Google uses a special anti-detection flow: visits homepage → types query with human-like timing → presses Enter.

stealth search google "web scraping tools" -f json       # Auto-humanized
stealth search google "query" -f json --warmup           # Visit random site first
stealth search duckduckgo "privacy browser" -f json
stealth search youtube "tutorial" -f json                # Video metadata
stealth search github "camoufox" -f json                 # Repo results

14 engines: google · bing · duckduckgo · youtube · github · amazon · reddit · wikipedia · twitter · linkedin · tiktok · stackoverflow · npmjs · yelp

Extract

stealth extract https://example.com --links              # All links
stealth extract https://example.com --images             # All images
stealth extract https://example.com --meta               # Title, description, OG
stealth extract https://example.com --headers            # h1-h6 headings
stealth extract https://example.com -s ".price" --all    # CSS selector
stealth extract https://example.com -s "a" -a "href" --all  # Attributes

Crawl

stealth crawl https://example.com -d 2 -l 50            # Depth 2, max 50 pages
stealth crawl https://example.com -o results.jsonl       # Save to file
stealth crawl https://example.com --include "blog"       # URL regex filter
stealth crawl https://example.com --delay 2000 --humanize
stealth crawl https://example.com --proxy-rotate         # Rotate per page

Monitor

stealth monitor https://shop.com/item -s ".price" -i 60  # Check every 60s
stealth monitor https://shop.com/item --contains "In Stock"
stealth monitor https://example.com --not-contains "Sold Out"

Headed Browser for Human Authentication

# Create the identity once, then open a browser that lives until its windows close
stealth profile create work --preset us-laptop
stealth open https://example.com/login --profile work

# Named sessions can be checkpointed at the same time
stealth open --url https://example.com/login --profile work --session login-flow

open is always headed and always uses a direct browser, even when the daemon is running. It ignores stdin, checkpoints cookies every second, performs a final live save when the last page closes, and gracefully handles SIGHUP, SIGINT, and SIGTERM on POSIX systems. If the browser process exits before a final live capture, the latest durable checkpoint is retained and a warning is printed. --checkpoint-interval accepts integer values from 250 through 60000 ms (default: 1000).

Interactive REPL

stealth interactive --url https://example.com
stealth interactive --no-headless --profile work --session debugging

stealth> goto https://google.com
stealth> click "button.submit"
stealth> hclick "a.link"              # Human-like click (bezier curve)
stealth> type "input[name=q]" hello
stealth> htype "input[name=q]" hello  # Human-like typing (variable speed)
stealth> scroll down 3
stealth> text / snapshot / links      # Inspect page
stealth> text markdown                # Page as Markdown (headings, lists, links, tables)
stealth> screenshot page.png
stealth> screenshot labelled.png annotate   # Ref boxes and labels drawn on the image
stealth> eval document.title
stealth> exit

Key Features

Daemon Mode

Keep a browser alive in the background for instant command execution.

stealth daemon start                   # Start background browser
stealth browse https://example.com     # ~1.2s (vs ~6s cold start)
stealth daemon stop                    # Shut down (auto-stops after 5min idle)

Browser Profiles

Persistent identities with unique fingerprints. Cookies auto-save between sessions.

stealth profile create work --preset us-desktop
stealth profile create rand1 --random

stealth browse https://example.com --profile work
# → Fingerprint: Windows, en-US, America/New_York

# Human-assisted login; cookies are checkpointed while the browser is open
stealth open https://example.com/login --profile work

8 presets: us-desktop · us-laptop · uk-desktop · de-desktop · jp-desktop · cn-desktop · mobile-ios · mobile-android

Session Persistence

# Link a named session to the existing work profile
stealth browse https://example.com --session my-task --profile work
# → Cookies + URL + history saved

# Session-only open restores the linked work profile and the saved URL
stealth open --session my-task

# An explicit initial URL skips the session's saved URL
stealth open https://other.com --session my-task

When --profile and --session are combined, the profile is the canonical cookie source and the session restores URL/history metadata. A session already linked to a different profile is rejected instead of merging two identities. When only --session is supplied, a linked profile is restored automatically. For open and interactive, an explicit initial URL takes precedence: stealth-cli skips the saved session URL before navigating to the requested URL.

Behavior/security change: Profile and session names may contain only ASCII letters, numbers, underscores, and hyphens ([A-Za-z0-9_-]+) and use lowercase canonical identities. Windows device basenames such as CON, NUL, COM1, and LPT1 are rejected on every platform. Existing mixed-case filenames are resolved case-insensitively. Older releases replaced unsupported characters with underscores in the filename while retaining the original name in JSON; for state previously requested as work.prod, reuse the sanitized basename work_prod. stealth profile list shows reusable profile basenames. There is no session-list CLI command, so inspect the filenames in $STEALTH_HOME/sessions (default ~/.stealth/sessions) when migrating an older session. Compatible legacy metadata is canonicalized and rewritten on the next successful state save; files are not renamed automatically. If a session links to a profile that no longer exists, startup fails before the browser launches instead of silently falling back to a different identity.

Named profile and session browser state is single-writer. Browser lifetimes and standalone create/save/delete operations all participate in the same lock protocol, so concurrent mutation fails instead of mixing identities. Lock files are never removed automatically after a crash: when the recorded owner is no longer running, stealth-cli prints the exact lock path and requires explicit removal after you confirm no process still owns that state. Profile and session JSON files contain authentication material; writes are atomic. On POSIX platforms, directories and files are enforced to owner-only modes (0700 and 0600). Windows does not provide equivalent POSIX mode-bit enforcement, so protect STEALTH_HOME with user-only Windows ACLs and do not place it in a shared directory.

By default, profiles, sessions, and their locks live under ~/.stealth. Set STEALTH_HOME to relocate those paths together. The configured state root and child paths must not be symlinks. Configuration (config.json), proxy-pool (proxies.json), and daemon socket/PID paths remain under ~/.stealth in the current source; config and proxy-pool files are stored with the same owner-only POSIX directory/file modes (0700/0600). Windows users must protect these paths with user-only ACLs. A hard browser crash can only preserve state captured by the most recent checkpoint; open defaults to a one-second interval.

Proxy Pool

stealth proxy add http://proxy1:8080 --label us --region US
stealth proxy add http://proxy2:8080 --label eu --region EU
stealth proxy test                                   # Health check
stealth browse https://example.com --proxy-rotate    # Auto-rotate

GeoIP: Camoufox auto-matches locale, timezone, and geolocation to proxy exit IP.

Humanize Mode

Simulate human behavior patterns to avoid behavioral detection:

stealth browse https://example.com --humanize
stealth search google "query" --humanize --warmup
  • Gaussian-distributed delays between actions
  • Bézier-curve mouse movements
  • Variable typing speed
  • Random scroll patterns

Global Configuration

Set defaults so you don't repeat flags:

stealth config set locale zh-CN
stealth config set humanize true
stealth config set retries 3
stealth config set format json
stealth config list

All core commands respect global config. CLI flags always override.

Pipe-Friendly

stdout = data, stderr = status. Compose with Unix tools:

stealth browse https://api.example.com -f json | jq '.title'
stealth search google "query" -f json | jq '.results[].url'
stealth extract https://example.com --links -f json | jq '.data[].url'

Integrations

HTTP API Server

Run stealth-cli as a service for programmatic access:

stealth serve --port 9377
# → Prints auto-generated API token on startup

curl localhost:9377/health                                          # No auth

curl -X POST localhost:9377/tabs \
  -H 'Authorization: Bearer <token>' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com"}'                               # Create tab

curl localhost:9377/tabs/tab-1/text -H 'Authorization: Bearer <token>'  # Get text

Options: --token <custom> · --no-auth (localhost only) · --host 0.0.0.0

Endpoints: /health · /tabs (POST/GET) · /tabs/:id/navigate · /tabs/:id/text · /tabs/:id/snapshot · /tabs/:id/screenshot · /tabs/:id/click · /tabs/:id/type · /tabs/:id/evaluate · /tabs/:id (DELETE) · /shutdown

MCP Server (Claude Desktop / Cursor)

Add stealth browsing capabilities to your AI coding assistant:

{
  "mcpServers": {
    "stealth": {
      "command": "stealth",
      "args": ["mcp"]
    }
  }
}

Legacy tools: stealth_browse (format: text, markdown, snapshot) · stealth_screenshot (annotate draws ref boxes and labels) · stealth_search · stealth_extract · stealth_evaluate · stealth_snapshot, plus one stealth_<action> tool per registered agent-facing action (stealth_observe, stealth_click, stealth_type, stealth_text, stealth_find, …)

Perception snapshot

stealth_snapshot and stealth interactive → a11y return the perception snapshot:

URL: https://shop.example/cart
Title: Your cart
Scroll: 0px above, 1840px below
Interactive elements: 3
Occluded: 2 — marked [occluded], no ref
New since last snapshot: 1 — marked [new]

- generic [active]:
  - button "Checkout" [occluded]
  - link "Continue shopping" [occluded]
  - dialog "Cookies":
    - text: We use cookies.
    - button "Accept" [ref=e7] [new]
  - iframe:
    - textbox "Card number" [ref=f1e3]
    - button "Pay" [ref=f1e4]
  • Interactive refs are checked at five points, including through every parent iframe. Covered elements are shown as [occluded] without a ref in the tree. Offscreen refs remain available for scrolling; unchecked refs remain when pageInfo.occlusionCheck is partial. Clicking repeats the same checks after scrolling and refuses covered targets (pass force: true to override). Frame geometry supports 2D transforms; 3D/perspective and motion-path frame transforms are refused by the guard.
  • A vertical scroll container keeps its ref and is marked [scrollable] (for example - generic "Items" [ref=e3] [scrollable]:), so scroll { ref, delta_y } moves that region instead of the page; scrollables[] names the same ref. Only main-frame containers that page info measures are matched, within a short bounded lookup that a page without one never runs.
  • Refs are stable within each document; [new] marks elements that appeared since your last snapshot (autocomplete suggestions, dialogs), including reused ref numbers after a child iframe navigates. A main-page navigation resets the baseline.
  • Open shadow DOM and iframes are included. Child-frame refs carry a frame prefix (f1e4) and work with stealth_click / stealth_type unchanged.
  • Nothing is written into the page: no marker attributes, no globals. Refs live in Playwright's isolated world.

stealth browse -f snapshot, the interactive snapshot command, and HTTP /snapshot keep the plain accessibility-tree format without refs or perception metadata. Use getA11ySnapshot() / buildA11yTree() in the SDK or HTTP /a11y-snapshot for perception.

SDK (Library Mode)

Use stealth-cli programmatically in your Node.js applications. The work profile must be created first, either with stealth profile create work --preset us-desktop or the SDK's createProfile('work', { preset: 'us-desktop' }) API.

import {
  launchBrowser,
  closeBrowser,
  checkpointBrowserState,
  navigate,
  getTextContent,
  getMarkdown,
  takeScreenshot,
} from 'stealth-cli';

const handle = await launchBrowser({ profile: 'work', humanize: true });
let operationError = null;
try {
  await navigate(handle, 'https://example.com');
  const text = await getTextContent(handle);
  console.log(text);
  const { text: markdown, truncated } = await getMarkdown(handle, { maxChars: 20000 }); // structure kept
  const { boxes } = await takeScreenshot(handle, { path: 'labelled.png', annotate: true }); // ref labels drawn on the image
  await checkpointBrowserState(handle); // Optional durable checkpoint while still open
} catch (error) {
  operationError = error;
}

let closeError = null;
try {
  await closeBrowser(handle, { strict: true });
} catch (error) {
  closeError = error;
}

if (operationError && closeError) {
  throw new AggregateError([operationError, closeError], 'Browser operation and cleanup both failed');
}
if (operationError) throw operationError;
if (closeError) throw closeError;

closeBrowser(handle) is best-effort by default: it attempts persistence and cleanup, then returns { persistence, persistenceError, cleanupErrors } instead of throwing for those failures. Inspect both error fields if you use the default mode. With { strict: true }, cleanup is still attempted, then an exported PersistenceError or BrowserCleanupError is thrown when work remains incomplete. Persistence is captured at most once; calling closeBrowser(handle) again retries only unfinished context, browser, or state-lock cleanup and never recaptures persistence. Fix the reported storage issue before repeating an authentication flow whose final persistence failed, and retry the same handle only for unfinished cleanup before reusing its named profile/session.

A failed launchBrowser() performs two bounded rollback attempts. If its error still reports incomplete browser cleanup, retain that original error and call retryBrowserLaunchCleanup(error, { strict: true }); the SDK keeps the rollback handle and state lease private and removes the retry capability only after cleanup completes. If profile/session lock acquisition or a standalone state operation reports incomplete state-lock cleanup, retain that exact error object and call retryStateLockCleanup(error); cloned or serialized errors intentionally do not carry the private recovery capability.

Use checkpointBrowserState(handle) for manual mid-run persistence on a handle launched with a named profile or session; it reuses the private browser-owned lease. The lower-level captureSession(name, context, page) API remains suitable for independently managed contexts, but it cannot acquire a session name already owned by a live launchBrowser() handle.

A successful close result or checkpointBrowserState(handle) result can contain cookie values and the raw last URL, and error objects retain non-enumerable diagnostic fields such as .cause or .url. Do not log, serialize, or return browser handles, checkpoint results, or raw close results across trust boundaries. Store STEALTH_HOME as sensitive authentication state. Error toJSON() output is redacted for routine logging, but the in-memory objects must still be treated as sensitive.

Atomic state writes hold a unique destination-scoped .claim from admission through publish or rollback sync. They fail closed when a prior process leaves a strict .claim, .tmp, or .rollback artifact. stealth-cli never auto-removes cross-process artifacts: verify that no live process owns the artifact, inspect it, and remove only the exact owner-only path reported by the command before retrying. On POSIX, sensitive path ancestors must be controlled by the current user or root and must not be non-sticky group/other-writable; user-controlled ancestor symlinks are rejected, while system-owned aliases are accepted only after their canonical target chain passes the same checks. This protocol serializes cooperative stealth-cli writers on a coherent local filesystem; owner-only files do not sandbox authentication state from hostile code already running as the same OS user.


Error Handling

stealth-cli provides structured errors with contextual hints:

✖ Failed to navigate to https://example.com
  Hint: Page load timed out. Try --wait <ms> or --retries <n>

✖ Google detected automation and blocked the request
  Hint: Try: --proxy <proxy>, --warmup, --humanize, or use a different engine

✖ Profile "work" not found
  Hint: Create with: stealth profile create work

Exit codes: 0 success · 1 general error · 2 invalid arguments · 3 browser launch · 4 navigation/blocked · 5 extraction · 6 timeout · 7 proxy · 8 profile/session/persistence · 129 SIGHUP · 130 SIGINT · 143 SIGTERM

Common Options

Option availability varies by command; run stealth <command> --help for the exact set.

| Option | Description | |--------|-------------| | --proxy <url> | Use proxy server | | --proxy-rotate | Rotate through proxy pool | | --profile <name> | Use saved browser identity | | --session <name> | Persist/restore browsing session | | --cookies <file> | Import Netscape-format cookie file | | --humanize | Simulate human behavior | | --retries <n> | Max retries on failure | | --no-headless | Show browser window on commands that default to headless | | --checkpoint-interval <ms> | open authentication-state checkpoint interval (250–60000; default: 1000) | | -f, --format | Output format: text, json, jsonl, snapshot, markdown |

Project Stats

Version:     0.9.0
Commands:    17
Tests:       742 passing (57 test files)
Source:      17,519 lines (78 JavaScript files under `src/`)
Extractors:  6 (Google, Bing, DuckDuckGo, YouTube, GitHub, generic)
Presets:     8 browser profiles
Engine:      Camoufox (C++ Firefox fork)
License:     MIT

Contributing

Start with SPEC.md — what the product is and what it declines — and AGENTS.md — the hard rules, the documentation map, and the gate before a push.

Contributions welcome! Some areas where help is appreciated:

  • New extractors — Add structured parsing for more search engines/sites
  • Profile presets — More realistic browser fingerprint configurations
  • Bug reports — Especially sites that still detect stealth-cli
  • Documentation — Usage guides, tutorials, examples

Please open an issue to discuss larger changes before submitting a PR.

Star History

🔗 Ecosystem

| Project | Description | |---------|-------------| | AgentMind | Self-learning memory system for AI agents | | stealth-x | Anti-detection X/Twitter automation (built on stealth-cli) | | dy-cli | Douyin/TikTok CLI | | xiaohongshu | Xiaohongshu automation | | freepost | AI social media management |

Acknowledgments

  • Camoufox — The Firefox fork that makes this possible
  • Playwright — Browser automation framework

License

MIT

Native video through the SDK

Install the video encoder with npx playwright-core install ffmpeg. Pass recordVideo: { dir: '/private/output', size: { width: 1280, height: 720 } } to launchBrowser(). This opts into a direct context even if a daemon exists; no action annotations are injected into pages. Keep const video = handle.page.video(), perform browser work, await closeBrowser(handle, { strict: true }), then read await video.path(). Output is WebM. Each context page has its own video; callers own storage permissions, file retention and cleanup of the output directory. Recording is off by default; the CLI/daemon do not expose a recording flag.

A synthetic click that triggers a matching attachment reports navigationStatus: "downloaded". This confirms the download started, not that its bytes were saved; use Playwright's download completion API separately.