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

memhouse

v0.18.9

Published

memhouse — agent conversation memory across 17 editors: parse-on-client shippers, a typed ClickHouse store with one set of rooms per member, kernel-agency install, and the agentlytics dashboard

Readme

memhouse

Claude Code deletes your transcripts after 30 days. (cleanupPeriodDays, default 30 — session files are removed at startup.) Measured on the machine this was built on: months of daily use, and the oldest surviving local transcript was nine days old. Every problem you solved before that exists nowhere — unless something durable was keeping it.

memhouse is that something. It reads the session transcripts your editors already write to disk — 17 of them: Claude Code, Codex, Cursor, Zed, Copilot, Gemini CLI and the rest — parses them locally, and ships typed rows into a ClickHouse you choose: a container on this laptop, a box you run, your company's cluster, or ClickHouse Cloud. memhouse runs no service of its own — there is no memhouse cloud to sign up for and nothing is proxied through anyone — so the account is yours and the data is wherever you decided to put it. Then you can search every past session, see what it cost, resume the conversation that solved this before, and let an agent query its own history.

And every row says where it came from, in a way no client can fake: user_id is stamped by the server (MATERIALIZED currentUser(), async inserts pinned off so the stamp cannot be skipped) and host is a fingerprint minted once per install. A team pointed at one house is a team memory with real attribution — not a label somebody's client claimed.

npm install -g memhouse
memhouse onboard

That is the whole install — no flags, nothing to compile. onboard finds your editors, sets up a house, ships, and starts the dashboard.

On most Linux distros that first line needs sudo. A distro-packaged Node puts its global prefix under /usr, which you do not own, so npm stops with EACCES. That is the single most likely place a first install stalls — measured on a clean Ubuntu machine:

ls -ld "$(npm prefix -g)"     # root-owned? use sudo. Owned by you? do NOT — see EACCES below
sudo npm install -g memhouse

If the prefix is yours (Homebrew, fnm, nvm, volta), sudo is the wrong fix and makes it worse — EACCES on install tells them apart.

No ClickHouse yet?

onboard offers to run one for you if docker or podman is present:

memhouse deploy --local      # a loopback-bound ClickHouse, then install and ship

The house it stands up is yours by name: its superuser defaults to your OS username (polat, not memhouse_root — override with --user), and the house database defaults to the same name, so your memory lives at polat.messages and WHERE user_id = 'polat' reads like it should. The name and password are fixed when the data volume is first initialised, like any ClickHouse container, and reused on every redeploy after that.

Otherwise point at one you already run, or hand the SQL to whoever administers it:

memhouse install --url https://… --user … --password …   # one you have
memhouse install --admin-user … --admin-password …       # let memhouse build it
memhouse install --print-sql                             # print the SQL, run it yourself

memhouse does not embed a database. If you have neither a ClickHouse nor a container engine, install one of the two.

Commands

memhouse onboard | install | setup | discover | doctor | uninstall | reset
memhouse ship [--full|--loop N] | search <terms> | stats | status
memhouse resume <session-id>               print the command that reopens a session
memhouse update [--check] [--channel TAG]  upgrade along the channel this install follows,
                                           restart daemons, check the schema
memhouse start | stop                      dashboard + shipper as daemons
memhouse service install | uninstall       survive a reboot
memhouse deploy --local | --down           stand up (or remove) a local house
memhouse invite <name>                     mint a member + house, hand them one env file
memhouse members [--db X]                  who is in a house, and what each can reach
memhouse whoami [--admin]                  which credential is in play, and what it may do
memhouse instance                          which memhouse this is: home, binary, house, rooms, scope, bindings
memhouse rooms                             what your rooms are actually called
memhouse share <user> [--only …|--revoke|--list]
                                           let a housemate read yours, in whole or in part
memhouse sessions-query                    print the session rollup SQL
memhouse passwd                            rotate this member's password
memhouse migrate | migrate-rooms [--dry-run] [--yes]
                                           run whatever this house still needs
