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

popterminal

v0.3.2

Published

Real public email for agents. CLI + MCP server that streams OTPs, magic links, and signups from a real @popterminal.com address into a SQLite-backed local store. Zero-config anonymous mode on popterminal.com or bring your own domain.

Readme

popterminal

Local capture endpoint for agents and dev workflows — receive real public mail (OTPs, magic links, signups) into a SQLite-backed CLI on your laptop. Zero-config out of the box; bring your own domain if you want.

$ popterminal start
daemon started (pid 8034)

$ popterminal handle show
handle:  dugong-3xy9
address: [email protected]

$ popterminal wait code --subject "verification" --timeout 5m --json
{
  "from": "[email protected]",
  "to": "[email protected]",
  "subject": "Your verification code is 482913",
  "text": "Your code: 482913\n",
  "tag": "code",
  "ingress": "anonymous"
}

What it does

  • popterminal start on a fresh install registers an anonymous handle on popterminal.com and opens a WebSocket to the service. You get an address like [email protected] instantly. No DNS, no Cloudflare account, no tunnel binary.
  • Mail to <handle>+<tag>@popterminal.com lands in inbox <tag> (sub-inboxes via plus-addressing). Mail to <handle>@popterminal.com (no tag) lands in default.
  • popterminal wait <inbox> blocks until a matching email arrives, prints JSON, exits — the OTP-retrieval pattern for shell-driven agents.
  • A local SMTP server on :2525 also captures mail from local apps under test (no internet needed for that path).
  • BYO-domain mode is supported: popterminal init --domain mydomain.com configures Cloudflare Email Routing on your own zone with a Cloudflare tunnel, no popterminal.com involved.

Install

npm install -g popterminal

Requires Node.js 20+.

Quickstart (anonymous mode)

popterminal start                    # registers a handle, connects WebSocket
popterminal handle show              # prints your <handle>@popterminal.com
popterminal wait <tag> --timeout 5m  # blocks until mail arrives at <handle>+<tag>@popterminal.com

That's it. No prerequisites, no DNS, no Cloudflare account.

Your handle is persistent across restarts (token stored in ~/.popterminal/anonymous-token). It expires only after 30 days of inactivity, or when you run popterminal handle reset.

Quickstart (BYO domain — for users who want full control)

If you'd rather receive mail on a domain you own, with the data never passing through popterminal.com infra:

Prerequisites:

  • A domain on Cloudflare (the catch-all rule sends all mail at the domain through popterminal — don't use a domain you receive important mail at).
  • The cloudflared binary (brew install cloudflared on macOS, or your package manager of choice).
  • A Cloudflare API token with: Zone:Zone:Read, Zone:Email Routing Rules:Edit, Account:Workers Scripts:Edit.

Setup:

# 1. Authenticate cloudflared with your zone
cloudflared tunnel login

# 2. Enable Email Routing in the Cloudflare dashboard (one-time, manual)
# Dashboard → your zone → Email → Email Routing → Get started

# 3. Run the wizard
export CLOUDFLARE_API_TOKEN='your-token'
popterminal init --domain yourdomain.com --non-interactive

# 4. Start the daemon
popterminal start

The wizard creates a tunnel, deploys a Worker on your CF account, configures Email Routing, and saves config. Re-running is idempotent.

Command reference

| Command | Purpose | |---|---| | popterminal start | Start daemon (first run registers anonymously; subsequent runs use saved config) | | popterminal stop | Stop daemon | | popterminal status | Daemon health | | popterminal handle show | Print current anonymous handle and address | | popterminal handle reset | Forget current handle; next start registers a fresh one | | popterminal config show | Print effective config | | popterminal init [--token T] [--domain D] [--non-interactive] | One-time BYO Cloudflare setup | | popterminal inbox create [name] | Create inbox; prints address | | popterminal inbox list | List inboxes | | popterminal inbox delete <name> | Delete inbox + all its emails | | popterminal emails <inbox> [--from] [--subject] [--limit] [--since] [--json] | List emails | | popterminal show <inbox> <id> [--raw\|--text\|--html\|--json] | Show one email | | popterminal wait <inbox> [--from] [--subject] [--timeout 60s] [--json] | Block until match | | popterminal tail <inbox> [--from] [--subject] [--json] | Stream new mail until killed | | popterminal open <inbox> <id> | Render email HTML in default browser | | popterminal delete-email <inbox> <id> | Delete one email | | popterminal web [--port 9090] [--no-open] | Start a local web UI at http://127.0.0.1:9090 to browse the inbox |

--json works on every read command. --from and --subject are case-insensitive substring matches. Short alias: pop works as well as popterminal.

Exit codes

| Code | Meaning | |---|---| | 0 | Success | | 1 | wait reached --timeout with no match | | 2 | Usage error (bad args, unknown inbox) | | 3 | Daemon not running (suggest popterminal start) | | 4 | Config missing or invalid (suggest popterminal init or delete config) | | 5 | Daemon-side failure |

How it works

Anonymous mode (default)

   Sender (any service)
        │
        ▼
  Cloudflare MX (popterminal.com)
        │
        ▼
  Email Routing catch-all rule
        │
        ▼
  popterminal.com Worker — looks up your handle, finds the Durable Object
        │
        ▼
  HandleDO (per-handle, hibernating) — INSERT into D1 buffer + push over WS
        │
        ▼
  Your CLI (anonymous ingress driver, WebSocket client)
        │  decode raw_b64 → mailparser → router → SQLite
        ▼
  ~/.popterminal/popterminal.db
        ▲
        │  fs.watch wake-up
        │
  popterminal wait/tail/emails (CLI readers)

The CLI dials out to popterminal.com via WebSocket — no inbound port, no tunnel binary, no public IP. Mail received while you're offline is buffered server-side for 48h and replayed when you reconnect, with ack-required delivery so nothing is lost on a CLI crash.

BYO mode

The same pipeline runs on your Cloudflare account. The CLI uses a cloudflared tunnel instead of WebSocket, and the Worker is deployed on your zone. popterminal.com is not in the data path.

Both paths feed the same parser → router → SQLite pipeline.

Files

| Path | What it is | |---|---| | ~/.popterminal/popterminal.db | SQLite (WAL mode) — inboxes + emails | | ~/.popterminal/config.json | Mode-specific config (domain, ports, handle, token refs) | | ~/.popterminal/anonymous-token | 32-byte hex token (mode 0600). Anonymous mode only. | | ~/.popterminal/popterminal.pid | Daemon PID (single-instance) | | ~/.popterminal/popterminal.log | Append-only daemon log | | ~/.popterminal/quarantine/ | Raw .eml files for messages mailparser couldn't parse | | ~/.popterminal/hmac-secret | BYO mode only — Worker shared secret (mode 0600) | | ~/.popterminal/cloudflared/ | BYO mode only — tunnel credentials + config.yml |

Configuration

Most of ~/.popterminal/config.json is set by popterminal start (anonymous) or popterminal init (BYO). Knobs you might tweak:

  • smtp_port (default 2525) — local SMTP server port for capturing outbound mail from local apps
  • https_port (default 8787) — BYO HTTPS receiver port

Env overrides:

  • POPTERMINAL_HOME — override ~/.popterminal/ location (mostly for tests)
  • POPTERMINAL_SERVER_URL — point the anonymous mode at a different server (default: https://popterminal.com)
  • POPTERMINAL_NO_REGISTER=1 — first-run skips registration; daemon stays in local-only SMTP mode (offline / air-gapped use)

Agent integration

Any agent that can run shell commands can use popterminal. The OTP retrieval pattern:

# get a fresh tag for this signup so mail won't mix with other tests
ADDRESS=$(popterminal handle show --json | jq -r '.address' | sed 's/@/+signup@/')

# tell the service to email ADDRESS, then…
OTP=$(popterminal wait signup --subject "code" --timeout 60s --json | jq -r '.text' | grep -oE '[0-9]{6}')
echo "OTP: $OTP"

Exit codes are predictable (0/1/2/3/4/5). Every read command takes --json. Errors go to stderr in JSON form when --json is set.

MCP server (Claude Desktop, Cursor, Claude Code)

popterminal ships a second binary, popterminal-mcp, which speaks the Model Context Protocol over stdio. It exposes the popterminal primitives as MCP tools so AI agents can wait for email, list received mail, and read message bodies without shelling out.

After npm install -g popterminal, add this to your MCP client config:

{
  "mcpServers": {
    "popterminal": {
      "command": "popterminal-mcp"
    }
  }
}

Config file locations:

  • Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) / %APPDATA%\Claude\claude_desktop_config.json (Windows)
  • Cursor: project-root .cursor/mcp.json or ~/.cursor/mcp.json
  • Claude Code: ~/.claude.json under "mcpServers", or per-project .mcp.json

Tools exposed:

| Tool | Purpose | |---|---| | get_address | Current handle + address. Call first. | | wait_for_email | Block until matching mail arrives. inbox, from?, subject?, timeout_seconds? | | list_emails | List recent mail. inbox?, from?, subject?, limit? | | read_email | Full body + HTML by ID | | reset_handle | Forget current handle. Daemon must be stopped. |

The MCP server reads from the same ~/.popterminal/popterminal.db, so it requires popterminal start to be running in a terminal (the daemon is what fills the database with incoming mail). If you call wait_for_email while the daemon is down, the tool returns a clear daemon_not_running error.

Web UI

New in v0.3.1. Run popterminal web to launch a local browser inbox at http://127.0.0.1:9090:

  • Three-pane layout: inboxes (left) / emails (middle) / message body (right)
  • Live updates via Server-Sent Events — new mail appears without refresh
  • Sandboxed HTML rendering, plain-text fallback, raw JSON view
  • Click-to-copy current address from the top bar
  • --port <N> to change port, --no-open to skip auto-opening the browser

Reads from the same ~/.popterminal/popterminal.db the daemon writes to, so it complements the CLI rather than replacing it.

Known limitations

  • BYO tunnel auto-restart not implemented: if cloudflared crashes, mail queues at Cloudflare Email Routing (no loss) until you popterminal stop && popterminal start. Anonymous mode reconnects automatically.
  • CLI + MCP only: TypeScript SDK + REST API still on the roadmap. MCP server ships in v0.3.
  • No outbound sending: receive-only. Outbound is the next planned addition (capture mode + relay through Resend/Mailgun/SendGrid).
  • No LLM-assisted extraction: parsed emails come out clean (text/html/subject/etc.) and the agent extracts what it needs.
  • Anonymous handles expire after 30 days of inactivity: if you receive mail through your CLI's WebSocket OR your handle has any active mail flow, the timer resets. If both your CLI is offline AND no mail arrives for 30 days, the handle is reclaimed. Run popterminal handle reset to deliberately roll your handle.

License

MIT.