If you’ve been managing more than one AI coding agent for more than three months, you probably have a small mess of shell aliases, .envrc files, and wrapper scripts. This article is a step-by-step migration to Ringlet.

The TL;DR: every alias becomes a profile. Every direnv file becomes a profile. Every wrapper script becomes a profile. The shell stops being the unit of configuration.

Step 0: Audit what you have

Before deleting anything, list it. The usual suspects:

# Shell aliases related to AI agents
grep -E '^alias.*(claude|codex|grok|droid|opencode)' ~/.zshrc ~/.bashrc ~/.config/zsh/*

# Environment variables exported per shell
grep -E 'ANTHROPIC|OPENAI|XAI|GROK|MINIMAX|OPENROUTER' ~/.zshrc ~/.bashrc ~/.zshenv ~/.profile

# Direnv config in project directories
find ~/Projects -name '.envrc' 2>/dev/null | head -20

# Wrapper scripts
ls ~/.local/bin/ ~/bin/ 2>/dev/null | grep -Ei 'claude|codex|grok|ai-'

The output is your migration checklist. Each line is one Ringlet profile.

Step 1: Install Ringlet, init

curl -fsSL https://raw.githubusercontent.com/neul-labs/ringlet/main/install.sh | sh
ringlet init

ringlet init detects every supported agent CLI on your $PATH, finds the existing usage logs (~/.claude/usage.jsonl, ~/.codex/sessions/), and imports them so the cost graph isn’t empty on day one.

Step 2: Move keys to the keychain

For every API key you currently have in a shell-exported environment variable:

# Anthropic personal
ringlet keychain set anthropic personal --from-env ANTHROPIC_API_KEY

# (set the work key in your shell briefly, then run:)
ANTHROPIC_API_KEY="sk-ant-..." ringlet keychain set anthropic work --from-env ANTHROPIC_API_KEY

# OpenAI
ringlet keychain set openai default --from-env OPENAI_API_KEY

# etc.

Verify:

ringlet keychain list
# anthropic/personal
# anthropic/work
# openai/default

Now delete the corresponding export lines from your shell rc files. Restart the shell. Confirm the env vars are gone:

echo $ANTHROPIC_API_KEY  # → (empty)

If something breaks at this stage, it means a tool you forgot about was depending on the global env var. Either move that tool inside a Ringlet profile too, or temporarily put the export back while you finish the migration.

Step 3: Create profiles for each shell alias

Take your audit list and translate one alias at a time.

Before — a typical setup:

# ~/.zshrc
alias claude-personal='ANTHROPIC_API_KEY=$(security find-generic-password -s anthropic-personal -w) claude'
alias claude-work='ANTHROPIC_API_KEY=$(security find-generic-password -s anthropic-work -w) claude'
alias claude-cheap='ANTHROPIC_BASE_URL=https://api.minimax.io/anthropic ANTHROPIC_API_KEY=$(security find-generic-password -s minimax -w) claude'
alias codex-work='OPENAI_API_KEY=$(security find-generic-password -s openai-work -w) codex'

After:

ringlet profiles create claude personal --provider anthropic --key-alias personal
ringlet profiles create claude work     --provider anthropic --key-alias work
ringlet profiles create claude cheap    --provider minimax   --key-alias minimax
ringlet profiles create codex  work     --provider openai    --key-alias openai-work

# Delete the four aliases from ~/.zshrc.
# Optionally re-add them as thin Ringlet wrappers:
echo "alias claude-personal='ringlet profiles run personal'" >> ~/.zshrc
echo "alias claude-work='ringlet profiles run work'"         >> ~/.zshrc
echo "alias claude-cheap='ringlet profiles run cheap'"       >> ~/.zshrc
echo "alias codex-work='ringlet profiles run codex-work'"    >> ~/.zshrc

The aliases stay; they just point at Ringlet. From your fingers’ point of view, nothing changes.

Step 4: Translate direnv to profiles

Direnv is the trickier case because it ties config to a directory, not just a command. Most direnv setups for AI agents look like:

# ~/Projects/client-acme/.envrc
export ANTHROPIC_API_KEY=$(security find-generic-password -s anthropic-acme -w)
export ANTHROPIC_BASE_URL=https://gateway.acme.com/anthropic

The Ringlet way: a profile with the same binding, plus an optional shell hook to auto-select it by directory.

ringlet profiles create claude acme \
  --provider custom \
  --custom-endpoint https://gateway.acme.com/anthropic \
  --key-alias anthropic-acme

# Auto-select when you're in the project directory
cat >> ~/.zshrc <<'EOF'
ringlet_auto_profile() {
  if [[ "$PWD" == "$HOME/Projects/client-acme"* ]]; then
    export RINGLET_DEFAULT_PROFILE=acme
  else
    unset RINGLET_DEFAULT_PROFILE
  fi
}
chpwd_functions+=(ringlet_auto_profile)
ringlet_auto_profile  # run on shell start
EOF

Now claude (or ringlet profiles run) inside the project directory uses the acme profile. Outside it, your default. The shell hook is cheap ([[ ]] is a builtin) so there’s no measurable cost on every cd.

If you want stricter “this dir → this profile” mapping, add a [directory-mapping] block to ~/.config/ringlet/config.toml:

[[directory-mapping]]
path = "~/Projects/client-acme"
profile = "acme"

[[directory-mapping]]
path = "~/Projects/personal"
profile = "personal"

ringlet profiles run (no args) consults this when called from inside the directory.

Step 5: Translate wrapper scripts

The trickiest one. A typical wrapper script might do:

#!/bin/bash
# ~/.local/bin/ai-prod
export ANTHROPIC_API_KEY=$(...)
export ANTHROPIC_BASE_URL=$(...)
# Log everything to a JSONL for audit
exec claude "$@" 2> >(tee -a ~/audit-prod.jsonl >&2)

The env-var part becomes a Ringlet profile (steps 2 and 3 above). The audit logging becomes a Ringlet hook:

ringlet hooks add prod --on tool-use \
  --shell 'echo "$(date -u) $RINGLET_TOOL $RINGLET_TOOL_INPUT" >> ~/audit-prod.jsonl'

Now ringlet profiles run prod does what the wrapper used to do, plus you get Ringlet’s cost tracking for free.

Step 6: Migrate per-project memory

If you’ve been using Claude Code in multiple projects, you probably have project-specific CLAUDE.md files. These are still relevant — they live in the repo and Claude reads them from the workspace. They don’t need migration.

What does need migration is anything stored in ~/.claude/ that’s project-specific (memory entries, project-level approvals). ringlet import claude --to <profile> will move them all into one profile by default. If you want to split them across profiles by directory, do it once manually:

# Move acme-related memory into the acme profile
mv ~/.claude/memory/projects/client-acme \
   ~/.ringlet/profiles/acme/HOME/.claude/memory/projects/

# Move personal-related memory into the personal profile
mv ~/.claude/memory/projects/personal-* \
   ~/.ringlet/profiles/personal/HOME/.claude/memory/projects/

Imperfect. Manual. Once.

Step 7: Delete the old setup

Once everything is mapped:

# Back up just in case
cp -r ~/.claude ~/.claude.bak
cp ~/.zshrc ~/.zshrc.bak

# Delete the original ~/.claude — your profiles have their own copies
# (skip this if you want to keep using Claude Code without Ringlet sometimes)
# rm -rf ~/.claude

# Delete obsolete env exports from ~/.zshrc
# (do this by hand — the audit list from Step 0 is your guide)

# Remove direnv .envrc files that are now redundant
# (only ones tied to AI keys; keep node version selectors, etc.)

What doesn’t translate

A few patterns Ringlet doesn’t replicate:

  • Per-invocation env tweaks. If your wrapper script reads from the shell environment dynamically (e.g. picks a model based on $DEBUG), you’ll need to set up multiple profiles or use the routing layer.
  • Interactive prompts inside the wrapper. Wrapper scripts sometimes ask “which account?” interactively. Ringlet’s model is “the profile chooses for you” — make the choice explicit ahead of time.
  • External tool integration. If a wrapper script exec’d a tool and an agent, sharing state between them — Ringlet doesn’t have a primitive for that. Keep the wrapper for that case.

Two weeks later

The pattern most people settle into:

  • Three to five profiles total. Personal, work, one per client engagement.
  • Shell aliases mapping claude to ringlet profiles run with a default profile based on directory.
  • One audit hook running across all profiles.
  • Weekly check on ringlet usage to catch surprises.

The shell rc shrinks. The .envrc litter shrinks. The “wait, which account is this terminal on?” anxiety goes away. The cost tracking pays for the install in about a week.

Worth 15 minutes.