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

promptwash

v0.1.0

Published

Redact secrets from, and mine insight out of, the Claude Code transcript archive in ~/.claude/projects

Readme

promptwash

Right now, on your laptop, there is a folder full of your API keys in plaintext. You didn't put them there. Claude Code did.

Every session Claude Code runs is written to ~/.claude/projects/ as JSONL and kept forever: your prompts verbatim, every reply, every tool call, and the full output of every bash command. Nothing is summarised or truncated.

So the first time a session ran cat .env, or printed a connection string, or echoed a token while debugging — that value became a permanent plaintext file, outside every .gitignore you have.

One month of ordinary use, measured:

694 files · 722 MB · ~2,100 prompts
800 secrets across 64 files
  ├─ 40 Supabase service_role JWTs   (full RLS bypass)
  ├─ 20 distinct database passwords  (production pooler hosts)
  └─ OpenAI / Stripe / Google / GitHub keys, private key blocks

Nobody leaked anything. Nobody pasted a key into a chat. It accumulated from normal work.

This repo cleans that up — and then does something more interesting with what's left.

One command

npx promptwash

That scans your archive for secrets (dry run, with a full report), asks before rewriting anything (every touched file is backed up first), then builds the dashboard, the prompt export and the functional breakdown into ./claude-history-stats/ and opens the dashboard in your browser. Everything runs locally; nothing leaves your machine.

npx promptwash --help for the knobs: --yes, --redact-only, --skip-redact, --no-open, --out, --literals, --entropy.

The sections below explain what each step does — and how to run the pieces individually from a clone.

1. Get the credentials out

node redact-secrets.mjs            # dry run: reports, writes nothing
node redact-secrets.mjs --apply    # rewrites, after backing up every file it touches

Structure-preserving: each record is parsed, walked, re-serialised. Line counts, key order and JSON validity all survive, so claude --resume keeps working. Unparseable lines pass through byte-for-byte, writes are atomic, and files touched in the last 10 minutes are skipped so a live session is never rewritten mid-write.

It knows the usual shapes — API keys, JWTs, private keys, DB URLs with inline passwords, *_SECRET/*_TOKEN assignments — and ignores <your-key-here>, xxxxxxxx, ${VAR} and friends.

It also catches the ones regexes normally can't. Credentials typed as prose — "I reset the password, it's hunter2Xyz now" — have no assignment or URL to anchor on. Two answers: --literals for known strings, --entropy for shape.

16+ characters, no separators, ≥2 lower, ≥2 upper, ≥1 digit, class-switch rate ≥ 0.4.

The separator rule is what makes it usable: real credentials are unbroken runs, while UUIDs, kebab identifiers and mcp__tool__names all contain - or _.

Composition is checked in code, not as a lookahead(?=\D*\d) is unbounded and will satisfy itself from a digit thousands of characters later in the file. An early version of this happily "verified" that sourceToolAssistantUUID contained a digit.

Naive version: 2.4M matches. This one: 243. Still dry-run it and read the report.

2. Then read what's left

Your transcripts are the only honest record of how you actually work — better than commit counts, better than LoC. Nobody writes prompts for the metrics.

npm run all        # → output/

You get a self-contained HTML dashboard (no server, no CDN, data inlined — a file:// page can't fetch() a sibling JSON), every prompt you've typed as readable markdown, and a functional breakdown of them.

The breakdown is the point. Prompts are classified by what they do to the conversation, not by topic:

| | | | --- | --- | | correction | pushing back on what the model produced | | steering | constraining how, without rejecting | | bug-report | the app is broken | | question · task | seeking information · opening new work |

correction vs bug-report is what makes the number honest. "the archive button doesn't work" and "no, I said HYR2 not HYR3" both look like pushback to a regex — only the second is overhead you could design away. The classifier routes on whether the complaint is model-directed or app-surface.

On that same month: 22% of everything typed was steering or correcting. Most corrections turned out to be one-line environment facts the model never had — which is a fact-sheet in AGENTS.md, not a prompting habit.

It also counts what you didn't type. The user role carries tool results, subagent notifications, slash-command bodies and [Image:] markers; counting those as prompts overstates your input by ~40%.

Before you trust the analysis

Redaction generalises — secret shapes are secret shapes. The analysis was calibrated against one person and one codebase, and these will be wrong for you:

  • Writing register (lib-classify.mjs) — the patterns match lowercase, unpunctuated, typo-tolerant English (dont, nah, pls, u). Write in full sentences, or not in English, and they'll miss nearly everything.
  • APP_SURFACE vocabulary — decides bug-report vs correction. It's one web app's nouns. Swap in yours.
  • The 400-token cutoff — from one prompt-length distribution. Run count-tokens.mjs and read its cutoff table first.
  • SYNTHETIC slash-command bodies — matches one /review template. Yours expand differently.

Then do the thing that actually works: read output/cat-correction.md end to end and check the categories match what you meant. That's how the numbers here got fixed — the first pass counted slash-command templates as steering and scored "good catch" as pushback.

Two warnings

The backups still hold everything. --apply copies each file to ~/.claude/projects-backup-<ts>/ before touching it. That's your undo, and it's a complete plaintext copy of every secret. Delete it once you're satisfied.

Redacting local disk doesn't un-leak anything. It doesn't reach server-side copies and it doesn't rotate a credential that was already exposed. If a service_role key shows up in the report, rotating it is step one. This is step two.

Layout

*.mjs                     the tools
lib-prompts.mjs           extraction — what counts as a prompt you typed
lib-classify.mjs          the functional categories
dashboard-template.html   edit this, not the generated html
output/                   everything generated — gitignored
secrets-literals.txt      your literal secrets — gitignored

node <tool>.mjs --help for flags. Node ≥ 20, one dependency (js-tiktoken).

Never run the id-scrubber over ~/.claude/projects. uuid/parentUuid link the conversation tree and tool_use_id pairs each call with its result — blanking them collides every pair and breaks --resume irreversibly. Output files are derived and disposable; the archive is not.

MIT.