ccshelf

One plugin shelf. A loadout per terminal.

ccshelf starts Claude Code with only the plugins, skills and MCP servers that a named profile asks for. Run ccshelf run sre in one terminal and ccshelf run frontend in another, at the same time.

Pick a loadout

Profile for this terminal
  • always onaudit-loggerprotected
  • ondesign-kit@acme/web
  • ondocs-writer@acme/platform
  • maskedpartner-linter@acme/platform
  • maskedrelease-notesdeprecated
  • maskedseo-tools@acme/seo
  • maskedsre-kit@acme/sre

Plugins

  • onfigmaorg registry
  • not namedpagerduty-roorg registry
  • hiddenclaude.ai connectorsclaudeai_connectors

MCP servers and connectors

frontend: 2 plugins on, 1 protected plugin kept on, 4 masked. 1 MCP server.

terminalprofile: examples/org-data-repo/profiles/frontend.toml
warn: plugin audit-logger@acme is protected and stays enabled
claude --settings ~/.cache/ccshelf/settings-1e4f76655e7b7ee774f743a24c52b834.json --mcp-config ~/.cache/ccshelf/mcp-1d38416511d8512691a7aabca160edfa.json --strict-mcp-config --append-system-prompt-file ~/.cache/ccshelf/prompt-23ddf2648cfd6ce7e3640f22b7c15c04.md --effort high
the settings file it namessettings-1e4f7665…json
{
  "disableClaudeAiConnectors": true,
  "enabledPlugins": {
    "design-kit@acme": true,
    "docs-writer@acme": true,
    "partner-linter@acme": false,
    "release-notes@acme": false,
    "seo-tools@acme": false,
    "sre-kit@acme": false
  },
  "env": {
    "CCSHELF_PROFILE": "frontend",
    "FIGMA_TOKEN_REF": "op://dev/figma/token"
  },
  "model": "opus"
}

Real ccshelf dry-run output from commit c2a679d, fictional acme org, fake claude. How it's made

Status: implemented. Stable releases are available for macOS, Linux and Windows. CI tests all six amd64 and arm64 targets. Open source (MIT). Unofficial: not affiliated with Anthropic.

Not every terminal needs every plugin.

An organization with around fifty internal plugins gets a crowded session. Skills for incident response sit next to skills for CSS, and nobody can say which plugin a task should use. Claude Code enables plugins through settings, so the set you choose is usually the set every session starts with.

ccshelf adds two things. A launcher that chooses the set per terminal, and a catalog that answers "what exists, which should I use, who owns it" from metadata the organization already keeps in git.

What we measured so far: in one run on one real setup, hiding MCP servers and claude.ai connectors through the generated settings cut about 5.7k input tokens (19%, from 29.7k to 24.0k). That is a single run, not a benchmark. Whether a smaller set also improves how often the right skill fires is the open Phase 0 routing evaluation. Details in docs/research/stage0.md.

How it works

A profile never installs anything. It switches off what it does not list, for one session.

One shared store

  • installed plugins
  • login
  • session history
  • memory

Your normal Claude Code config. ccshelf never writes it.

ccshelf

  • resolve the profile
  • mask what it does not list
  • validate the result
  • write settings-<hash>.json

Files are named by content hash and written to its own cache.

Terminal A

claude --settings a1…

sre loadout

Terminal B

claude --settings 7f…

frontend loadout

  1. Resolve. Read the profile, follow its extends, and list what is installed with claude plugin list --json. ccshelf reports plugins that a profile names but you lack, with the install command. Nothing is installed silently.
  2. Compile. Write a settings file that sets enabledPlugins to false for every installed plugin the profile does not list. ccshelf regenerates it on each run, so a plugin you install tomorrow does not leak in. The same file can hide claude.ai connectors and named MCP servers.
  3. Validate. Claude Code ignores an invalid settings file without a message, so ccshelf checks the generated JSON against a closed schema before every launch.
  4. Start. On macOS and Linux ccshelf replaces itself with claude using exec, so job control and exit codes are exactly those of plain claude. On Windows it starts claude and waits.

Shared by every profile

Login, session history, auto memory, CLAUDE.md and the plugin cache. There is one copy of each plugin. Profiles only change what is enabled. Sessions are not tagged by profile, so /resume lists sessions from every profile. Resuming one under a different profile is untested: its recorded prompt and transcript can still describe the other profile's skills.

Not hidden by a profile

A repository's own .claude/skills/, .claude/agents/ and .claude/settings.json still apply. Its .mcp.json does too, unless the profile sets strict.

The catalog

Metadata lint, a generated static site and catalog.json, and local search, all from the organization's marketplace repository.

