A tiny shell wrapper that auto-loads any plugins living inside your project's .claude/plugins/ folder — no marketplace, no /plugin install, no settings.json edits.
Claude Code plugins are normally installed via a marketplace or by editing .claude/settings.json. For personal or team-internal plugins that live inside a repo, this is clunky — you want to git clone a project and have its plugins just work.
There's an undocumented-but-official CLI flag, --plugin-dir <path>, intended for development and testing. It loads a plugin from disk for the duration of the session. This wrapper detects every plugin under ./.claude/plugins/*/ and appends one --plugin-dir flag per plugin automatically — so claude, claude -c, claude --resume, etc. all pick them up with no extra typing.
Each plugin lives in its own folder and must contain a manifest:
.claude/plugins/
├── my-plugin/
│ ├── .claude-plugin/
│ │ └── plugin.json ← required manifest
│ ├── .enabled ← opt-in marker (see below)
│ ├── skills/
│ ├── agents/
│ ├── commands/
│ ├── hooks/
│ └── .mcp.json ← optional
└── another-plugin/
└── ...
Minimal plugin.json:
{
"name": "my-plugin",
"version": "0.1.0",
"description": "What it does"
}Add to ~/.zshrc (or wherever you keep shell functions):
claude() {
local -a plugin_args
local plugin_root="./.claude/plugins"
if [[ -d "$plugin_root" ]]; then
if [[ -n "$CLAUDE_PLUGINS" ]]; then
# Override: explicit allowlist wins (space-separated)
for name in ${=CLAUDE_PLUGINS}; do
[[ -f "$plugin_root/$name/.claude-plugin/plugin.json" ]] \
&& plugin_args+=(--plugin-dir "$plugin_root/$name")
done
else
# Default: load every plugin with a .enabled marker
for p in $plugin_root/*/.claude-plugin/plugin.json(N); do
[[ -f "${p:h:h}/.enabled" ]] \
&& plugin_args+=(--plugin-dir "${p:h:h}")
done
fi
fi
command claude "${plugin_args[@]}" "$@"
}Reload: source ~/.zshrc
Add to ~/.bashrc:
claude() {
local -a plugin_args=()
local plugin_root="./.claude/plugins"
if [[ -d "$plugin_root" ]]; then
if [[ -n "$CLAUDE_PLUGINS" ]]; then
for name in $CLAUDE_PLUGINS; do
[[ -f "$plugin_root/$name/.claude-plugin/plugin.json" ]] \
&& plugin_args+=(--plugin-dir "$plugin_root/$name")
done
else
shopt -s nullglob
for manifest in "$plugin_root"/*/.claude-plugin/plugin.json; do
local plugin_dir="${manifest%/.claude-plugin/plugin.json}"
[[ -f "$plugin_dir/.enabled" ]] \
&& plugin_args+=(--plugin-dir "$plugin_dir")
done
shopt -u nullglob
fi
fi
command claude "${plugin_args[@]}" "$@"
}Reload: source ~/.bashrc
type claude
# → claude is a shell functiontouch .claude/plugins/my-plugin/.enabled
git add .claude/plugins/my-plugin/.enabledNow anyone who clones this repo and uses the wrapper gets my-plugin loaded automatically.
rm .claude/plugins/my-plugin/.enabledCLAUDE_PLUGINS="wip-plugin" claudeCLAUDE_PLUGINS="foo bar" claude -cCLAUDE_PLUGINS=" " claude # space = non-empty but matches nothingcommand claude -c| Decision | Why |
|---|---|
Marker file (.enabled) as default |
Explicit opt-in avoids loading half-finished WIP plugins accidentally. Commit the marker to share defaults with teammates. |
Env var (CLAUDE_PLUGINS) as override |
Per-invocation flexibility — test one plugin in isolation without editing files. |
command claude at the bottom |
Bypasses the function itself; prevents infinite recursion. |
| Glob-based discovery | No hardcoded plugin list. Add a plugin folder, touch .enabled, done. |
Checks .claude-plugin/plugin.json exists |
Skips stray files/folders that aren't real plugins. |
--plugin-dir** is a dev/testing flag.** It's ephemeral — the plugin isn't registered in Claude Code's settings, it's just loaded for the session. That's exactly what we want for repo-local plugins, but means the UI might not show them under "installed plugins" the same way as marketplace installs.- Relative paths only. The wrapper uses
./.claude/plugins— it only activates when your CWD is the project root.cd project && claudeworks;claudefrom a sibling directory does not. - No lazy loading. All enabled plugins load at startup. If you have many heavy plugins, consider using the env var override instead of markers.
- Subshells and scripts don't inherit functions by default. If you call
claudefrom a shell script, eithersource ~/.zshrcinside it orexport -f claude(bash only).
If you want plugins registered in Claude Code's actual settings (persistent, visible in /plugin), add a marketplace entry to .claude/settings.json:
{
"extraKnownMarketplaces": {
"local-plugins": {
"source": { "source": "directory", "path": "./.claude/plugins" }
}
}
}Plus a .claude/plugins/.claude-plugin/marketplace.json:
{
"name": "local-plugins",
"owner": { "name": "you" },
"plugins": [{ "name": "my-plugin", "source": "./my-plugin" }]
}Then /plugin install my-plugin@local-plugins inside Claude Code (once per plugin, per project).
The wrapper sidesteps all of that, at the cost of the dev-flag caveat above. Pick based on whether you want "zero-config drop-in" or "first-class registered plugins."