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

ensure-claude-plugins

v1.0.1

Published

Install the Claude Code plugins a repository declares in .claude/settings.json — silently, idempotently, never failing the caller.

Downloads

517

Readme

ensure-claude-plugins

Make a repository's declared Claude Code plugins actually installed on this machine — silently and idempotently.

It reads the repo's committed .claude/settings.json, and for every enabled entry it shells out to the claude CLI to add the marketplace and install the plugin. A gitignored stamp file makes every later run a no-op. It never fails its caller, so it can sit in a prepare/postinstall script without ever breaking npm install.

$ npx ensure-claude-plugins
Setting up 3 Claude Code plugin(s) at local scope…
  ✔ context7@context7-marketplace
  ✔ efp@expofp
  ✔ nx@nx-claude-plugins
Claude Code plugins ready. Restart Claude Code to pick them up.

$ npx ensure-claude-plugins      # second run
$                                # silent no-op

Security, up front, because this runs during npm install: it installs only what the repository's own committed .claude/settings.json declares, adds no registry or index of its own, and does nothing a developer running claude plugin install by hand wouldn't do. Details in Security.

Why this exists

.claude/settings.json can declare enabledPlugins and extraKnownMarketplaces, and the official docs say teammates then get prompted to install. Verbatim, from Discover plugins § Configure team marketplaces (fetched 2026-07-29, archived in docs/reference/discover-plugins.md):

Team admins can set up automatic marketplace installation for projects by adding marketplace configuration to .claude/settings.json. When team members trust the repository folder, Claude Code prompts them to install these marketplaces and plugins.

As of Claude Code v2.1.195, this install step applies on every path that loads plugins. A plugin that only the project's .claude/settings.json enables, and that comes from an external source such as a GitHub repository or npm package, doesn't load until the team member installs it. Until then, Claude Code reports the plugin as not installed and shows the claude plugin install command to run.

Measured on Claude Code v2.1.220 (2026-07-29), this is what actually happens. A session started in a checkout with all three plugins declared but none installed prompts to trust the folder, and never prompts to install. /plugin reports:

 ❯ context7 (project)
    Plugin "context7" not cached at /Users/…/.claude/plugins/cache/context7-marketplace/context7/1.0.2
    Run /plugin to refresh the plugin cache

 ✘ nx (project)
    Plugin "nx" is enabled in project settings but isn't installed here
    Run `claude plugin install nx@nx-claude-plugins --scope project` to install it for this project

 ✘ efp (project)
    Plugin "efp" is enabled in project settings but isn't installed here
    Run `claude plugin install efp@expofp --scope project` to install it for this project

So the first sentence of the docs does not hold; only the second does. Every developer must run the install commands by hand, in every checkout. Worse, the command Claude Code suggests uses --scope project, which rewrites the committed .claude/settings.json — see Scopes. And the claude plugin CLI (checked on v2.1.220) has no bulk command that reads the project settings: no sync, no install --all.

This package automates exactly that gap, and nothing else. The claim is dated on purpose — Claude Code moves fast. If it ships real auto-install, see Retirement plan.

Install

Add it as a devDependency and run it from prepare (works with npm, pnpm and yarn):

{
  "devDependencies": { "ensure-claude-plugins": "^1.0.0" },
  "scripts": { "prepare": "ensure-claude-plugins" }
}

Already using husky? Chain it:

{ "scripts": { "prepare": "husky && ensure-claude-plugins" } }

Install it as a devDependency; don't put a bare npx ensure-claude-plugins in prepare. The tool's defining property is that it can never fail its caller, and a bare npx in an install hook adds an unpinned registry fetch inside npm install — a new failure mode for the one script that must not have any. npx ensure-claude-plugins stays the documented one-off for ad-hoc use in a checkout that doesn't have it installed.

Requirements: Node ≥ 20. The claude CLI on PATH — if it isn't, the tool says so in one line and exits 0.

Usage

ensure-claude-plugins [options]

| flag | behaviour | | ------------------------------ | ------------------------------------------------------------------------------------------------------------- | | --scope user\|project\|local | Install scope, default local — see Scopes | | --force | Ignore the stamp file, reinstall everything | | --check | Report what is missing, install nothing, write nothing, exit 1 if anything is missing — for CI | | --dir <path> | Project root, default the current directory | | --quiet | Suppress the success lines; failures still print | | --help, --version | The usual |

Normal runs are a silent no-op when CI is set, when ENSURE_CLAUDE_PLUGINS_SKIP=1, or when the claude binary isn't on PATH. --check deliberately ignores all three — its whole purpose is to run on CI.

Scopes

The claude CLI defaults to user scope. This tool defaults to local, and the difference matters more than it looks:

