Auto-detect a repository's stack and launch Claude Code with only the plugins that repo actually needs — so a Python service doesn't pay the context cost of your frontend plugins, and vice-versa.
You keep one login, one memory store, one set of installed plugins — cca
runs on your regular ~/.claude config and just adds --plugin-dir flags for
the stack it detects.
$ cd ~/code/my-api # a Python project
$ cca
cc-autoconfig: stack=[python] profile=python
# ...Claude starts with the Python plugin dir appended via --plugin-dir
$ cd ~/code/my-site # a SolidJS/TS project
$ cca
cc-autoconfig: stack=[node, typescript, frontend] profile=node+typescript+frontend
# ...Claude starts with the typescript-lsp and frontend-design plugin dirs appendedEvery enabled plugin loads its skill descriptions into the system prompt on
every turn (often several thousand tokens each). Claude Code's
enabledPlugins is global-only — per-project enabledPlugins is not
supported — so there's
no built-in way to say "load the Python tooling only in Python repos."
cca sidesteps that with claude --plugin-dir <path>: it detects the repo's
stack, resolves each matched plugin's newest cached payload directory under
~/.claude/plugins/cache/, and execs claude with one --plugin-dir flag per
resolved plugin, on top of whatever you already have enabled globally. It
never touches ~/.claude itself — no separate config dir, no separate login,
no writes at all. Login, memory, history, and installed plugins are exactly
the shared, ambient ~/.claude you'd get from running plain claude.
Works on Linux, macOS, and Windows. Needs Node.js (you already have it if you
run Claude Code) and git. No runtime dependencies.
npm install -g cc-autoconfig # or: npm i -g github:stanlrt/cc-autoconfigcca is now on your PATH. The first run creates
~/.config/cc-autoconfig/config.json from a generic template — edit it to match
the plugins you've installed (claude plugin list shows their name@marketplace
ids), then use cca anywhere you'd type claude.
From source
git clone https://github.com/stanlrt/cc-autoconfig ~/code/cc-autoconfig
cd ~/code/cc-autoconfig && npm install -g . # or ./install.sh on UnixNothing personal lives in this repo — your plugin choices, private plugins, and
any tokens stay in ~/.config/cc-autoconfig/config.json, which is never
committed here. See examples/ for starter configs.
Versions before 0.2.0 worked by materialising a mirrored CLAUDE_CONFIG_DIR
profile per stack under ~/.cache/cc-autoconfig/profiles/. That machinery is
gone: cca now runs directly on your shared ~/.claude and injects
--plugin-dir flags instead of switching config dirs.
- Old profile directories under
~/.cache/cc-autoconfig/profiles/are no longer read or written bycca— they're safe to delete (rm -rf ~/.cache/cc-autoconfig/profiles). - If a previous version of
ccawrote plugin toggles into your base~/.claude/settings.json'senabledPlugins, that global map is still respected byclaudeas-is — anything listed there loads in every session regardless of stack. Prune it down to only what you truly want everywhere; letcca's per-stack rules handle the rest. disableMcpand per-profileCLAUDE_CONFIG_DIRare gone; there is nothing to migrate for MCP servers — they were already shared and still are.
~/.config/cc-autoconfig/config.json:
{
"always": ["my-workflow-plugin@my-marketplace"],
"rules": [
{
"name": "python",
"match": { "files": ["pyproject.toml", "requirements.txt", "*.py"] },
"plugins": ["pyright-lsp@claude-plugins-official"]
},
{
"name": "frontend",
"match": {
"files": ["package.json"],
"grep": { "file": "package.json", "pattern": "react|solid-js|vue|svelte" }
},
"plugins": ["frontend-design@claude-plugins-official"]
}
]
}always— plugin ids (name@marketplace) passed as--plugin-diron every launch.rules[]— each matched rule contributes itsplugins(union). A rule matches when all the conditions it declares hold:match.files— matches if any listed name exists at the repo root. Supports*/?globs (e.g.tsconfig.*.json).match.grep—{ "file", "pattern" }; the file must exist and its contents match the regex (use it to tell a frontend app from a backend one).match.path— regex tested against the repo's absolute path (e.g."/work/acme/"to load a client's plugins only under that tree).- A rule with an empty/omitted
matchalways matches — handy as a base rule.
Each plugin id must already be installed (claude plugin install …) so it
has a cached payload under ~/.claude/plugins/cache/<marketplace>/<plugin>/;
cca only resolves and points at that cache, it never installs anything. A
plugin id that isn't installed is skipped with a warning, not a hard error.
The "profile" shown in cca's log line is just the sorted set of matched rule
names joined with + (base if nothing matches) — it's informational only,
not a directory or config dir.
Detection is not limited to the repo root. cca walks the tree up to
scanDepth levels (default 4), skipping heavy dirs (node_modules, .git,
dist, .venv, target, …), and a rule matches if its markers appear
anywhere in that subtree. Every matching rule contributes, so a repo with a
TS frontend in apps/web/ and a Python service in services/api/ resolves to
node+python+typescript and loads both toolchains' plugin dirs.
match.grep likewise scans every file with the given name (e.g. every
package.json in the tree).
Prefer strong project markers (package.json, pyproject.toml, go.mod) over
loose file globs like *.py in rules — the latter would flip a whole repo's
stack on a single stray script. Tune breadth with a top-level "scanDepth": N.
CCA_DRY=1 cca # prints the resolved plugin dirs (one per line), doesn't launch ClaudeOn each launch cca:
- Resolves the repo root (
git rev-parse --show-toplevel, else$PWD). - Evaluates your rules against it, collecting the union of matched
plugins(plusalways). - For each matched
plugin@marketplace, finds its newest cached version dir under~/.claude/plugins/cache/<marketplace>/<plugin>/(semver-aware, with amtimefallback for sha-versioned or unparsable dirs) and turns it into a--plugin-dir <path>flag. A plugin with nothing cached is skipped with a warning. execsclaudewith those flags appended to whatever arguments you passedcca, and sets the child'sCLAUDE_CONFIG_DIRto the shared base (~/.claude, orCCA_BASE). This is set explicitly — not left to inherit — so a staleCLAUDE_CONFIG_DIRalready in your environment (for example from an older cca that materialised per-profile dirs, or a nested launch) can't split your login back apart. Everyccasession therefore lands on the same config dir and shares one login, exactly like plainclaude.
cca is entirely read-only against ~/.claude: it reads the plugin cache and
your config file, and never writes, links, or mutates anything under your base
config. Login, .credentials.json, memory, history, and installed_plugins.json
are exactly what plain claude would see — because it's the same directory.
Run cca in as many repos at once as you like. Because there's no per-stack
config dir or materialisation step, two cca sessions in different repos are
exactly as independent (or as shared) as two plain claude sessions in
different terminals — same login, same memory, same installed-plugins state,
no extra locking or contention introduced by cca.
No filesystem tricks (no symlinks, no junctions) are needed anymore — cca
only reads the plugin cache and spawns claude. CI runs the test suite on
Linux, macOS, and Windows.
CCA_CONFIG— path to the config file (default~/.config/cc-autoconfig/config.json).CCA_BASE— base config dir to resolve the plugin cache under, and the value forced into the child'sCLAUDE_CONFIG_DIR(default~/.claude). Set this if you intentionally run a non-default config dir.CCA_DRY— when set, print the resolved--plugin-dirpaths and exit instead of launchingclaude.
- A plugin must be installed (
claude plugin install …) for a rule to resolve it to a--plugin-dir;ccaonly points at what's already cached, it doesn't install anything. - Detection walks up to
scanDepthlevels (default 4). Very deeply nested package roots may need a higherscanDepth; conversely, lower it to speed up huge trees. - Plugins enabled globally in
~/.claude/settings.json'senabledPluginsload in everyccasession regardless of stack —ccaonly adds plugin dirs on top, it doesn't disable anything. Keep that map limited to plugins you genuinely want everywhere; let rules handle the rest.
npm install # dev deps (eslint, prettier) — the shipped tool has none
npm test # node --test (detection, plugin version resolution)
npm run lint # eslint
npm run format # prettier --writeTests are hermetic (temp dirs via CCA_BASE) and never touch your real
~/.claude. CI runs them on Linux, macOS, and Windows across Node 18/20/22.
MIT — see LICENSE.