Skip to content
memandipPublic

About

agman — agent manager: AWS_PROFILE-style config profiles for AI coding agents (Claude Code today; Codex, Gemini CLI on the roadmap)

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Latest commit

 

History

81 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

agman

agman — agent manager. Config profiles for AI coding agents: switch between complete work/personal setups the way AWS_PROFILE switches AWS accounts. Manages Claude Code, Codex CLI, and Gemini CLI.

A profile is everything your agents are in a given context — instructions, settings, rules, skills, agents, plugins, MCP servers — kept as one config tree per tool under ~/.agman/<name>:

~/.agman/work/claude/       ->  ~/.claude
~/.agman/work/claude.json   ->  ~/.claude.json
~/.agman/work/codex/        ->  ~/.codex
~/.agman/work/gemini/       ->  ~/.gemini

agman use activates a profile by symlinking each tool's config path at it — no shell setup required, and it applies to every new session on this machine, IDE extensions included. One pure-bash script, no dependencies, works on macOS (stock bash 3.2), Linux, WSL, and Git Bash.

Tool Config paths Env override
Claude Code ~/.claude, ~/.claude.json CLAUDE_CONFIG_DIR
Codex CLI ~/.codex CODEX_HOME
Gemini CLI ~/.gemini none — symlink switching is the only way

Only tools you actually have are touched: a profile covers a tool when it holds config for that tool, the CLI is on your PATH, or its config path exists. Restrict the set explicitly with AGMAN_TOOLS="claude codex" if you'd rather manage a subset. Gemini CLI is the reason agman switches by symlink rather than environment variables — it has no config-dir override at all, so nothing else generalizes.

$ agman create personal              # empty profile with a starter CLAUDE.md
$ agman create work --copy-current   # seeded from your current configs
$ agman use work
Switched to profile 'work' (tools: claude codex gemini).
Seeded this profile with your current Claude account (no re-login needed).
Your original configs were backed up as the 'global' profile.
$ claude                             # every new session now uses 'work'
$ agman off                          # fully restore your original configs

How it works

agman use <name> symlinks every managed config path at the profile (including ~/.claude.json, so user-scope MCP servers follow it):

  • The first time you switch, your original configs are moved into ~/.agman/global — a normal profile named global. Switch back to it anytime with agman use global, or run agman off to physically restore it and stop managing ~/.claude entirely.
  • The symlink doubles as the marker: a real directory means agman isn't managing that tool; a symlink into ~/.agman/ means that profile is active. agman current (or ls -l ~/.claude) tells you which.
  • agman refuses to touch a config symlink it didn't create (e.g. dotfiles managers) and skips that tool entirely rather than half-activating it; it also refuses to remove the active profile.

Selection precedence, highest first:

  1. CLAUDE_CONFIG_DIR set in a shell — wins in that shell only, never overridden. Use agman env, agman run, or agman exec for per-shell/per-process profiles alongside the global switch.
  2. The config symlinks set by agman use — machine-wide for all new sessions, no shell integration needed. This is the only mechanism that works for Gemini CLI.
  3. Nothing — stock ~/.claude, untouched behavior.

Running sessions keep the config they started with; switching affects new sessions.

Sessions: claude --resume keeps working across switches

Claude Code stores a resumable session inside the config directory, at <config dir>/projects/<encoded-cwd>/<session-id>.jsonl. Swapping that directory per profile would strand every earlier session, so agman keeps session and history state in one shared place that all profiles symlink:

~/.agman/.state/projects/        <- session transcripts (and auto memory)
~/.agman/.state/history.jsonl    <- prompt history
~/.agman/.state/todos, file-history, shell-snapshots, session-env, sessions

The practical effect: claude --resume <id> finds your session no matter which profile is active, and a session started under one profile is resumable under another. What stays per-profile is the persona — CLAUDE.md, settings, rules, skills, agents, plugins, MCP servers.

Codex CLI and Gemini CLI get the same treatment, one level down:

~/.agman/.state/codex/    <- sessions (rollouts), history.jsonl, log, tmp, .tmp (plugin cache)
~/.agman/.state/gemini/   <- antigravity, antigravity-browser-profile, tmp, history

Codex rollouts stay resumable across profiles, and the multi-GB browser profile Antigravity keeps inside ~/.gemini is stored once instead of once per profile. A profile holds configuration, never that bulk, so agman remove cannot take it down with it.