memhouse relocate --to <url>               copy this house to a new ClickHouse, then repoint
memhouse nightly [--out DIR]               build an installable tarball from this checkout
memhouse plugins install claude            5 skills — house, recall, sql, access,
                                           admin — into every Claude Code config
                                           dir found
memhouse prompt                            memory snippet for an agent's system prompt
memhouse prompt --install                  an install prompt, rendered for this machine

Any command's own usage: memhouse <command> --help (or -h) — always prints usage, never runs the command.

Every command works both ways: interactive for you, --yes / flags / --json for an agent — so an agent can install its own memory unattended.

onboard offers the skills at the end, and plugins works across every Claude Code config directory on the machine~/.claude, whatever CLAUDE_CONFIG_DIR points at, and each Kommander-style playbook under ~/.claude-playbooks/. All are selected by default; answer with numbers to narrow it, or pass --target DIR for exactly one. Installing into just the default config is how /mem:recall ends up missing from the instance you actually work in, silently, since a missing skill never announces itself.

Config resolves: flags → MEMHOUSE_* env → $MEMHOUSE_HOME/env (default ~/.memhouse/env). There is no house-shaped default: with nothing configured, the commands that read or write memory refuse and say so rather than guessing localhost:8123, which on a lot of machines is a real house belonging to someone else.

Which sessions ship

Everything on the machine, by default: every adapter, from wherever that editor keeps its sessions. memhouse discover prints what each adapter is watching and the variable that moves it:

Watched directories (override with the variable shown, in the env file):
    claude       ~/.claude, ~/.claude-playbooks/kommander, …   [MEMHOUSE_CLAUDE_ROOTS]
    codex        ~/.codex                                      [MEMHOUSE_CODEX_ROOTS]
    gemini-cli   ~/.gemini                                     [MEMHOUSE_GEMINI_CLI_ROOTS]
    cursor       ~/Library/Application Support/Cursor/User, …  [built-in]

Two kinds of line in the env file narrow it, or --editors / --claude-roots at install:

MEMHOUSE_EDITORS='claude'
MEMHOUSE_CLAUDE_ROOTS='~/.claude-playbooks/kommander-chaos'

MEMHOUSE_EDITORS names the adapters to run. MEMHOUSE_<EDITOR>_ROOTS replaces one adapter's location: the adapter's name upper-cased, dashes to underscores. Claude is the one adapter with many roots (every Claude Code config directory), so its list may hold several; every other adapter has one store, so one path. Adapters marked [built-in] resolve a platform app-data directory and cannot be moved yet.

An adapter name or a directory that does not exist is refused and reported against that adapter, never shipped from the default instead: a typo that quietly shipped nothing would look like a working install with an empty house, and one that quietly shipped the wrong store would be worse.

Channels

memhouse update follows the npm dist-tag the install came from, not latest by reflex. A house published under another tag — npm publish --tag team, so one team can run a newer layout without every other house being dragged along — installs with npm install -g memhouse@team, and update keeps it there. The channel is inferred from the installed version; pin it with MEMHOUSE_CHANNEL='team' in the env file or memhouse update --channel team, which writes the same line. A tarball or checkout build sits on no tag and is never auto-updated; update --check says so and names the way forward. An invite from a house on a channel carries it, so the invitee follows it too.

Instances

An instance is a home: an env file naming a house, the daemons shipping to it, and the playbooks whose sessions it ships (MEMHOUSE_CLAUDE_ROOTS). A machine can run several, each under its own MEMHOUSE_HOME, on different channels. plugins install claude from an instance installs into exactly the playbooks that instance ships and binds them: it writes MEMHOUSE_HOME and MEMHOUSE_BIN into each playbook's settings.json env, which Claude Code applies to every session there. So the /mem:* skills in a bound playbook read that instance's house and run that instance's binary, whatever the shell they were started from had. plugins list shows the binding; plugins remove takes it out. Everything else in settings.json is left as it was.

Going back into a session

search finds the conversation; resume hands you back into it.

memhouse resume claude:6b1f…            # prints:  cd /path/to/project && claude --resume 6b1f…

It prints the command instead of running it. A resume run from the wrong directory or with a stale id does not fail — it opens a new session, and the transcript you wanted is still gone while the tool reports success. You paste it, so you see it first.

Verified for claude, codex and opencode. Cursor, Zed, VS Code, Kiro, Copilot for JetBrains, Antigravity and Devin/Windsurf have no CLI that takes a session id, so there is nothing to resume into and resume says exactly that, rather than opening a folder and calling it the same thing. Everything else — goose, gemini-cli, cursor-agent, copilot-cli — is refused as unverified, which is a different sentence on purpose: nobody has read the flag out of its own --help yet, and guessing is how you print a command that quietly does the wrong thing. See memhouse/resume.js.

Which machine is which

All of your machines ship into your rooms, and the host column is what tells them apart. So each install writes ~/.memhouse/host.json — a random fingerprint, minted once — and every row that machine ships carries <hostname>-<8 hex> built from it.

Random, not derived, because deriving it fails in both directions: two laptops that both answer to the default MacBook-Pro on the same platform hash to the same id and merge into one apparent machine, and renaming a machine moves a derived id so its own history appears to stop and a stranger's to begin. A stored fingerprint cannot collide and does not move — rename the machine and status says so while the id stays put.

memhouse status     # ✓ host: macminim-4127a95b
memhouse doctor     # names every host in your rooms, and whether this one is among them

Then query one machine with WHERE host = 'macminim-4127a95b'.

Uninstalling, in three tiers

memhouse uninstall                   # stop everything; KEEP the config and this host's identity
memhouse uninstall --credentials     # also forget the house and its password
memhouse uninstall --full-removal    # all of ~/.memhouse, identity included

The default is deliberately the conservative one. Stopping the shipper is routine — before an upgrade, while debugging, when a laptop should go quiet for a week — and it should not also mean "forget which house I use and who this machine is". --full-removal asks first, because a later reinstall mints a new identity and this machine then reads as two machines in your history.

No tier touches your memory. Transcripts live in ClickHouse, and everything the shipper wrote rebuilds from the local session stores with ship --full.

Staying current

memhouse update            # upgrade, restart the daemons, check the house schema
memhouse update --check    # compare versions, change nothing

A bare npm i -g memhouse@latest does half the job. It does not restart the daemons, which keep parsing with the code they booted with (a shipper here once ran 1d16h out of a directory that had been moved); and it does not check whether your house predates a room the new shipper needs. update does both, and from a checkout it rebuilds public/ — otherwise git pull serves you the previous release's dashboard forever.

The daemons also notice on their own: each one compares the installed version against what it booted with and hands over — exiting under systemd/launchd so the supervisor restarts it, re-execing itself when nothing is supervising. update just makes it immediate.

How your memory is stored

A house is a database; its rooms are tables. sessions, messages and tool_calls hold your turns. Every row says where it came from: user_id, stamped by the server (MATERIALIZED currentUser(), with async inserts pinned off so the stamp cannot be skipped), and host, the machine's install fingerprint. WHERE user_id = 'alice' is one person; WHERE host = '…' is one machine. Neither is forgeable from the client.

Your rooms are named for you, and one grant covers them. Every member of a house holds GRANT … ON mem.<name>_* TO <name>: their own rooms, present and future, and nothing else in the database. That is the whole access model, and it is the same on a laptop, a team's server, or a kernel:

| | rooms | the member holds | |---|---|---| | alone on your laptop | mem.polat_* | one grant on mem.polat_* | | a team on one server | mem.polat_*, mem.alice_*, … | each their own |

A colleague can read their isolation back with SHOW GRANTS in one line, which is the property this rests on. There is no row policy to trust and no database-wide grant to worry about: ALL ON mem.* would cover rooms created later, which is how two people in one database once became a leak, so memhouse never issues it — not to a member, not to you.

Standalone is a team of one. memhouse deploy --local creates the house with an admin credential (memhouse_root) and a member named after your OS user, keeps both in the env file, and ships as the member. Inviting a colleague later is memhouse invite <name>; the admin credential is already there.

Reading across everyone is a UNION ALL over the rooms you hold:

SELECT user_id, count() FROM mem.polat_messages GROUP BY user_id
UNION ALL
SELECT user_id, count() FROM mem.alice_messages GROUP BY user_id

An operator with an admin credential reads every room; a member reads the ones they own plus whatever was shared with them, and naming a room they do not hold is an error rather than a silent omission.

The house never destroys what it cannot rebuild

The shipper is insert-only. It runs no DELETE, no TRUNCATE, no mutation of any kind, so no privilege it holds can lose you a row.

That is not free, because ReplacingMergeTree is a dedupe engine, not a diff engine: a re-parse yielding fewer messages leaves the old higher-seq rows with nothing written over them. The shipper used to delete the session's rows first to clear that tail — and a shorter re-parse has two indistinguishable causes. Either an adapter bug was fixed and the extra rows are junk, or Claude Code compacted the transcript, or the retention window (cleanupPeriodDays, 30 days by default) took it. In the last two cases the house holds the only surviving copy, and the delete destroyed it.

So rows carry an epoch: which parse of the session they belong to.

  • Nothing changed (the common case, including ordinary growth): same epoch, rows dedupe exactly as before, no extra storage.
  • Shorter, or rewritten at a position the house already holds: the new parse goes to epoch + 1. The old one stays complete and readable. You pay storage only when the old rows are irreplaceable.

Reads show one parse per session — the newest — so counts, tokens and cost are unchanged. /mem:sql and any hand-written query should filter the same way; the skill carries the clause.

Imported history is protected the same way. origin says who wrote a row, and it is in the sorting key of messages and tool_calls, so ReplacingMergeTree cannot collapse an imported row against a shipped one. Anything you imported from an older house, another product, or a machine that no longer exists survives every re-ship. sessions is the deliberate exception — one row per session, no origin and no epoch in its key, because a session's metadata has a single current version and a second row makes every rollup count its messages twice.

The shipper verifies the sorting keys before it writes and refuses if they are wrong, so an old house cannot be corrupted by a new shipper. memhouse migrate-rooms rebuilds it: copy, atomic swap, and the old room kept as <room>_pre_epoch for you to drop. The house records the move in events, and memhouse doctor reads it back.

Upgrading

memhouse update upgrades the code, restarts the daemons, and — from 0.10.0 onward — detects a schema migration the house needs and prompts to run it.

One exception: upgrading from 0.9.x. That release predates migrations, and update runs the old version's code (it replaces itself mid-run), so it cannot prompt. After the upgrade the first ship refuses (nothing is lost) and tells you the one command to run by hand:

memhouse update      # installs the new version, refuses to ship the old-schema house
memhouse migrate     # the one-time rebuild it named — copy + atomic swap, nothing deleted
memhouse doctor      # every line a check mark

If other machines ship into the same house as the same member, upgrade them too — a pre-0.10 shipper still deletes-before-reinsert; memhouse doctor flags any that are behind, and prints the REVOKE ALTER DELETE, ALTER UPDATE fallback that stops them destructively until you can.

Troubleshooting

An editor reads zero sessions

Five adapters — cursor, zed, opencode, goose, antigravity — read sessions out of SQLite files. That used to mean better-sqlite3, whose native binding is built by an npm install script; npm 12 blocks install scripts by default, so a plain install left all five reading nothing and the fix was a flag (--allow-scripts=better-sqlite3) you had to re-type on every upgrade. SQLite is node:sqlite now — part of Node — so that failure class is gone along with the flag.