Each plugin gets a small sidecar file, catalog/plugins/<name>.toml, with an owner, a status, when to use it and when not to, what it overlaps with and a review date. ccshelf lint fails the pull request when a required field is missing or stale. ccshelf catalog build turns the same data into a site. ccshelf search and ccshelf doctor read it locally.

To start, ccshelf catalog init [dir] sets up a new org data repo. It can also add the missing pieces to an existing marketplace repository without changing a file that is already there (unless you pass --force, which saves a backup first). It prints a plan first (create, skip-exists, needs-merge). Then it writes the configuration, a sidecar stub per plugin, CODEOWNERS, pinned workflows and a README. It leaves placeholders that ccshelf lint reports as warnings until someone fills them in. There are no built-in profiles. --dry-run writes nothing.

Mockup of generated output, built from the fictional acme data in examples/org-data-repo. It illustrates the data, not a screenshot of the real site.
  • audit-logger@acme

    active
    Use for
    compliance audit trail
    Avoid for
    local debugging logs
    Owner
    @acme/platform
    Review by
    2027-06-01

    Protected in the org config: never masked by any profile.

  • design-kit@acme

    active
    Use for
    building UI components, design system reviews
    Avoid for
    backend services
    Owner
    @acme/web
    Review by
    2027-03-01
  • docs-writer@acme

    active
    Use for
    writing technical documentation, API references, release notes
    Avoid for
    marketing copy
    Owner
    @acme/platform
    Overlaps with
    release-notes
    Review by
    2027-04-01
  • partner-linter@acme

    experimental
    Use for
    style and policy linting
    Avoid for
    performance profiling
    Owner
    @acme/platform
    Overlaps with
    sre-kit
    Review by
    2027-02-01
  • release-notes@acme

    deprecated
    Use for
    drafting release notes
    Avoid for
    new projects
    Owner
    @acme/platform
    Use instead
    docs-writer
    Review by
    2027-01-15
  • seo-tools@acme

    experimental
    Use for
    on-page SEO audits, sitemap review
    Avoid for
    paid search
    Owner
    @acme/seo
    Review by
    2027-01-31
  • sre-kit@acme

    active
    Use for
    incident response, postmortems
    Avoid for
    routine deploy checks
    Owner
    @acme/sre
    Overlaps with
    partner-linter
    Review by
    2027-02-15

The site is a plain static directory. Publish it to GitHub Pages or any internal static host. Make sure that the people who can see the site are the people who may see the plugin list. A pull request that touches only a sidecar needs review from the platform owners through CODEOWNERS, so authors write the metadata and the platform team approves it.

Trust and security

A profile is a loading filter, not a security boundary. These five rules keep a shared profile from becoming a way to run code. The docs number them SR1 to SR5.

Closed profile schema implemented
The generator writes only an allowlist of settings keys. A profile can never write permissions, hooks, apiKeyHelper or endpoint settings, and its environment names pass an allowlist. Protects against a profile that redirects your prompts to another endpoint or runs code at start.
Trust the resolved closure, pinned by commit SHA implemented
Shared profiles need explicit trust. The trust hash covers the profile, its parents, the MCP registry entries it uses, its prompt file and the source commit. --yes never accepts trust, and CI fails closed. Project profiles are off by default. A source can follow a branch only through an explicit setting, and every new commit on it needs trust again. Protects against a changed tag, branch or registry entry slipping in after you approved.
No shadowing, protected plugins implemented
A project profile can never shadow another source's profile. The org config can list plugins and MCP servers (audit, secret scanning) that no profile may mask. Protects against a repository or a profile switching off a control the organization requires.
Private, verified cache implemented
Cache directories are owner-only (mode 0700, files 0600) and created without following symlinks. ccshelf re-hashes each content-addressed file before reuse. It never writes secrets to disk or to argv, and it redacts output. Protects against another local user, or a stale file, changing what claude receives.
Hardened CI and releases signed release available
The Action is pinned by full commit SHA and embeds the expected checksum of the binary it downloads. cosign signs the release checksums. Archives have GitHub build provenance and an SBOM. Workflows use permissions: {} and least privilege. Protects against a tampered binary or a compromised action in your pipeline.

Read the full security design and the security policy.

Platforms, GitHub and managed policy

The test suites pass on native macOS, Linux and Windows CI runners, across amd64 and arm64. Only macOS has driven the real claude. GHE Server is designed for but untested.

Build targets and how far each is exercised
Six build targets
Systemamd64arm64
macOStestedtested
Linux (WSL counts)testedtested
Windows (native)CI testedCI tested

