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
- always onaudit-logger
- ondesign-kit
- ondocs-writer
- maskedpartner-linter
- maskedrelease-notes
- maskedseo-tools
- maskedsre-kit
Plugins
- onfigma
- not namedpagerduty-ro
- hiddenclaude.ai connectors
MCP servers and connectors
frontend: 2 plugins on, 1 protected plugin kept on, 4 masked. 1 MCP server.
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
{
"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
- Resolve. Read the profile, follow its
extends, and list what is installed withclaude plugin list --json. ccshelf reports plugins that a profile names but you lack, with the install command. Nothing is installed silently. - Compile. Write a settings file that sets
enabledPluginstofalsefor 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. - Validate. Claude Code ignores an invalid settings file without a message, so ccshelf checks the generated JSON against a closed schema before every launch.
- Start. On macOS and Linux ccshelf replaces itself with
claudeusingexec, so job control and exit codes are exactly those of plainclaude. On Windows it startsclaudeand 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.
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
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
-
docs-writer@acme
active- Use for
- writing technical documentation, API references, release notes
- Avoid for
- marketing copy
-
partner-linter@acme
experimental- Use for
- style and policy linting
- Avoid for
- performance profiling
-
release-notes@acme
deprecated- Use for
- drafting release notes
- Avoid for
- new projects
-
seo-tools@acme
experimental- Use for
- on-page SEO audits, sitemap review
- Avoid for
- paid search
-
sre-kit@acme
active- Use for
- incident response, postmortems
- Avoid for
- routine deploy checks
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,apiKeyHelperor 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.
--yesnever 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
claudereceives. - 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.
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
| System | amd64 | arm64 |
|---|---|---|
| macOS | tested | tested |
| Linux (WSL counts) | tested | tested |
| Windows (native) | CI tested | CI 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
claudewith the profile. Arguments after the name go toclaude. Without a name, a picker appears on a terminal.--install-missinginstalls the plugins of the profile that are not installed. dry-run <profile>- Print the exact
claudecommand 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
--fromanother. 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.tomlby hand. A write is confirmed on a terminal, keeps a.bakand never records trust. account add|ls|rm- Name, list or forget a separate Claude Code identity and data directory (
CLAUDE_CONFIG_DIR).rmkeeps the directory. The global--accountflag 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-runwrites 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.
--checkonly reports.--rollbackrestores the previous binary. Nothing updates by itself unless you set[update] mode. version- Print the version.
--versionworks 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.