What can still go wrong is one store at a time: a state.vscdb locked by a running editor, a corrupt file, a schema a new editor release changed. None of it is silent — memhouse discover and memhouse doctor both name the skipped adapter and say why, and the other sixteen keep working.

npx -y memhouse discover      # no flags here either

EACCES on install

sudo is right for only one of the two causes, and what tells them apart is who owns the prefix root — not the path, and not the file npm named.

ls -ld "$(npm prefix -g)"
  • Owned by root — a system-managed Node (distro packages under /usr). Re-run with sudo.
  • Owned by you — the prefix is yours (Homebrew, fnm, nvm, volta), so the root-owned file npm tripped on is a stray from an earlier sudo npm. Another sudo adds more. Repair just that path: sudo chown -R "$(id -u):$(id -g)" <the path npm named>. Same for ~/.npm.

The path proves nothing on its own: Homebrew's prefix is /usr/local on Intel and /opt/homebrew on Apple Silicon, and is yours in both cases.

Daemons don't survive a reboot

memhouse start detaches with pidfiles — they outlive the shell, not a restart. memhouse service install writes a real user service instead (systemd --user, or a launchd LaunchAgent) and takes over. On Linux a --user unit stops at logout unless lingering is on; install detects that and prints the loginctl command.

A rootless podman house has the same problem for the same reason, and deploy --local says so.

Working from a checkout

npm install            # two runtime deps, no build step
node bin/memhouse.js …
npm test               # syntax gate + unit checks

misc/origin-matrix.sh <label> <url> <user> <pass> checks the import-protection guarantees against a real ClickHouse. misc/tier-matrix-test.sh walks the tier/ownership matrix against a real container engine on Linux. Both take minutes and are not part of npm test.

Layout

| Path | What | |---|---| | memhouse/ | the product — DESIGN.md, per-member/, shipper/, server/, delivery/ | | editors/ | the 17 editor adapters (inherited from agentlytics; the crown jewels) | | pricing.js + pricing.json | the cost engine | | ui/ | the dashboard SPA (built to public/, served by the memhouse server) | | agency/ | the earlier agentlytics-agency wrap — prior art, not how memhouse works | | TERMINOLOGY.md | the constellation terminology canon |

Deeper reading: memhouse/DESIGN.md (the bets, and why the room layout has moved three times), memhouse/house/HOUSE.md (the layout and the schema), SECURITY.md (what holds, and what does not), memhouse/delivery/kernel-install.md, memhouse/COMPETITION.md.

Deferred designs (captured, not yet built): docs/design/host-repoint-reconciliation.md — what should happen when the shipper is repointed at a new, empty host (house identity, local binding state, and a refuse-then-init-here gate so transcripts never land on the wrong server).

In constellation terms (TERMINOLOGY.md) memhouse is an agency: a house — the mem database — plus a resident working in it, the shipper. The house alone would hold; the shipper is what makes it act. It competes with memory-house; if it wins, it becomes memory-house v4.

What's supported

| Surface | Status | |---|---| | macOS | primary — developed and driven here daily | | Linux | real — installs, ships, service install (systemd --user), rootless podman deploy --local; exercised on Debian and Ubuntu machines | | Windows | untested — the adapters declare Windows paths but no one has run a single install; treat it as unknown until this row changes | | ClickHouse | 25.11 and 26.x, both in CI; 26.2+ needs no flags, 25.x needs the text-index flag the shipper already passes | | Node | ≥ 24 — SQLite comes from node:sqlite, which is stable in 24. It works flagless on 22.13+ but prints ExperimentalWarning on every command (measured on v22.23.2), and Node 20 is past EOL. There is no native dependency and no build step at any version. |

Heritage & license

Built on agentlytics by Fatih Kadir Akın (MIT) — the adapters, dashboard, and cost engine come from there, and this repo's history carries the full lineage. MIT.