GitHub.com, GHE Cloud, GHE Server

No host is hard-coded. Git operations shell out to your own git, so your credential helper and SSH config apply. On GHE hosts, use git URL sources. The logic lives in the binary and the GitHub Actions workflow is a thin wrapper, so the same commands run in another CI or locally. Third-party actions such as setup-go need to be mirrored or allowed on GHE Server.

Managed policy

ccshelf asks what Claude Code allows and degrades one feature at a time. It never bypasses policy. Plugins forced on by policy cannot be masked and are listed as "always on by policy". A profile's on_blocked setting decides whether a blocked feature warns or stops the launch, and ccshelf doctor --policy prints the capability matrix.

Install and quick start

Install the latest release with the script for your platform. It verifies the download before installing. You can also build from source. New here? Follow the getting started guide.

Linux and macOS

curl -fsSL https://github.com/yorch/ccshelf/releases/latest/download/install.sh | sh

Supports amd64 and arm64. WSL uses the Linux installer. Installs into ~/.local/bin, without sudo, and needs curl. To pin a version and require cosign signature verification, run curl -fsSL https://github.com/yorch/ccshelf/releases/latest/download/install.sh | sh -s -- --version v0.2.0 --require-signature. SHA-256 verification against that release's checksums.txt is always required. The installer checks the signature when cosign is installed.

Windows

irm https://github.com/yorch/ccshelf/releases/latest/download/install.ps1 | iex

Supports amd64 and arm64 in Windows PowerShell 5.1 or PowerShell 7. Installs into %LOCALAPPDATA%\Programs\ccshelf. Add -AddToPath to update your user PATH. To pin a version and require cosign, run & ([scriptblock]::Create((irm https://github.com/yorch/ccshelf/releases/latest/download/install.ps1))) -Version v0.2.0 -RequireSignature. The binaries are not notarized or Authenticode-signed yet. ccshelf is unofficial and not affiliated with Anthropic. See the README for all options, manual verification and uninstall instructions.

Build from source

git clone https://github.com/yorch/ccshelf
cd ccshelf
go build -o ccshelf ./cmd/ccshelf
./ccshelf version

Needs the Go version in go.mod or newer. Put the binary on your PATH, or run go install ./cmd/ccshelf from the checkout to install into $(go env GOPATH)/bin. ccshelf update leaves a go install copy to Go and prints the command to run.

Try it

ccshelf new sre-night --from base --plugin sre-kit@acme
ccshelf ls
ccshelf show sre-night
ccshelf dry-run sre-night
ccshelf run sre-night
ccshelf run --account personal sre-night --resume

Create a personal profile, list profiles from every source, see the resolved plugins and trust closure, print the exact claude command without running it, then run it. The last line shows the argument order: ccshelf flags first, then the profile name, then whatever goes to claude.

Every command works with flags alone. On a terminal, ccshelf asks for missing values and prints the equivalent command line, so you can script it later.

Missing plugins

ccshelf run --install-missing sre-night
ccshelf run --install-missing --yes sre-night
ccshelf run --install-missing=sre-kit@acme --yes sre-night

A profile chooses plugins. It does not install them. When a plugin of the profile is not installed, ccshelf run warns and prints the /plugin install command. With --install-missing, ccshelf prints claude plugin install --scope user for each missing plugin, asks you to confirm, and installs them. --yes skips the question. --install-missing=id,id installs only the plugins that you name, and each one must be in the profile.

It installs only from a marketplace that you already added, and it never adds one. It runs after the trust check and not for a profile from a project source. Managed policy applies, because Claude Code does the install. With --yes, plugins install without a question, so use it only for profiles that you trust. dry-run never installs. See decision D-65.

For an organization

ccshelf lint
ccshelf compile --check
ccshelf catalog build

Run these in your org data repository, or through the reusable GitHub Action, which installs a checksum-verified release and runs one command. Start from the fictional template in examples/org-data-repo and read the Action guide.

Command reference

Read commands (ls, show, search, doctor, diff) accept --json. NO_COLOR and --no-color are honored, and --no-interactive turns prompts off. Every flag of every command is in the generated command reference.

Launcher

run <profile>
Start claude with the profile. Arguments after the name go to claude. Without a name, a picker appears on a terminal. --install-missing installs the plugins of the profile that are not installed.
dry-run <profile>
Print the exact claude command and run nothing.
show <profile>
The resolved plugins, MCP servers and trust closure.
ls
List profiles from every source.
new <profile>
Create a personal profile, optionally --from another.
trust <profile>
Show what a shared profile does and record trust, pinned to a commit.
All other commands, catalog commands and exit codes

More launcher commands

diff <a> <b>
Compare two profiles.
edit <profile>
Open the profile in $EDITOR. Needs a terminal.
init
Create the config, optionally from an org data repo URL. It only writes source entries and never trusts anything.
config
Show the configuration and add, pin or remove profile sources or change a setting without editing config.toml by hand. A write is confirmed on a terminal, keeps a .bak and never records trust.
account add|ls|rm
Name, list or forget a separate Claude Code identity and data directory (CLAUDE_CONFIG_DIR). rm keeps the directory. The global --account flag picks one for a command.
shell-init
Shell integration for bash, zsh, fish and PowerShell.
completion
Generate completions for bash, zsh, fish and PowerShell.

Catalog and organization

lint
Check marketplace entries, sidecars and profiles.
compile [--check]
Regenerate the profile-* bundle plugins, or fail if the committed ones differ.
catalog init [dir]
Set up a new org data repo, or add the missing files to an existing marketplace repo. Prints a plan and leaves existing files alone unless --force (which saves a .bak). --dry-run writes nothing.
catalog build
Build the static catalog site and catalog.json.
search <query>
Find plugins and profiles by what they are for.
recommend
Suggest profiles from the current directory. Rule-based, no LLM.
doctor [--policy]
Overlaps, unused or deprecated plugins in use, stale owners, policy shadowing and the capability matrix.
update [--check] [--rollback]
Replace this binary with the latest release after checking its SHA-256 and, if cosign is installed, its signature. --check only reports. --rollback restores the previous binary. Nothing updates by itself unless you set [update] mode.
version
Print the version. --version works too.

Exit codes

0
success
1
failure
2
usage error, including a missing value in a non-interactive run
3
blocked by policy
4
trust required
130
interrupted

The full reference, with every flag, is the command-line design in the repository.

Questions

Is this an Anthropic product?

No. ccshelf is an independent open-source project, unofficial and not affiliated with Anthropic. It starts the claude you already have installed. It does not proxy or intercept the conversation, and it has no telemetry.

Is a profile a security boundary?

No. A profile filters what loads. Profiles share your login, history and memory, and a repository's own settings still apply. Real enforcement of permissions and hooks comes only from Claude Code's managed settings. See the Claude Code documentation.

What happens under managed policy?

ccshelf works within it and never bypasses it. If policy blocks a flag or a settings key, ccshelf skips the affected feature with a warning, or stops the launch. The profile's on_blocked setting decides which. Policy-enabled plugins stay on and are reported as such. Exit code 3 means a launch was blocked by policy.

How do accounts fit in?

An account is a separate Claude Code identity and data directory (CLAUDE_CONFIG_DIR), a different axis from profiles. The account says who you are. The profile says what is active. ccshelf keeps a CLAUDE_CONFIG_DIR that you set and never unsets it. You can also name accounts: ccshelf account add work creates ~/.claude-work and prints the one-time login steps. Pick one with --account, the profile's account field or default_account. An explicit --account replaces a variable that is already set, with a warning. The other two never do. Each account has its own installed plugins and login. ccshelf never reads or copies credentials, and account rm never deletes the directory.

How do I update ccshelf?

Run ccshelf update. It downloads the latest release over HTTPS. It checks the archive against the release's checksums.txt (and, when cosign is installed, the signature of that file). It runs the new binary once to confirm its version. Then it replaces the old binary and keeps it as ccshelf.old for ccshelf update --rollback. ccshelf update --check only reports. ccshelf leaves a copy installed by Homebrew, Scoop, WinGet or go install to that tool, and prints the command to run. Nothing checks for updates by itself unless you opt in with [update] mode = "notify" (one line when a release exists) or "install" (a newer stable release of the same major version, or for 0.x, the same minor). Both act only in an interactive terminal, never in scripts or pipelines (use ccshelf update --yes there). CCSHELF_NO_UPDATE_CHECK=1 turns them off, and CI is never touched. If the latest release is older than yours, --force does not downgrade. --allow-downgrade does.

Does it change my Claude Code config?

No. It writes generated, content-addressed files to its own cache and never touches your settings.json, ~/.claude.json or the plugin cache.

Does a smaller set make Claude pick better skills?

Not shown yet. The routing evaluation that would show it is still open, and the one measurement so far covers token savings from hiding MCP servers and connectors only. The project's own kill criteria say what happens if the evidence does not support the launcher.

Where can I get the latest release?

Install with the platform-specific commands above, or choose an archive from the latest GitHub release. Releases include signed checksums and build provenance for macOS, Linux and Windows on amd64 and arm64.