Compose Claude Code sessions from named bundles of skills, subagents, hooks,
MCP servers, and settings. Switch bundles per claude process; your real
~/.claude/ is never touched.
Status: v0.1.0, beta. Solo-maintained, best-effort. Breaking changes possible while pre-1.0. Issues welcome.
npm i -g @vessux/umbel # global install — gives you the `umbel` binary
# or
npx -y @vessux/umbel --help # one-shot via npxRequires Node ≥ 18.17. Unix only (symlinks).
Note: the npm package is scoped (
@vessux/umbel) due to npm's name-similarity filter. The CLI binary is plainumbel, so the rest of these docs use that.
First, get some artifacts on disk (skills, agents, hooks, MCPs) — see
Installing artifacts. Then author a bundle at
~/.config/umbel/bundles/data-science.md:
---
name: data-science
description: Tools for data science work
skills: [local/pandas-cheatsheet, local/plotnine]
agents: [local/data-scientist]
mcps: [local/duckdb]
settings:
model: claude-opus-4-7
---Every artifact ref is <source>/<leaf> — bare names are rejected. Bundles
can also pull in hooks:; see
docs/bundles-spec.md for the full schema.
Launch Claude Code with that bundle:
umbel run data-science -- claudePin a bundle to a project so plain umbel run uses it:
umbel apply data-scienceumbel does not fetch artifacts for you — it composes what you already have on disk. Drop skills, agents, hooks, and MCPs into:
~/.config/umbel/skills/<source>/<leaf>/SKILL.md
~/.config/umbel/agents/<source>/<leaf>/AGENT.md
~/.config/umbel/hooks/<source>/<leaf>/HOOK.md
~/.config/umbel/mcps/<source>/<leaf>/MCP.md
<source> is any lowercase identifier you pick — typically the upstream org
name (superpowers, obra) or local for hand-authored content. It exists
so multiple upstreams with overlapping names can coexist; bundles always
reference artifacts as <source>/<leaf>.
Override the root with UMBEL_ARTIFACTS_DIR (default
$XDG_CONFIG_HOME/umbel, typically ~/.config/umbel).
Most community plugins are git repos with a skills/<leaf>/SKILL.md layout.
Symlink (or copy) each skill into a source-qualified namespace:
# Pick a namespace name — usually the upstream owner.
NS=upstream-name
REPO=/tmp/upstream-repo
git clone https://github.com/<owner>/<repo> "$REPO"
mkdir -p ~/.config/umbel/skills/$NS
ln -sf "$REPO"/skills/*/ ~/.config/umbel/skills/$NS/Then reference them in a bundle as $NS/<leaf>. Repeat for agents/,
hooks/, and mcps/ if the upstream ships them.
Caveat: umbel's
HOOK.md/MCP.mdfrontmatter is umbel-specific (seedocs/bundles-spec.md). Skills and agents drop in as-is; hooks and MCPs from upstreams using a different config shape need a small adapter file.
git clone https://github.com/obra/superpowers /tmp/superpowers
mkdir -p ~/.config/umbel/skills/superpowers
ln -sf /tmp/superpowers/skills/*/ ~/.config/umbel/skills/superpowers/Then in a bundle:
skills: [superpowers/brainstorming, superpowers/test-driven-development]Claude Code reads from one ~/.claude/ per user. Editing it to swap skill /
subagent / MCP setups between projects is friction. umbel lets you keep many
named bundles and pick one per claude process via --plugin-dir,
--settings, and --mcp-config. The bundle is compiled to a Claude Code
plugin layout in a cache dir on demand; your real ~/.claude/ stays untouched.
See docs/bundles-spec.md for the full design.
A bundle session does not see your everyday Claude Code skills, agents, or
plugins from ~/.claude/. Some surfaces still pass through, by design or by
quirk:
| Surface | Visible under a bundle? | How to control it |
|---|---|---|
~/.claude/skills, agents, plugins |
No — fully isolated | Nothing to do; --plugin-dir + --settings replace user-scope sources. |
~/.claude/ oauth, history, projects state |
Yes (shared on purpose) | Keeps you logged in across bundles. Bundles never write here. |
<project>/.claude/skills, agents |
Yes — baseline leak | Keep the dir empty, or move those skills into a project-scope bundle (see below). |
<project>/.mcp.json |
No, when bundle defines MCPs | Bundle adds --strict-mcp-config. Set mergeMcp: true to additive-merge. |
<project>/.mcp.json |
Yes, when bundle has no MCPs | The strict flag is only emitted alongside bundle MCPs. |
So if your goal is "don't show me anything other than what this bundle declares", you have to:
-
Author the bundle (user-scope or project-scope — bundles themselves are not the leak).
-
Empty
<project>/.claude/skills/and<project>/.claude/agents/, or move their contents into a project-scope bundle that extends the user one. For example, put project-specific skills under<project>/.claude/skills/local/<leaf>/and reference them from<project>/.claude/bundles/<name>.md:--- name: my-project extends: [data-science] skills: [local/project-specific-skill] ---
extends:composes parent + child via C3 linearization (seedocs/bundles-spec.md). -
Leave
mergeMcpunset (defaultfalse) so<project>/.mcp.jsonstays hidden when the bundle defines MCPs.
Run umbel show <name> before launching to see the resolved manifest plus
the project-vs-bundle MCP diff.
umbel list # scope-grouped bundle table
umbel show [name] # resolved manifest + sources + MCP diff
umbel build [name] [--no-cache] # warm cache, print path
umbel apply [name] [--vanilla] # pin <project>/.umbel-bundle (--vanilla = pin "no bundle")
umbel unpin # remove the pin
umbel run [name] [-- ...claude args] # launch claude (bundle if name/pin, vanilla otherwise)
umbel init # multi-step authoring wizard
umbel gc # prune cache (keep newest 3 per name)
umbel shim install [--force] # install ~/.local/share/umbel/bin/claude (the PATH shim)
umbel shim uninstall # remove the shim
umbel shim path # print the shim's absolute path
umbel skills [options] # low-level skill installer (v0 picker)When invoked without [name] on a TTY, run / apply / show / build
open a single-select picker. For run and apply the picker prepends a
(vanilla) row meaning "no bundle, plain claude." Pinned bundle (or
vanilla pin) is pre-selected.
Install the shim once so plain claude resolves through umbel and
discovers the project's bundle automatically. The shim short-circuits to
the real claude binary when umbel has already resolved the launch (so
subprocess shellouts to claude don't re-prompt):
umbel shim install # writes ~/.local/share/umbel/bin/claude
# then add to ~/.zshrc or ~/.bashrc:
export PATH="$HOME/.local/share/umbel/bin:$PATH"After that, plain claude in a project with a .umbel-bundle pin runs
under that bundle. In a project without a pin, the shim shows the picker
so you can choose a bundle for that session or pick (vanilla) to run
plain claude. Non-interactive shells fall back to vanilla automatically.
To opt out of all umbel routing for one invocation, call claude by its
absolute path, or temporarily unset PATH's shim entry.
<project>/.umbel-bundle is plain text, one line:
- a bundle name → run under that bundle;
__vanilla__→ run plain claude with no bundle, no picker;- absent → picker on TTY, vanilla on non-TTY.
umbel apply <name> writes a bundle pin. umbel apply --vanilla writes
the vanilla pin. umbel unpin removes the file. Commit it to share a
default with your team, or .gitignore it for per-developer setup.
- Explicit
<name>arg UMBEL_BUNDLEenv var (set to__vanilla__to force vanilla)<project>/.umbel-bundlepin file (name or__vanilla__)- On TTY → picker with
(vanilla)row; on non-TTY → silent vanilla.
The original v0 entry point. Symlinks handpicked skills from
$UMBEL_ARTIFACTS_DIR/skills/ into a project's .claude/skills/. Useful for
sandbox image builds and CI idempotency checks. Mostly subsumed by bundles —
keep using it if it fits your workflow.
# Interactive, from a Claude Code project root
umbel skills
# Deterministic install for a sandbox image build
umbel skills --target ./skills --skills tdd,grill-me,review
# CI-safe idempotency check
umbel skills --target .claude/skills --skills tdd,review --dry-run
# Replace a stray real dir left over from a manual copy
umbel skills --target .claude/skills --skills tdd --force| Flag | Meaning |
|---|---|
--target <path> |
Exact parent dir for skill symlinks |
--source <path> |
Override source root (default $UMBEL_ARTIFACTS_DIR/skills) |
--skills <csv> |
Non-interactive selection; implies no prompts |
--force |
Back up conflicting real dirs/files and replace |
--dry-run |
Print plan, exit 0, no writes |
-h, --help |
Usage |
-v, --version |
Version |
Rows start checked iff currently installed correctly.
| Icon | Meaning | Default-checked | Toggleable |
|---|---|---|---|
| (none) | Not installed | no | yes |
| (none) | Installed (correct symlink) | yes | yes (uncheck = remove) |
⚠ |
Symlink → different source | yes | yes (leave = relink) |
✖ |
Real dir/file, not a symlink | no | no (need --force) |
? |
Malformed SKILL.md | no | yes |
| Var | Effect |
|---|---|
UMBEL_ARTIFACTS_DIR |
Override artifact root (default: $XDG_CONFIG_HOME/umbel). |
UMBEL_DATA_DIR |
Override generated-data root, home of the PATH shim (default: $XDG_DATA_HOME/umbel). |
UMBEL_CACHE_DIR |
Override compiled-bundle cache root (default: $XDG_CACHE_HOME/umbel). |
UMBEL_BUNDLE |
Used by run resolution (arg > env > pin). __vanilla__ forces vanilla. |
UMBEL_RESOLVED |
Set by umbel run when spawning claude. The shim short-circuits to vanilla when present, so subprocess shellouts to claude don't re-prompt. |
NO_COLOR |
Disable ANSI color (icons retained). |
| Code | Meaning |
|---|---|
| 0 | Success (or dry-run completed) |
| 1 | Apply / runtime failure |
| 2 | Usage error (bad flag, validation error, picker on non-TTY) |
| 3 | Source / bundle / parent not found |
| 4 | Conflict without --force (skills picker) |
bundle '<name>': artifact ref(s) missing source qualifier; use '<source>/<leaf>'
The bundle lists a bare ref like tdd. Bundles only accept fully-qualified
refs (local/tdd, superpowers/tdd, …). If you have a legacy flat layout
under ~/.config/umbel/skills/<leaf>/, move it under a source namespace:
cd ~/.config/umbel/skills
mkdir -p local
# Move every existing leaf into the `local/` namespace
for d in */; do
[ "$d" = "local/" ] && continue
mv "$d" local/
doneThen reference each skill as local/<leaf> in your bundles.
bundle '<name>': source(s) not found: skills/<source>/<leaf>
The ref looks right, but the directory does not exist. Check:
- The path resolves under
$UMBEL_ARTIFACTS_DIR(default$XDG_CONFIG_HOME/umbel, typically~/.config/umbel) —echo ${UMBEL_ARTIFACTS_DIR:-$HOME/.config/umbel}. - The leaf directory contains the expected manifest file (
SKILL.md,AGENT.md,HOOK.md, orMCP.md). - If you symlinked from an upstream repo, the link target is still readable.
bundle '<name>' not found
The CLI cannot find a manifest named <name>.md in either scope. Run
umbel list to see the scope-grouped table of bundles umbel can see —
user-scope (~/.config/umbel/bundles/) and project-scope
(<project>/.claude/bundles/). The same listing also shows the resolved
pin (.umbel-bundle) and the UMBEL_BUNDLE env override, so it doubles
as a quick "what would umbel run pick?" check.
See CONTRIBUTING.md. Solo passive maintenance — PRs and issues welcome, response is best-effort.
MIT — see LICENSE.