Upgrading from an earlier agman recovers history automatically: the next agman use merges whatever each profile is holding into shared state, file by file, and never overwrites. Since transcripts are UUID-named, collisions effectively don't happen; if one does, your copy is kept aside as projects.agman-conflict rather than being lost, and both agman doctor and agman off list anything kept aside so it doesn't sit unnoticed.

agman off turns the shared links back into real directories under ~/.claude, ~/.codex and ~/.gemini, so a restored config works without agman. Set AGMAN_SHARE_STATE=0 if you would rather each profile keep its own history — for example to keep client work strictly separate — at the cost of cross-profile resuming.

Accounts: switching profiles does not log you out

New profiles inherit your current Claude account, so switching never drops you at a login prompt. This needs care because Claude Code splits auth across two places:

  • Account identity lives in ~/.claude.json. Claude Code runs first-launch onboarding when hasCompletedOnboarding is absent, and treats auth as cached when oauthAccount is set. agman create seeds those keys (plus userID and machineID) from your live config, and agman use backfills any profile that predates this behavior.
  • Credentials live in the macOS Keychain, or in <config dir>/.credentials.json on Linux and Windows. On macOS the symlink keeps the path ~/.claude, so the Keychain entry is shared automatically. On Linux and Windows, agman promotes the credential file to ~/.agman/.credentials.json and symlinks each profile at it, so a token refresh in one profile is visible to all of them. agman off turns that link back into a real file, so your restored config works without agman.

Only account identity is copied — personal project state, history, and caches stay out of new profiles. A clean structured merge uses jq or python3 when available; with neither installed agman copies the whole state file instead and says so, rather than silently breaking your login.

Run agman doctor to see per-profile auth state. It reports presence only and never prints token material.

Giving a profile its own account

To have a profile authenticate as a different Claude account — a work login separate from your personal one:

agman login work      # runs 'claude setup-token', then paste the token (hidden input)
agman logout work     # back to your shared account

This stores a long-lived token (claude setup-token, valid about a year and it does not rotate) at mode 600 inside the profile, and points the profile's settings.json at a small helper script through Claude Code's apiKeyHelper setting. Because that setting applies to the CLI, the VS Code extension, and the Agent SDK, the profile's account follows you into IDEs — which an environment variable could not do.

Deliberately not used: snapshotting Keychain entries or .credentials.json. Refresh tokens rotate on every use, so a snapshot is already stale by the time you switch back, and restoring it logs you out.

Limits of setup-token credentials, worth knowing before you rely on them:

  • requires a Pro, Max, Team, or Enterprise plan
  • model requests only: no Remote Control and no claude.ai connectors (locally configured MCP servers still work)
  • managed setups that pin forceLoginOrgUUID reject environment credentials, so this path may be blocked on company-managed machines
  • expires after about a year; rerun agman login to replace it

Cloning a profile never copies its account: the token, helper, and apiKeyHelper setting are stripped, so the clone starts on your shared login.

Per-project profiles

A repository can name the profile it wants in a .agman file at its root:

profile: acme-work

Then agman use and agman run with no argument read it, from that directory or any subdirectory:

cd ~/work/acme && agman use      # activates acme-work

Because the file arrives with a clone, it is untrusted input: agman asks once before honouring it and records the decision. In a non-interactive shell it refuses rather than switching silently.

With the shell integration installed, entering the directory switches that terminal automatically and leaving it switches back:

eval "$(agman init zsh)"   # or: init bash

That auto-switch works by exporting CLAUDE_CONFIG_DIR, so it is terminal-only — IDE extensions don't inherit a shell's environment and keep following agman use. A CLAUDE_CONFIG_DIR you set by hand always wins; the hook only manages the value it set.

Note that Claude Code already scopes plenty of things per project natively — ./CLAUDE.md, ./.claude/settings.json, ./.claude/rules/, ./.claude/skills/, ./.claude/agents/, and ./.mcp.json. Reach for a .agman file when you need to change something with no project scope at all: the account, user-scope MCP servers, installed plugins, or the whole persona.

Install

Homebrew (macOS and Linux):

brew install memandip/agman/agman

That first install also trusts the tap, so from then on the short name works for everything:

