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

@peeve/cli

v1.1.1

Published

The Peeve developer CLI — init, connect, verify and toggle the kill switch. Zero dependencies.

Readme

@peeve/cli

The peeve command — scaffold, connect, verify, and operate a Peeve install from the terminal. Built entirely on Node's parseArgs (node:util); zero third-party dependencies.

# run without installing
npx @peeve/cli <command>

# or install globally
npm install -g @peeve/cli
peeve <command>

Requires Node 18+.


Commands

peeve init

Detects your framework, installs the widget, and writes peeve.config. Run it in your project directory — init scans the project, figures out where the HTML shell lives, injects the Peeve <script> there, and writes a .peeve config recording what it did. No manual copy-paste.

peeve init --publishable-key pk_live_…             # detect → inject → write .peeve
peeve init --publishable-key pk_live_… --dry-run   # show WHICH file it would edit + the insertion
peeve init --framework next-app --key pk_live_…    # override detection
peeve init --publishable-key pk_live_… --print     # only print the snippet, write nothing (legacy)
peeve init                                          # prompts for the key in a TTY, then confirms the edit

| Flag | Description | | --- | --- | | --publishable-key <pk_…> | Your publishable key (pk_*). Alias: --key. Prompted if omitted in a TTY. If still missing (non-interactive), a clearly-marked pk_REPLACE_ME placeholder is inserted with a printed TODO. | | --framework <name> | Override detection. One of: next-app, next-pages, react, vue, angular, sveltekit, nuxt, astro, html. | | --out <dir> | Project directory to scan and write into (default: cwd). | | --cancel-selector <css> | CSS selector for your cancel/downgrade control. Emits data-cancel-selector="…" on the <script> so Peeve auto-tags churn intent. Prompted in a TTY; skipped with --yes. | | --dry-run | Report which file would be edited and show the insertion, without writing. | | --print | Print the <script> snippet only; write nothing (the old behavior). | | --yes, -y | Don't prompt; skip the edit confirmation and the cancel-selector prompt. |

How detection works

init reads package.json deps/devDeps and probes key files:

| Framework | Signals | Where the widget is injected | | --- | --- | --- | | Next.js (App Router) | next + app/layout.* (or src/app/layout.*) | inside the layout's <body> | | Next.js (Pages Router) | next + pages/_document.* (or src/pages/_document.*) | inside _document's <body> | | React + Vite / CRA | react (+ vite) + index.html / public/index.html | before </body> | | Vue | vue + index.html | before </body> | | Angular | @angular/core + src/index.html | before </body> | | SvelteKit | @sveltejs/kit + src/app.html | before </body> | | Nuxt | nuxt + nuxt.config.* | prints app.head.script config guidance (no HTML shell to edit) | | Astro | astro + src/layouts/Layout.astro | inside the layout's <body> | | Plain HTML / static | an index.html with no framework | before </body> | | Unknown | none of the above | prints the snippet + guidance, edits nothing |

Safety

  • Idempotent — if the target already contains the widget (its src or a data-publishable-key/data-workspace-key), init skips with an "already installed" message. Running it twice never double-inserts.
  • Preview + confirm — init shows the exact file and the + line it will add, then asks y/N in a TTY. --yes (or a non-interactive shell) skips the prompt; --dry-run shows everything and writes nothing.
  • Never clobbers — it inserts only at a real <body> anchor. If the expected anchor isn't found (or the file can't be read), it does not force an edit — it falls back to printing the snippet + guidance. Before editing it writes a .bak next to the file (never overwriting an existing one — the next free .bak, .bak.1, …); assume your project is git-tracked too.
  • Stays inside your project — init edits only a regular file that really lives under the project directory. A symlinked target (or one behind a symlinked parent) is refused, not followed, so cloning an untrusted repo and running init can't write outside it.
  • Won't publish a secret — an sk_… key is refused rather than written into the snippet. See Configuration.

The .peeve config it writes records { publishableKey, framework, target, cancelSelector? } so verify and later runs know what was installed and where. It is written 0600, and init adds it to your .gitignore — creating one if there isn't one, never duplicating an existing entry, and leaving an explicit !.peeve alone if you'd rather track it. The key it holds today is public by design, but the format permits a secretKey, so "not committed" is the safer default for it.

After installing, init prints the billing-intent data-* attributes so you can wire up passive churn / upgrade tracking on your own markup. In a TTY it also offers to capture your cancel-button CSS selector and bake it into the snippet as data-cancel-selector="…".

peeve connect

One-liner to add Peeve to the current project and point at the sign-in flow — init plus a sign-in link.

peeve connect --publishable-key pk_live_…

peeve verify

Check an install end to end: is the platform reachable, is the key valid, and (with --site) is the widget <script> present on the page?

peeve verify --publishable-key pk_live_… --site https://app.acme.com

| Flag | Description | | --- | --- | | --publishable-key <pk_…> | Key to test (pk_*). Alias: --key. Or from PEEVE_PUBLISHABLE_KEY / PEEVE_KEY / .peeve. A secret key is refused. | | --site <url> | Fetch this page and check for the widget tag. |

peeve killswitch

Toggle the workspace kill switch. Requires a secret key (sk_*).

peeve killswitch --on --secret-key sk_live_…   # stop Peeve on your site
peeve killswitch --off --secret-key sk_live_…  # resume

| Flag | Description | | --- | --- | | --secret-key <sk_…> | Your secret key (sk_*). Alias: --key. Or from PEEVE_SECRET_KEY / PEEVE_KEY / .peeve. | | --on / --off | Trip (default) or clear the kill switch. |


Billing-intent attributes

Peeve reads a small set of data-* attributes off your own billing markup so it can passively track pricing views and cancel intent — and attach which plan, price, and interval each signal belongs to — even when the assistant is never opened. That's how a team sees who's about to churn and correlate reached-out-vs-cold.

The widget script tag is the same one peeve init prints; these attributes go on the page elements you already render.

| Attribute | Put it on | What it does | | --- | --- | --- | | data-peeve-cancel | Your cancel / downgrade button or link | Its presence marks a cancel/downgrade control → emits a churn cancel_intent signal. | | data-peeve-plan="<name>" | A pricing-plan card | Plan display name, e.g. "Growth". | | data-peeve-amount="<amount>" | A pricing-plan card | Price, e.g. "99" or "$99.00". | | data-peeve-currency="<code>" | A pricing-plan card | Optional ISO 4217 code for the amount (USD/EUR/GBP…). Inferred from the amount's currency symbol when present, else defaults to USD. Only meaningful alongside an amount. | | data-peeve-interval="<monthly\|quarterly\|yearly>" | A pricing-plan card | Billing cycle. Use yearly for annual. |

Copy-paste — annotate a cancel button and a pricing plan card:

<!-- Cancel / downgrade control → emits a churn `cancel_intent` signal. -->
<button data-peeve-cancel>Cancel subscription</button>

<!-- Pricing-plan card → tags pricing views with plan / amount / interval. -->
<div data-peeve-plan="Growth"
     data-peeve-amount="$99.00"
     data-peeve-currency="USD"
     data-peeve-interval="monthly">
  …
</div>

Why: passive churn / upgrade tracking — Peeve links each pricing view and cancel click to a plan, price, and interval with no extra event code.

Normalization. Peeve normalizes every data-peeve-* value before storing it — it never keeps your raw string — so the data is consistent across apps: amount → a number (currency symbols and thousands separators stripped), currency → an uppercased ISO code, interval → canonical monthly | quarterly | yearly, and plan → trimmed.

Prefer not to hand-edit the cancel button? Pass its CSS selector to init and the widget tags it for you (no markup change needed):

peeve init --publishable-key pk_live_… --cancel-selector "#cancel-plan"
# → <script … data-cancel-selector="#cancel-plan" async></script>

peeve init prints the annotation example above after it scaffolds, and (in a TTY) offers to capture the selector interactively.


Configuration

Peeve has two kinds of key, and the CLI keeps them apart on purpose:

  • pk_… publishable — public by design. It ships inside your page HTML to every visitor. Used by init, connect and verify.
  • sk_… secret — a backend credential. Used by killswitch only.

Every command resolves the key of the kind it can safely use, in this order:

| | init / connect / verify (publishable) | killswitch (secret) | | --- | --- | --- | | 1. Flag | --publishable-key (or --key) | --secret-key (or --key) | | 2. Environment | PEEVE_PUBLISHABLE_KEY, then PEEVE_KEY | PEEVE_SECRET_KEY, then PEEVE_KEY | | 3. Config file | .peeve → publishableKey | .peeve → secretKey |

PEEVE_KEY stays the catch-all, but is only accepted when its prefix matches the kind being asked for — so a PEEVE_KEY=sk_live_… left exported from a killswitch run is never picked up by init and baked into your HTML. init, connect and verify refuse an sk_… key outright rather than exposing it.

The .peeve file is JSON ({ "publishableKey": "pk_…" }; init also stores "framework", "target" — the file it edited — and "cancelSelector" when you provide one). init writes it 0600 (owner-only). Prefer PEEVE_SECRET_KEY over putting a secretKey in .peeve, so it can't be committed.

The Peeve platform URLs are fixed — there's no base-URL flag or env var to set. Each command talks to the host that serves what it needs: verify probes the widget endpoints, and killswitch is a privileged backend call. Global flags: -h/--help, -v/--version.


Examples

# scaffold, print only
npx @peeve/cli init --publishable-key pk_live_abc --print

# verify a live install
npx @peeve/cli verify --publishable-key pk_live_abc --site https://app.acme.com

# emergency stop (secret key)
PEEVE_SECRET_KEY=sk_live_xyz npx @peeve/cli killswitch --on

Network — the hosts this CLI contacts

| Host | Used by | Why this host | |---|---|---| | live.peeve.ai | peeve verify | It probes the same publishable-key, Origin-gated endpoints a visitor's browser calls — which is what makes the check faithful rather than a stand-in. A 403 from here means the key is valid and this origin simply isn't allowlisted, which is the expected result from a terminal. | | p.peeve.ai | peeve killswitch | A privileged backend call needing a secret key. It has no equivalent on the documented API, so it is served from the packages host. | | app.peeve.ai | peeve connect | Printed as a sign-in link for you to open. Nothing is fetched from it. | | cdn.peeve.ai | the snippet peeve init writes | Where widget.js is served from, on your visitors' pages. |

All of these are fixed — there is no base-URL flag, env var or constructor option to change them, by design. Which host a given call uses follows from what that call is, not from configuration.

If you run an egress allow-list or a strict CSP, these are the hostnames to permit.

License

MIT © Peeve