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

clsniff

v2.0.0

Published

Zero-configuration HTTP/HTTPS traffic sniffer for command-line tools like claude

Readme

clsniff

CI npm license node

A zero-configuration HTTP/HTTPS traffic sniffer for command-line tools.

Wrap any command with clsniff and every HTTP/HTTPS request it makes will be captured and saved to a JSON file — including full request/response headers and bodies (SSE responses are captured as an array of parsed events).

clsniff demo

Prerequisites

  • Node.js 22.12 or later
  • mitmproxy — the proxy engine, installed however you like (installation guide). clsniff looks for its mitmdump executable in PATH and in the usual installation directories of each platform; if yours lives elsewhere, point at it with --mitmdump <path>.

How it works

clsniff starts mitmdump(mitmproxy's headless mode) as a subprocess and launches your command as a child process with environment variables injected so it routes traffic through the proxy and trusts its CA certificate.

When a request passes through the proxy, clsniff:

  1. Captures the full request (method, URL, headers, body)
  2. Forwards it to the real server without modification
  3. Captures the full response (status, headers, body or SSE events)
  4. Forwards the response to the child process without modification
  5. Writes a JSON file with both sides of the exchange

The child process is completely unaware of the interception. Its stdin, stdout, and stderr are passed through directly.

Getting started

# Install globally
npm install -g clsniff
clsniff --viewer -- claude

# Or run without installing
npx -y clsniff@latest --viewer -- claude

Intercepted requests are saved to ~/.clsniff/ as JSON files, one per request/response pair.

Usage

clsniff [options] -- <command> [args...]

The -- separator is required to separate clsniff options from the wrapped command. Everything after it belongs to the command, its own flags included:

clsniff --viewer -- claude --dangerously-skip-permissions

Options

| Flag | Description | Default | |---|---|---| | --output-dir <path> | Directory where session log folders are created | ~/.clsniff | | --name <name> | Name for the session folder instead of the auto-generated timestamp | (timestamp) | | --port <number> | Port for the local proxy (0 = OS auto-assign) | 0 | | --mask-headers <names> | Comma-separated header names to redact in JSON output. Can be repeated. | (none) | | --exclude <hosts> | Comma-separated hosts to bypass interception entirely (NO_PROXY format). Bypassed hosts get a direct TCP tunnel — no MITM, no logging. Can be repeated. See Writing an --exclude entry. Example: example.com,.datadoghq.com,localhost:5000 | (none) | | --exclude-url <patterns> | Comma-separated URL substrings whose matching requests are intercepted and forwarded as usual but excluded from the JSON log. Unlike --exclude, this filters by full URL (host + path + query), so you can drop specific endpoints of a host while still capturing the rest. Can be repeated. Example: /api/claude_code/,/api/oauth/validate | (none) | | --configuration <path-or-url> | Path or http(s) URL of a JSON file providing defaults for outputDir, name, port, maskHeaders, exclude, excludeUrl and mitmdump. Explicit CLI options take precedence. See Configuration file. | (auto-discovered) | | --no-configuration | Do not load any configuration file, not even the auto-discovered ones. See Automatic discovery. | (off) | | --mitmdump <path> | Path to the mitmdump executable, or to the directory holding it. Only needed when it is not in PATH nor in a usual installation directory. | (auto-discovered) | | --install-cert | Install mitmproxy's CA certificate in the system trust store | (off) | | --viewer | Start the web-based log viewer | (off) | | --no-open | Do not auto-open the browser when starting the viewer | (off) |

--viewer includes a Claude tab with enhanced support for parsing and displaying Claude-specific request/response formats, according to https://platform.claude.com/docs/en/api/messages/create

Writing an --exclude entry

  • example.com — that host on any port. Subdomains are not included.
  • .example.com — the host and any subdomain, on any port.
  • localhost:5000 — that port only; the rest of the host stays captured.

Hosts match by name and by the address they resolve to, so --exclude 127.0.0.1:5000 also covers a request written as http://localhost:5000.

Configuration file

Instead of passing --output-dir, --name, --port, --mask-headers, --exclude, --exclude-url and --mitmdump on every invocation, you can store them in a JSON file and load it with --configuration <path-or-url>. This is especially handy for a long exclude/excludeUrl list.

{
  "outputDir": "/home/me/captures",
  "name": "my-session",
  "port": 8080,
  "maskHeaders": ["authorization", "x-api-key"],
  "exclude": ["example.com", ".datadoghq.com", "localhost:5000"],
  "excludeUrl": ["/api/claude_code/", "/api/oauth/validate"],
  "mitmdump": "~/.local/bin/mitmdump"
}

All keys are optional. Any explicit CLI option overrides the value from the file, which in turn overrides the built-in default. Unknown keys are ignored with a warning.

See clsniff.example.json for a ready-made example that keeps Claude Code's /v1/messages traffic while filtering out its telemetry and housekeeping endpoints.

Automatic discovery

When --configuration is omitted, clsniff loads both of these files, if present, and merges them key by key:

  1. ~/.clsniff/clsniff.json — for your personal defaults
  2. ./clsniff.json (current directory) — for per-project settings, overriding your personal defaults

Each key of the per-project file replaces the same key of the personal one; keys it does not define are inherited. Arrays such as exclude are replaced as a whole, not concatenated. For example, with maskHeaders and mitmdump in ~/.clsniff/clsniff.json and only exclude in ./clsniff.json, all three apply.

The paths being used are printed to stderr on startup. If neither file exists, clsniff runs with its built-in defaults as before. An explicit --configuration replaces automatic discovery: only that file is loaded.

Drop your favourite settings in ~/.clsniff/clsniff.json once and you never have to pass them again.

To skip automatic discovery for a single run and use only the built-in defaults plus your CLI options, pass --no-configuration.

Remote configuration

--configuration also accepts an http(s) URL, which is downloaded on every run. Useful for sharing a configuration across a team without committing it to each repo:

clsniff --viewer --configuration https://raw.githubusercontent.com/panicoenlaxbox/clsniff/main/clsniff.example.json -- claude

Examples

Intercept all traffic (everything after -- is passed straight to the command):

clsniff -- claude

Intercept traffic, open the viewer and redact the API key:

clsniff --viewer --mask-headers "authorization" -- claude

Local traffic is captured by default (e.g. an MCP server listening on http://localhost:5000). To skip all of it, or just one port:

clsniff --exclude localhost,127.0.0.1 -- claude
clsniff --exclude localhost:5000 -- claude

Browse existing sessions (standalone viewer, no command needed):

clsniff --viewer

Use the shared configuration straight from GitHub:

npx --yes clsniff@latest --viewer --configuration https://raw.githubusercontent.com/panicoenlaxbox/clsniff/main/clsniff.example.json -- claude

Install that configuration as your personal default (PowerShell Core), so you never have to pass it again:

New-Item -ItemType Directory -Force ~/.clsniff | Out-Null
Invoke-RestMethod https://raw.githubusercontent.com/panicoenlaxbox/clsniff/main/clsniff.example.json -OutFile ~/.clsniff/clsniff.json

From then on:

npx --yes clsniff@latest --viewer -- claude

Output format

Each clsniff invocation creates a new timestamped folder under ~/.clsniff/ (one folder per run):

~/.clsniff/
  2026-04-09T14-30-00-000Z/
    1744200600123_1.json
    1744200601456_2.json
    clsniff.log

clsniff.log contains timestamped internal messages (proxy startup, per-request entries, errors).

Use --name to replace the timestamp with a name of your own. Already captured sessions can also be renamed from the viewer. The session being recorded is the only one that cannot be renamed.

Each JSON file contains one request/response pair:

{
  "id": 20,
  "timestamp": "2026-04-09T14:53:19.651Z",
  "duration_ms": 1936,
  "request": {
    "method": "POST",
    "url": "https://api.anthropic.com/v1/messages?beta=true",
    "headers": {
      "authorization": "Bearer sk-ant-...",
      "content-type": "application/json",
      "anthropic-version": "2023-06-01"
    },
    "body": {
      "model": "claude-sonnet-4-6",
      "stream": true,
      "messages": [
        { "role": "user", "content": "Hello" }
      ]
    }
  },
  "response": {
    "status": 200,
    "headers": {
      "content-type": "text/event-stream; charset=utf-8",
      "request-id": "req_011CZpbpzFt2wEZFQmZAyayD"
    },
    "body": [
      { "event": "message_start", "data": { "type": "message_start", ... } },
      { "event": "content_block_delta", "data": { "type": "content_block_delta", ... } },
      { "event": "message_stop", "data": { "type": "message_stop" } }
    ]
  }
}

Certificate management

On first run, mitmproxy generates a CA (Certificate Authority) key pair and stores it in ~/.mitmproxy/:

~/.mitmproxy/
  mitmproxy-ca.pem          (CA key + certificate)
  mitmproxy-ca-cert.pem     (CA certificate, PEM — used by NODE_EXTRA_CA_CERTS)
  mitmproxy-ca-cert.cer     (CA certificate, DER — used by certutil on Windows)
  mitmproxy-ca-cert.p12     (CA certificate, PKCS12)
  mitmproxy-dhparam.pem     (DH parameters)

These files are generated once and reused across all sessions and tools.

The CA belongs to mitmproxy, not to a particular installation of it, so reinstalling or switching mitmdump keeps both the CA and the trust you already granted it.

Trusting the CA certificate

On first run (when ~/.mitmproxy/mitmproxy-ca-cert.pem does not yet exist), clsniff automatically tries to install the CA certificate in your system trust store. You can also trigger this manually at any time with --install-cert.

| Platform | Action | |---|---| | Windows | Runs certutil -addstore -user Root mitmproxy-ca-cert.cer. A Windows security dialog will appear asking for confirmation. | | macOS | Prints the sudo security add-trusted-cert ... command to run manually. | | Linux | Prints instructions to add the cert to your distro's CA bundle. |

Regardless of system-level trust, NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, and REQUESTS_CA_BUNDLE are always set in the child process environment to ensure Node.js, Python, and OpenSSL-based tools trust the proxy CA automatically.

Note: SSL_CERT_FILE replaces the system CA bundle for the child process. If you use --exclude to bypass certain hosts, those connections reach the real server but will fail TLS verification because the real CAs are no longer trusted in SSL_CERT_FILE. In that case, install the cert system-wide (via --install-cert) so the system CA bundle is used instead.

Note: mitmdump runs with --ssl-insecure, so certificates presented by the real upstream servers are not validated. A sniffer is not a security boundary, and this keeps local HTTPS endpoints with self-signed certificates (a development MCP server on https://localhost, for instance) working out of the box.

Environment variables

clsniff injects the following variables into the child process before launching it.

Proxy routing:

  • HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy — set to http://127.0.0.1:<port>
  • NO_PROXY, no_proxy — derived from --exclude, and empty by default. Whatever your own environment sets is replaced, so nothing can silently disable interception.

CA trust:

Development

git clone https://github.com/panicoenlaxbox/clsniff.git
cd clsniff
npm install

# Viewer only
npm run dev:hot

# Viewer + sniff a command
npm run dev:hot -- claude

# Viewer + sniff a command, with clsniff options
npm run dev:hot -- --configuration clsniff.example.json -- claude

# CLI only, no viewer
npm run dev -- -- claude

The dev:hot commands use Vite's hot module reload, so edits under viewer/src show up instantly. Changes to the backend (anything under src/, e.g. the API or the CLI) are not hot-reloaded — restart the command to pick them up.