| scope | recorded in | consequence | | ----------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | user | ~/.claude/settings.json | The plugin's skills, commands and hooks load into every Claude Code session on the machine. A repo-specific hook then fires in repositories that have nothing to do with it. | | project | .claude/settings.json (committed) | Confined to the project — but the CLI rewrites the committed file in its own formatting while installing, even when the change is a semantic no-op. Every developer gets a dirty working tree from npm install. | | local (default) | .claude/settings.local.json (gitignored) | Confined to the checkout, invisible to git. |

The project rewrite is not hypothetical. Measured diff on a repo whose settings were Prettier-formatted, after an install whose enabledPlugins entry was already present (a semantic no-op):

   "permissions": {
-    "deny": ["Read(docs/_old/**)"]
+    "deny": [
+      "Read(docs/_old/**)"
+    ]
   },

If a previous run installed at a different scope, the tool installs at the new scope and then prints the exact claude plugin uninstall <id> --scope <previous> commands for the copies left behind. It never uninstalls anything itself — at user scope that set is shared with every other repo on the machine, where a plugin may have been wanted on its own merits.

What to gitignore

.claude/.plugin-setup
.claude/settings.local.json

.plugin-setup is this tool's stamp file; settings.local.json is where the claude CLI records local-scope installs (and where your own local overrides live).

Forgetting this is the one mistake that bites quietly: a committed stamp tells every fresh checkout that the plugins are already installed, so nothing installs and nobody is told why. So a run that installs something asks git whether these files are ignored, and prints the lines to add — plus git rm --cached if one is already committed. It never edits .gitignore itself; that file is yours, and a tool that rewrites committed files from inside pnpm install is the problem it set out to avoid. Runs that install nothing skip the check entirely, so the everyday no-op stays silent.

--check in CI

--check verifies that everything the repo declares is actually installed on the machine it runs on — for a container image or self-hosted runner that is supposed to have the plugins baked in:

- name: Claude Code plugins present
  run: npx ensure-claude-plugins --check

It installs nothing, writes nothing, exits 1 listing whatever is missing, and runs even where CI would make a normal run a no-op.

How it works

  • Reads <dir>/.claude/settings.json, then merges <dir>/.claude/settings.local.json over it — local wins key by key, including an explicit false that disables an entry the committed settings enable.
  • For every <plugin>@<marketplace> id enabled: one `claude plugin marketplace add
  • Writes .claude/.plugin-setup with one line per installed plugin: sha256(id|source|scope) truncated to 12 hex chars. Per-plugin, so one plugin nobody can install (a private marketplace, no credentials) retries alone instead of dragging the whole set through a retry-and-report on every run. Rotating a marketplace URL or changing scope changes the fingerprint and re-provisions everyone automatically.
  • Never hangs, never fails the caller: stdin ignored, output captured (shown only for the plugin that failed), GIT_TERMINAL_PROMPT=0, 120 s timeout per CLI call, and exit 0 on every path except --check.

Security

Claude Code plugins execute code with your privileges — Anthropic's own docs tell you to install plugins only from sources you trust. This tool does not change that calculus:

  • It installs only what the repository's own committed .claude/settings.json (plus your own gitignored .claude/settings.local.json) declares.
  • It has no registry, index or marketplace of its own, and zero runtime dependencies.
  • It does nothing a developer following the repo's README and running claude plugin install by hand wouldn't do — it just does it consistently.

Trusting a repo's declared plugins is a decision you make when you adopt the repo; this tool only executes that decision.

Troubleshooting

A GitHub-sourced plugin fails with Permission denied (publickey)

A plugin whose marketplace entry is {"source": "github", "repo": "owner/repo"} is cloned by the claude CLI over SSH ([email protected]:owner/repo.git) — even for a public repo, and even though the marketplace itself was just fetched over HTTPS. On a machine with no GitHub SSH key the install fails outright, and the CLI has no flag to pick the transport.

This tool fixes that by injecting url.https://github.com/[email protected]: through git's environment config into the spawned CLI only — your own git config is untouched. The one trade-off: a plugin hosted in a private GitHub repo then needs an HTTPS credential helper (gh auth setup-git) where an SSH key would otherwise have worked.

Plugin "x" not cached at … Run /plugin to refresh the plugin cache

Plugin content lives at ~/.claude/plugins/cache/<marketplace>/<plugin>/<version> and is shared across scopes and projects. Uninstalling the last install of a plugin deletes that directory, and any other install record still pointing at it then reports not cached. Reinstalling repairs it: claude plugin install <id>, or ensure-claude-plugins --force in the affected checkout.

Retirement plan

This package exists to work around a gap: Claude Code documents install-on-trust but doesn't do it, and its CLI has no bulk install that reads the project settings. If Claude Code ships that for real, this package will be deprecated with a pointer to the built-in mechanism, not defended.

License

MIT © ExpoFP