brew install agman
brew upgrade agman

To use the short name from the start, trust the tap explicitly first — Homebrew will not resolve a bare formula name from an untrusted third-party tap:

brew tap memandip/agman
brew trust memandip/agman
brew install agman

From a clone:

git clone https://github.com/memandip/agman.git && cd agman && ./install.sh

Or piped (no clone):

curl -fsSL https://raw.githubusercontent.com/memandip/agman/main/install.sh | bash

Update later with:

agman update

(update does a git pull when agman runs from a checkout, otherwise downloads the latest release script and swaps it in atomically after validation.)

Commands

Command What it does
create <name> New empty profile with a starter CLAUDE.md, inheriting your current account. Flags: --copy-current (seed from current config; skips sessions/cache/history), --from <profile>, --link-plugins
use [name] Activate: symlink each tool's config path → profile. With no name, reads the nearest .agman file. First use backs up your originals as the global profile, and migrates older profiles to the current layout
off Deactivate and physically restore your original configs
current Show the active profile and which tools it covers
list List profiles; (active: …) shows the live tools
dir <name> / env <name> Print a profile's Claude config dir / export lines for every tool that supports one. dir --for-cwd resolves the nearest trusted .agman file
run [name] [-- args] Launch claude once under a profile without switching globally. With no name, reads the nearest .agman file
exec <name> -- <cmd> Run any command with CLAUDE_CONFIG_DIR and CODEX_HOME set
rename <old> <new> / remove <name> Manage profiles (rename repoints active symlinks; remove refuses if active)
migrate Move profiles to the current layout and relink (runs automatically on use)
login <name> / logout <name> Give a profile its own Claude account, or return it to your shared login
cloud <subcommand> Sync profiles with an agman cloud server: login (URL defaults to the public server at https://agman.vercel.app), push [name] [--dry-run], pull <name> [--as <local>] [--force], init <org-slug> (new profile from a team default), list, status, logout. Pushes are versioned and encrypted at rest server-side. A synced profile is a persona, not a login: account material (the identity file, .credentials.json, per-profile tokens, codex/gemini auth, any .env) and ephemeral state (sessions, history, caches, tmp, logs — including state other products drop into the managed trees, like Antigravity's browser profile inside ~/.gemini) stay local by default and are stripped on pull; a push is refused if a synced file looks like it holds a secret. push --dry-run lists every file a push would carry with its size, without contacting the server; a payload over the server limit is refused before upload with the largest files named. The limit is read from the server when it advertises one (maxPushBytes for direct requests — hosting platforms like Vercel cap function bodies at ~4.5 MB — and blobPushBytes for the presigned blob-upload flow, which carries archives up to agman-cloud's 25 MB maximum straight to blob storage, bypassing the function body limit); pushes pick the right path automatically. AGMAN_CLOUD_MAX_PUSH_BYTES overrides and forces the direct path. Interactive pushes show curl's transfer bar during the upload (auto-detected on a terminal; AGMAN_PROGRESS=1/0 forces/suppresses it). --include-secrets (push) / --with-secrets (pull) opt into carrying account material for your own machine-to-machine restore. Pulls treat a downloaded profile as untrusted — they strip hooks/apiKeyHelper from settings.json, never touch shared-state symlinks, back up an existing profile first, and refuse the active profile without --force. Reinstallable plugin artifacts (plugin caches, marketplace clones, catalog data) are never synced in either direction; pull reinstalls them into the pulled profile from its own references (settings.json enabledPlugins + plugins/known_marketplaces.json) via the claude CLI — set AGMAN_CLOUD_NO_INSTALL=1 to skip. Dependency artifacts (.venv/venv, node_modules, __pycache__, .git — e.g. under a cloned MCP server in ~/.claude/mcp-servers) and agman's shared-state merge leftovers (*.agman-conflict, *.agman-orphaned) are never synced either: the sources next to them sync, and the artifacts are rebuilt on the target machine (pip install / npm install / git clone). Locally cloned profiles keep them — the exclusion is cloud-only. https is required (override with AGMAN_CLOUD_INSECURE=1); the API key is passed to curl via a mode-600 file, never the command line. agman cloud --verbose <sub> (or AGMAN_VERBOSE=1) logs the request line, push payload size, and response code
update Self-update from git or GitHub
doctor Diagnose setup: tool detection, per-path link state, per-profile auth, layout version
stealth <subcommand> Block a forced OTEL exporter from leaving this machine (see Stealth): claude --otel <endpoint> [--profile <name>] [-- args] launches Claude through a local mitmproxy that answers <endpoint>'s host with 403, dropping telemetry while everything else still works; stop / status manage the daemon. Needs mitmproxy (mitmdump) + lsof + curl.
init [zsh|bash] Optional per-shell integration (see below)

Per-shell profiles (optional)

The global symlink switch needs no shell setup. If you also want per-shell profiles — e.g. a work terminal and a personal terminal at the same time — either eval an export line:

eval "$(agman env personal)"

or add the optional hook to ~/.zshrc / ~/.bashrc, which makes agman use/off also set CLAUDE_CONFIG_DIR in the current shell:

eval "$(agman init zsh)"   # or: init bash

Caveats you should know

  • Switching is machine-global for new sessions (that's the point). Terminals or IDE windows with running Claude sessions keep the config they launched with.
  • use requires symlink support; on Windows use WSL, or Git Bash with Developer Mode enabled.
  • Profiles share one account by default — see Accounts. Per-profile accounts are Phase 4 on the roadmap. Treat profile dirs as sensitive: they can hold credentials and MCP tokens.
  • agman update and the curl installer need curl. Everything else is bash plus standard tools; jq or python3 improves profile creation but is not required.
  • CLAUDE_CONFIG_DIR (used by the per-shell layer) is honored by the Claude Code CLI but not officially documented (anthropics/claude-code#33430). The global symlink switch does not depend on it.

Stealth: block a forced OTEL exporter

Some Claude Code team accounts inject an OpenTelemetry exporter (OTEL_EXPORTER_OTLP_ENDPOINT) that cannot be switched off by hand. agman stealth blocks it at the network layer, so the telemetry never leaves your machine:

agman stealth claude --otel https://otel.acme-corp.example/v1/traces

stealth claude launches your regular claude through a local mitmproxy daemon that answers every request to <endpoint>'s host with 403, drops the OTEL POSTs, and passes everything else through — so api.anthropic.com and the rest keep working. --otel is required. Add -- to forward extra args (e.g. -- --resume), and --profile <name> to also set CLAUDE_CONFIG_DIR to that profile's config.

agman stealth claude --otel https://otel.acme-corp.example/v1/traces --profile work -- --resume
agman stealth status     # is the proxy daemon running, and blocking which host?
agman stealth stop       # stop the daemon

Setup (macOS)

stealth needs mitmproxy's mitmdump; lsof and curl come with macOS:

brew install mitmproxy

On first run agman generates ~/.mitmproxy/mitmproxy-ca-cert.pem if it's missing, and Node trusts it automatically through NODE_EXTRA_CA_CERTS. Optionally trust it system-wide once:

sudo security add-trusted-cert -d -r trustRoot \
  -k /Library/Keychains/System.keychain \
  ~/.mitmproxy/mitmproxy-ca-cert.pem

The daemon persists across sessions until you stealth stop it. Its state — including the small generated mitmproxy addon (block_otel.py) — lives under ~/.agman/.stealth, so it is never synced or cloned with a profile. The dependencies are checked only when you run stealth (like cloud needs curl/tar); the rest of agman stays dependency-free. The port defaults to 8080 (override with AGMAN_STEALTH_PORT); the CA path is ~/.mitmproxy/mitmproxy-ca-cert.pem (override with AGMAN_STEALTH_CA).

Development

bash tests/run.sh    # sandboxed: fake HOME, stub claude binary — safe to run anywhere

CI runs the suite on Ubuntu + macOS and lints with shellcheck on every push.

Roadmap

  • Profile syncing between machines — shipped as agman cloud (0.8.0), backed by the agman cloud companion service
  • Profile sharing over git: push/pull to a repository you own, and a .agman file that fetches a profile that isn't local yet
  • More tools: Qwen Code and Kimi CLI adapters, once their config layouts are verified against the real CLIs
  • fish shell support, tab completions
  • --fresh-auth seeding (exclude copied credentials)

License

MIT

About

agman — agent manager: AWS_PROFILE-style config profiles for AI coding agents (Claude Code today; Codex, Gemini CLI on the roadmap)

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages