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

claude-keep

v0.1.1

Published

Keep your Claude Code home (~/.claude) in a private git repo, and replay MCP servers, plugins and setup steps on a new machine.

Readme

claude-keep

Keeps your Claude Code home directory (~/.claude) in a git repo you own, and reapplies the machine state (MCP servers, plugins, setup steps) when you set up a new machine. It versions what you author: skills/, agents/, commands/, hooks, settings.json and a memory/ tree. It leaves out transcripts, sessions, caches, credentials and downloaded plugin sources: those are private, huge, or reinstallable. Two repos are involved: this public tool, and the private config repo it manages. Zero dependencies, Node 20+.

Requirements

git on PATH (on Windows, Git for Windows, which brings the sh the bundled pre-commit hook needs), Node 20 or newer, and the Claude Code CLI (claude), which apply uses to register MCP servers and install plugins.

gitleaks is optional but recommended. The pre-commit hook init installs runs gitleaks protect --staged --redact; without gitleaks it prints a notice and exits 0, so no secret scan runs.

Install

Run it without installing. On a machine that has never seen the package, add -y so npx does not stop to ask. Or install globally, which gives you claude-keep and the short alias ckeep:

npx claude-keep <verb>
npx -y claude-keep doctor
npm install --global claude-keep

Quick start: a new config repo

npx claude-keep init                 # ~/.claude becomes a repo
$EDITOR ~/.claude/claude-keep.json   # describe MCP servers, tools, setup steps
cd ~/.claude
git remote add origin [email protected]:you/claude-config.git
npx claude-keep push                 # first commit, then push

Make that remote private: everything in it is committed in the clear.

New machine

Install git and Node 20+, then clone your config repo into place. Either clone to ~/.claude, or clone anywhere and point CLAUDE_CONFIG_DIR at it.

git clone [email protected]:you/claude-config.git ~/.claude
npx -y claude-keep doctor    # what is this machine missing?
npx claude-keep apply        # replay MCP servers, plugins, setup steps
claude                       # start Claude Code and log in

Run apply from a terminal, not from inside a Claude Code session: its steps want the terminal, and a session that rewrites the config it is running from is worse still. It refuses to run when CLAUDECODE is set.

Then, in each project that should keep its own memory:

npx claude-keep link <slug>

Daily use and the machine switch

Back up whenever you want, from anywhere:

npx claude-keep push

To hand work to another machine, use the bundled handoff skill from inside a Claude session by typing /handoff. It writes a short note to a temporary file (where you stopped, the next step, blockers, and the branch and commit you were on), then runs npx claude-keep push --wip --note <that file>. That commits and pushes the dirty project tree, files the note under the project's memory slug, and pushes the config repo.

On the machine you arrive at, run npx claude-keep pull. It tells you whether what it brought in needs apply.

Verbs

Only link takes a positional argument. Any unknown flag is an error. --help prints the top-level usage.

init

claude-keep init

Turns the config dir into a claude-keep repo. Safe to run again.

  • Creates the directory and the repo when missing, with main as the initial branch. A directory nested inside someone else's work tree gets a repo of its own rather than writing into the parent.
  • Merges .gitignore rule by line: existing rules stay, missing ones are appended under a # added by claude-keep header. Appended rules land after everything already in the file, including a !negation, so read the merged result before committing it.
  • Installs the pre-commit hook and pins core.hooksPath to the repo's own hooks directory, so a global core.hooksPath cannot shadow it. It never overwrites a hook it did not write: a foreign pre-commit, or a core.hooksPath already pointing elsewhere, is reported as skipped with the reason.
  • Writes .gitattributes (* text=auto eol=lf) only on a repo it just created. Renormalizing line endings on a repo that already has history is not safe, so an existing one is left alone.
  • Creates claude-keep.json and memory/ when absent.

It prints one line per item (repo, .gitattributes, .gitignore, manifest, memory/, hook) with that item's status and, where it skipped, the reason.

doctor

claude-keep doctor

It changes nothing, and a missing tool or a warning still exits 0, so it is safe in a script. It exits 1 when it cannot do its job at all: an unparsable claude-keep.json, or a missing git.

It checks git, node, claude and gitleaks (optional, printed in lower case so its absence does not read as a real gap), then runs every deps[].probe line from your manifest through a shell, filtered by the dep's os and capped at 10 seconds each. Exit 0 means present; a failing probe prints that dep's hint. Warnings it can add:

  • no claude-keep.json, or the config dir is not a git repo
  • the pre-commit hook is not active
  • a key in settings.json env whose name looks like a credential (KEY, TOKEN, SECRET, PASSWORD, AUTH, CREDENTIAL, …)
  • the current project has no memory link, or its settings file is not valid JSON
  • CLAUDECODE is set, so apply would refuse

pull

claude-keep pull

Runs git pull --rebase --autostash in the config dir. It fails if the config dir is not a git repo, or has no remote.

  • Lists the changed files, the first 20 by name and the rest as a count.
  • When claude-keep.json itself changed, prints every added and removed line carrying a run, probe, command, args, cwd or url key. That list is uncapped on purpose: a padded manifest must not be able to hide its last entry behind a cap. Read those lines before running apply.
  • Says run: claude-keep apply (...) when a change touched settings.json, claude-keep.json, skills/handoff, or a path a step names in cwd or when.missing.
  • If the pull fails, including a rebase conflict, it fails with the last 20 lines of git's output plus the way out: git -C <config dir> rebase --abort.

apply

claude-keep apply

Replays machine state. Refuses to run inside a Claude Code session. It works in a fixed order, and each item it adds or runs prints a + ... line first. Items already present, or skipped, appear only in the summary at the end:

  1. marketplaces from settings.json extraKnownMarketplaces
  2. plugins from settings.json enabledPlugins, the ones set to true
  3. MCP servers from the manifest, via claude mcp add-json --scope user
  4. steps from the manifest
  5. the bundled handoff skill

apply only adds. It never removes a marketplace, plugin or MCP server, so anything you registered by hand survives. A plugin whose marketplace was skipped is skipped too, rather than failing later and taking the rest of the run with it. A failing step stops the run: the steps after it may depend on it.

The bundled skill is installed once, then left alone. A copy that differs is treated as your edit, not as a stale version, and is reported as kept. Delete the directory to get the shipped version back.

link

claude-keep link <slug> [--force]

Gives the current project its own memory directory. The slug must match ^[a-z0-9-]{1,40}$.

It creates <config dir>/memory/<slug> and writes autoMemoryDirectory into .claude/settings.local.json at the git toplevel, not in whatever subdirectory you are standing in, which is where doctor and push read it back. When the config dir is ~/.claude, the value is written as ~/.claude/memory/<slug>; otherwise it is the full path.

If the file already links a different directory, link asks before replacing it. Answer n and the old link stands, though the new memory directory is still prepared. With no terminal to ask on, it does not guess: it fails before writing or creating anything and tells you to rerun with --force. Run outside a git repo, it uses the current directory and says so.

push

claude-keep push [--wip] [--note <file>]

Commits and pushes the config repo. Safe inside a Claude session.

The memory slug comes from .claude/settings.local.json, then .claude/settings.json, at the project root, the same setting link writes. A project that never linked one files under home. A settings file that will not parse is an error rather than a silent fallback, since the wrong slug would file a handoff where the other machine will not look for it.

  • --wip commits the dirty project tree as wip: handoff <date> <time> and runs git push -u origin HEAD. A clean tree is still pushed, since it can be ahead of its remote. It is refused in three cases, so a --wip meant for a project cannot sweep up something much larger: the project root is your home directory, is the config dir, or contains the config dir. A project push that fails stops the verb there, before the note is filed and the config repo is synced.
  • --note <file> copies that file to memory/<slug>/handoff.md and rewrites a single line in memory/<slug>/MEMORY.md: - [Handoff](handoff.md) — <date> · <summary>. The summary is the note's first non-empty, non-heading line, cut to 80 characters. The line is replaced rather than piled up: there is only ever one current handoff.

The config repo is then committed as sync: <slug> <date> <time> and pushed. The last line names the command for the other machine: arrive with: npx claude-keep pull.

The manifest

claude-keep.json sits at the root of the config dir and holds the machine state a git repo cannot carry. Three root keys are understood. Any key whose name starts with _ is a comment and is ignored, including the _examples block the template ships.

| Key | Shape | Read by | | ------- | ------ | --------------- | | mcp | object | apply | | deps | array | doctor | | steps | array | apply, pull |

mcp is keyed by server name, matching ^[A-Za-z0-9][A-Za-z0-9_-]*$. Each server needs a type of http, sse or stdio. http and sse need url; stdio needs command, and may add args (array of strings) and env (object of strings).

deps entries need name and probe. probe is a shell command line; exit 0 means present. hint is the install line shown when it fails, either a string or an object keyed by platform.

steps entries need name and run, a shell command line run from the config dir unless cwd says otherwise. when holds exactly one guard: missing skips the step when that path exists, missingBin skips it when that command is already on PATH. A step with no when runs on every apply, so keep it idempotent.

Paths expand ~, ${HOME} and ${CLAUDE_CONFIG_DIR} in an MCP server's command, args and env, and in a step's cwd and when.missing. Those two may point outside the config dir; that is your call.

OS names are Node's process.platform values: win32, linux, darwin. The os key means two things by shape. On deps and steps it is an array that filters: the entry is only used on those platforms. On an mcp server it is an object keyed by platform whose fields override the defaults.

{
  "mcp": {
    "notes": {
      "type": "stdio",
      "command": "~/projects/my-tool/.venv/Scripts/my-tool.exe",
      "args": ["--stdio"],
      "os": { "linux": { "command": "~/projects/my-tool/.venv/bin/my-tool" } }
    }
  },
  "deps": [
    {
      "name": "my-tool",
      "probe": "my-tool --version",
      "hint": { "win32": "winget install Example.MyTool", "linux": "sudo apt install my-tool" }
    }
  ],
  "steps": [
    {
      "name": "build my-tool",
      "run": "npm ci && npm run build",
      "cwd": "${CLAUDE_CONFIG_DIR}/tools/my-tool",
      "when": { "missing": "${CLAUDE_CONFIG_DIR}/tools/my-tool/dist/index.js" }
    },
    { "name": "get my-tool", "run": "npm i -g my-tool", "when": { "missingBin": "my-tool" } }
  ]
}

What is versioned and why

The .gitignore that init writes has three groups. Most patterns are anchored at the root with a leading /, so a project nested inside the config dir keeps its own files. The exceptions are deliberate: the Python bytecode rules and **/node_modules/ match at any depth.

| Group | Examples | Why it is ignored | | ----- | -------- | ----------------- | | Runtime and sensitive | /.credentials.json, /settings.local.json, /history.jsonl, /projects/, /sessions/, /shell-snapshots/, /todos/, /statsig/, /telemetry/, /ide/, /paste-cache/ | Credentials, transcripts and per-machine scratch. None of it is authored, some is private, and a session means nothing on another machine. | | Python bytecode | __pycache__/, *.pyc | Generated, and noisy in every diff. | | Heavy and reinstallable | /plugins/, **/node_modules/ | Fetched, not authored. apply reinstalls plugins from settings.json; a package manager reinstalls the rest. |

Two consequences:

  • settings.json is versioned. Never put a credential in its env block: no API key, no auth token, no cloud access key. It is committed in the clear, and doctor warns about it. Use apiKeyHelper, or put the value in settings.local.json, which is ignored.
  • memory/ is the versioned memory home. link points each project's autoMemoryDirectory at a directory under it, so memory travels with the config repo.

Trust model

claude-keep gives your config repo the same authority you give your own shell. apply runs the steps[].run command lines from claude-keep.json through a shell and registers MCP stdio servers that Claude Code will later launch. doctor runs your deps[].probe lines the same way. Beyond the manifest, pull writes settings.json, skills/, agents/, commands/ and any hooks/ tree straight into your Claude Code home, so a change there takes effect the next time Claude Code starts, whether or not you run apply. The hooks block in settings.json matters most: those are shell commands Claude Code runs on its own at session events, so a hook that arrives through pull runs without you invoking anything. Use claude-keep only with a private repo you control, review what pull brought in before running apply, and treat a manifest someone sent you exactly as you would treat a shell script they sent you. Everything in the repo is committed in the clear, so keep API keys and tokens out of settings.json env and out of MCP env blocks.

Remote Control

claude-keep is not a replacement for Claude Code's Remote Control, which streams a live session between devices. Syncing files through git is a different job, and the two can be used together.

Not in scope

  • Plugins and marketplaces are declared in settings.json, not in the manifest.
  • Removal is manual. Dropping a manifest entry does not unregister what apply already added.
  • The arriving-command display in pull is a line-based scan of the diff. A JSON value sitting on its own line, away from its key, will not be shown.

Development

npm test                        # node --test, no dependencies to install
node --test test/leak.test.mjs  # the privacy gate on its own

test/leak.test.mjs scans every tracked and untracked file in the repo for private identifiers that must never reach a public package: local usernames, home directory paths, personal email addresses, and internal project or client names. A nested repository, whether a submodule or an untracked clone, is left to its own gate. It also asserts that its own patterns still match their samples and still ignore a list of innocent lookalikes, so a typo cannot silently disable it. Use placeholder names such as alice and ~/projects/my-tool in anything you add.

CI runs the full suite on ubuntu-latest and windows-latest, on Node 20 and Node 24, plus the leak test as a separate named job.

License

MIT