9 releases (1 stable)
| new 1.0.0 | May 14, 2026 |
|---|---|
| 0.4.0 | May 13, 2026 |
| 0.3.1 | May 11, 2026 |
| 0.2.1 | May 11, 2026 |
| 0.1.0 | Apr 30, 2026 |
#274 in Development tools
490KB
9K
SLoC
agentenv
Project-scoped AI agent and plugin environment manager
agentenv lets you define, version, and reproduce the AI capabilities of a project across tools like Claude Code, Codex, Cursor, and others.
It works similarly to .nvmrc, .jenv, or .tool-versions, but instead of runtime versions, it manages agents, commands, skills, and plugins.
Table of contents
- Problem
- Solution
- How it works
- Installation
- Quick start
- Configuration
- Commands
- Marketplace structure
- Target adapters
- VS Code integration
- Design principles
- Roadmap
- Non-goals
- License
Problem
AI coding tools are fragmented:
- Each tool has its own plugin, agent, command, or skill structure
- No standard way to share project-specific AI setups
- Teams cannot reliably reproduce AI capabilities across machines
- Global installs break project isolation
- Marketplace-style agent repositories are useful, but importing everything globally is too broad
Solution
agentenv introduces:
- A project-local config:
.agentrc.yaml - A plugin marketplace source
- A selective plugin import system
- A cross-tool sync engine
- Idempotent symlink-based linking
- Non-blocking startup behavior for editors such as VS Code
How it works
Project opens
↓
agentenv sync
↓
Fetch/update marketplace
↓
Resolve selected plugins
↓
Recreate managed symlinks into target tools
↓
Warn on recoverable failures
The marketplace is treated as a source of truth. The project config decides which plugins are imported and which tools receive them.
Installation
macOS / Linux with Homebrew
brew install eduardoarantes/agentenv/agentenv
npm (cross-platform)
npm install -g @eduardoarantes/agentenv
Rust / Cargo
cargo install agentenv
Manual install
Download the latest binary from GitHub Releases and place it on your PATH.
Example:
chmod +x agentenv
mv agentenv /usr/local/bin/agentenv
VS Code extension
The extension wraps the CLI so syncs run automatically when you open or edit a project's .agentrc.yaml. The CLI must be installed and on PATH first (see the options above).
From the Marketplace:
code --install-extension eduardoarantes.agentenv
Or search agentenv in the Extensions panel (Marketplace listing).
From a .vsix (offline or pre-release builds):
code --install-extension agentenv-<version>.vsix
Quick start
1. Initialize a project
agentenv init
This creates:
# .agentrc.yaml
version: 1
# Source-of-truth tool. agentenv reads its native layout losslessly and
# renders the canonical out to every other configured target. v1 supports
# `claude-code` as the source.
source: claude-code
marketplaces:
default:
path: ~/.agentenv/marketplace
remote: https://github.com/eduardoarantes/claude-code-plugin-marketplace.git
ref: main
# Add plugin entries here, e.g.:
# - name: git-simple
plugins: []
# Tools to materialize the canonical for. `claude-code` is the source and
# is never written. Opt in by uncommenting.
targets:
cursor: {}
# codex: {}
# copilot: {}
# gemini-cli: {}
# junie: {}
sync:
onOpen: true
refetch: true
The plugin list is empty so the first agentenv sync succeeds — open
.agentrc.yaml and add plugins as you need them. See the
Configuration section below for the full shape.
init also adds .agentenv/ to the project's .gitignore (creating the file
if needed). That directory holds state.json, a per-machine manifest of the
links agentenv installed — it stores absolute paths and is regenerated on
every agentenv sync, so it should not be committed.
2. Sync the environment
agentenv sync
3. Validate setup
agentenv doctor
Configuration
.agentrc.yaml
version: 1
# Source of truth for hooks, skills, and agents. agentenv reads the named
# tool's native layout losslessly into .agentenv/<capability>.canonical.yaml
# and renders that canonical out to every other configured target. Setting
# `source: claude-code` also implicitly imports marketplaces / plugins from
# `~/.claude/settings.json` and `<project>/.claude/settings.json` (missing
# files are tolerated). See "Importing from Claude Code" below.
source: claude-code
marketplaces:
default:
path: ~/.agentenv/marketplace
remote: https://github.com/eduardoarantes/claude-code-plugin-marketplace.git
ref: main
plugins:
- name: python-agents
- name: pr-review
- name: engineering-standards
# Set-membership map of write targets. Per-target object is currently `{}`;
# the source target is never written, even if listed.
targets:
cursor: {}
codex: {}
sync:
onOpen: true
refetch: true
clean:
pruneEmptyDirs: true
Fields
| Field | Required | Description |
|---|---|---|
version |
Yes | Config schema version. |
source |
Yes² | Source-of-truth tool for the canonical pipelines. v1 supports claude-code. Setting source: claude-code also implicitly imports extraKnownMarketplaces and enabledPlugins from ~/.claude/settings.json and <project>/.claude/settings.json (project wins over global; explicit .agentrc.yaml entries win over both). The source target is read-only and is dropped from targets: during sync if listed. See Importing from Claude Code below. |
marketplaces |
Yes¹ | Marketplace repos keyed by namespace. |
marketplaces.<namespace>.path |
Yes | Local cache directory where agentenv clones the marketplace repo (from remote at ref). It's not a path inside the project — it's an agentenv-managed checkout used as a read-only source. Supports ~ (home), absolute paths, and relative paths (resolved against the project root). Refetches reset the working tree to origin/<ref>, so don't hand-edit anything inside it. A common choice is ~/.agentenv/marketplace to share the cache across projects. |
marketplaces.<namespace>.remote |
Yes | Git remote used to clone/fetch the marketplace. |
marketplaces.<namespace>.ref |
No | Branch, tag, or commit to use. Defaults to main. |
plugins |
No | List of plugins to import. A plugin without namespace uses default. |
targets |
No | Set-membership map of tools to materialize the canonical for. Each per-target object is currently {} — listing a target opts it in; path conventions live inside each capability's writers module. Recognised names: claude-code, cursor, codex, copilot, gemini-cli, junie, antigravity. Required when at least one capability needs propagation; source: is required whenever targets: is non-empty. |
sync.onOpen |
No | Whether editor integrations should sync on workspace open. |
sync.refetch |
No | Whether to fetch marketplace updates before syncing. |
clean.pruneEmptyDirs |
No | After agentenv clean removes managed links, prune any now-empty directories inside the project root. Stops at the project root and never touches dirs that still hold user files. Defaults to true. |
gitignore_managed_links |
No | When true, agentenv maintains a sentinel-delimited block in <project>/.gitignore listing every link/copy it currently owns. User-authored .gitignore lines outside the block are preserved verbatim. agentenv clean strips the block entirely. See Auto-gitignoring managed links below. |
instruction_files |
No | Map of root-level source files (CLAUDE.md, AGENTS.md, CURSOR.md, …) to lists of project-relative destination paths. agentenv symlinks each source into each destination. Never overrides existing user files; agentenv-managed symlinks are updated when the source changes. See Propagating instruction files below. |
¹ marketplaces may be omitted when source: claude-code is set and
Claude's settings.json provides at least one extraKnownMarketplaces
entry.
² source is required whenever targets: is non-empty (every capability
pipeline needs somewhere to read from). v1 only implements claude-code
as a source — setting any other value fails validation.
Importing from Claude Code
Setting source: claude-code does double duty:
- It declares Claude as the source of truth for the canonical
pipelines — agentenv reads
.claude/settings.json(hooks),.claude/skills/and.claude/agents/losslessly into.agentenv/<capability>.canonical.yamland renders them out to every other configured write target. - It implicitly imports Claude's
extraKnownMarketplacesandenabledPluginsfrom both~/.claude/settings.jsonand<project>/.claude/settings.json(project beats global; explicit.agentrc.yamlentries win over both). Missing settings.json files are tolerated.
version: 1
source: claude-code
# Propagate Claude's plugins, skills, agents, and hooks to your other
# tools. The claude-code target is the source — listing it has no effect.
targets:
cursor: {}
codex: {}
Inspect what got imported with agentenv claude-config show
and the generated canonical with agentenv canonical show.
Local assets are first-class. With source: claude-code, your
project's <project>/.claude/{agents,skills}/ directories ARE the source
— agentenv reads them directly into the canonical and writes each one out
to every configured target. A skill at .claude/skills/refactor/SKILL.md
materializes at .cursor/skills/refactor/SKILL.md,
.codex/skills/refactor/SKILL.md (via .agents/), and so on. Subagents
defined in .claude/agents/<name>.md go through the same flow, with the
Codex writer transforming Markdown frontmatter to TOML and reporting any
fields it had to drop.
Hooks come from the merged ~/.claude/settings.json + project
settings.json hooks block. The writers are documented in
docs/HOOKS.md.
Propagating instruction files
Different AI tools expect their cross-tool instruction sheet in different
places — Claude Code reads CLAUDE.md, Codex/Cursor/Copilot read
AGENTS.md, Junie reads .junie/AGENTS.md, Antigravity reads agents.md,
and so on. Rather than duplicate the same content N times, point at a single
source file and let agentenv mirror it everywhere.
Automatic defaults (source: claude-code)
When source: claude-code is set and you haven't written your own
instruction_files: block, agentenv applies a sensible default: it uses
CLAUDE.md (or AGENTS.md if no CLAUDE.md exists) as the source and
propagates it to each configured target's expected instruction-sheet path:
| Target | Default destination(s) |
|---|---|
codex, cursor, copilot |
AGENTS.md |
gemini-cli |
GEMINI.md, AGENTS.md |
junie |
.junie/AGENTS.md |
antigravity |
agents.md |
| (any other) | none |
So a minimal config like
version: 1
source: claude-code
targets:
cursor: {}
junie: {}
automatically links CLAUDE.md → AGENTS.md and CLAUDE.md → .junie/AGENTS.md
on the next agentenv sync. Run agentenv explain to see exactly which
links would be created. To opt out of the defaults, write your own
instruction_files: block (described below) — any explicit entry replaces
the defaults entirely.
Manual propagation
For full control, write instruction_files: yourself:
instruction_files:
CLAUDE.md: # source file at project root
- AGENTS.md # destinations (project-relative)
- .junie/AGENTS.md
- agents.md
CURSOR.md:
- .cursor/rules/main.md
After agentenv sync, each destination is a symlink pointing at the source
file. The destinations are tracked in .agentenv/state.json so
agentenv clean reverts them.
Safety: agentenv never overrides existing files. If a destination already contains user content (a regular file, a directory, or a symlink agentenv doesn't own), it's left untouched and a warning is logged. Agentenv-managed symlinks pointing at the wrong source are updated when the config changes — those are owned by agentenv, so updating them isn't an override of user content.
If a configured source file doesn't exist at the project root, its
destinations are skipped with a warning. Removing an entry from
instruction_files and re-syncing cleans up the previously-managed
destinations.
Auto-gitignoring managed links
Agentenv-managed symlinks are derived state — they're regenerated every
time agentenv sync runs, so committing them adds noise (every plugin
update produces a diff) and risks broken links across machines if absolute
paths leak in. Set gitignore_managed_links: true to have agentenv keep
your .gitignore honest:
version: 1
source: claude-code
gitignore_managed_links: true
targets:
cursor: {}
After agentenv sync, <project>/.gitignore looks like:
# (your existing user-authored lines stay above)
node_modules/
.env
# >>> agentenv managed (do not edit; regenerated by `agentenv sync`) <<<
/.cursor/agents/task-executor-tdd.md
/.cursor/agents/test-standard-reviewer.md
/.junie/AGENTS.md
# <<< agentenv managed >>>
Rules:
- User-authored lines outside the block are preserved verbatim — agentenv only rewrites the contents between the sentinel comments.
- One entry per managed link, sorted alphabetically for stable diffs.
- Idempotent: re-running sync with no changes produces an identical
.gitignore. agentenv cleanstrips the block entirely, regardless of whether the flag is still set in.agentrc.yaml. Cleanup means cleanup.- If
.gitignoredoesn't exist when sync runs, agentenv creates one containing just the managed block.
When the flag is false (the default), agentenv never touches .gitignore.
Commands
agentenv init
Create a starter .agentrc.yaml.
agentenv init
agentenv sync
Fetch the marketplace, resolve plugins, and reconcile managed links.
agentenv sync # honor sync.refetch from config
agentenv sync --refetch # force fetch even if config disables it
agentenv sync --no-fetch # skip the network; require an existing local copy
Behavior:
- Marketplaces missing locally are cloned from
remoteatref. - With
sync.refetch: true(or--refetch), existing marketplaces are fetched and reset toorigin/<ref>. The cache directory is agentenv-managed; don't hand-edit it. - Fetch failures are non-fatal when a local copy exists — they surface as warnings and sync continues with the cached content.
--no-fetcherrors out only when a marketplace is missing locally.- Broken or missing plugins produce diagnostics.
- Existing managed links are recreated.
- User-created files are not deleted.
agentenv list
List configured marketplaces, plugins, and targets from .agentrc.yaml.
agentenv list
agentenv doctor
Diagnose project and system state. Exits non-zero when issues are found, so it's safe to use as a CI gate.
agentenv doctor
Checks:
.agentrc.yamlexists and parses- each configured marketplace exists locally
- every selected plugin resolves against its marketplace
- managed links recorded in
.agentenv/state.jsonstill exist on disk
agentenv explain
Show what sync would do without touching the filesystem. Useful for
debugging and code review.
agentenv explain
Marketplaces are not fetched — the cache must already be populated (run
agentenv sync once first).
agentenv clean
Remove every link recorded in .agentenv/state.json, then delete the state
file.
agentenv clean
Defensive: only removes symlinks that still point at the source agentenv
recorded. If you replaced a managed link with your own file, that file is
left untouched and reported as skipped.
After links are removed, clean prunes any now-empty directories inside the
project root (e.g. a leftover .claude/skills/). It stops at the project
root and never touches dirs that still hold user files. Disable via
clean.pruneEmptyDirs: false in .agentrc.yaml.
agentenv claude-config show
Print the marketplaces, plugins, and hooks agentenv would import from your
Claude settings.json files. Useful for debugging the implicit Claude
settings import that runs whenever source: claude-code is set.
agentenv claude-config show # human-readable
agentenv claude-config show --json # machine-readable
This command reads ~/.claude/settings.json and
<project>/.claude/settings.json directly — it does not require
source: claude-code to be set in .agentrc.yaml, so you can preview the
import before flipping the switch.
agentenv canonical show
Print the canonical YAML agentenv generated for a capability under
<project>/.agentenv/<capability>.canonical.yaml. Useful for inspecting
what the source reader actually captured before the writers materialize
it to each target.
agentenv canonical show skills
agentenv canonical show agents
agentenv canonical show hooks
Errors out with a helpful message if no canonical exists yet — run
agentenv sync first.
Marketplace structure
agentenv expects a marketplace compatible with the Claude Code plugin marketplace style.
Example:
plugins/
my-plugin/
.claude-plugin/
plugin.json
agents/
commands/
skills/
hooks/
The marketplace is not owned by agentenv. It is consumed as an external source.
Target adapters
agentenv is source-driven: one tool is declared the source of truth
(source: claude-code in v1) and each other listed target receives the
capability via a per-capability writer module under
crates/agentenv-core/src/<capability>/writers/. Each writer owns:
- the destination path on disk
- format translation (e.g. Codex's agent files are TOML, not Markdown)
- refuse-on-conflict detection so agentenv never clobbers user-authored content
- a
dropsreport so any field/event a target cannot represent is surfaced in the sync output rather than silently lost
Internal layout (one module per capability):
crates/agentenv-core/src/
hooks/
readers/{claude_code.rs, mod.rs}
writers/{cursor.rs, codex.rs, mod.rs}
canonical_io.rs
pipeline.rs
skills/
readers/{claude_code.rs, mod.rs}
writers/mod.rs # symlink writer for all targets
...
agents/
readers/{claude_code.rs, mod.rs}
writers/{codex.rs, mod.rs} # codex.rs materializes to TOML
...
v1 source: claude-code. v1 write targets:
| Capability | cursor | codex | copilot | gemini-cli | junie | antigravity |
|---|---|---|---|---|---|---|
| skills | ✓ | ✓ (.agents/) |
✓ | ✓ | ✓ | ✓ (.agent/) |
| agents | ✓ | ✓ (TOML) | ✓ (.agent.md) |
✓ | ✓ | skip |
| hooks | ✓ | ✓ (~/.codex/config.toml) |
— | — | — | — |
VS Code integration
The VS Code extension is thin — the CLI remains the source of truth. Install it from the Marketplace (see Installation).
On workspace open:
VS Code activates
↓
Runs `agentenv sync`
↓
Shows warning only on failure
↓
Writes details to Agentenv output channel
Commands
agentenv: Sync Pluginsagentenv: Doctoragentenv: Open Configagentenv: List Pluginsagentenv: Clean Managed Links
Settings
| Setting | Default | Description |
|---|---|---|
agentenv.path |
agentenv |
Path to the agentenv binary. Resolved against $PATH if not absolute. |
agentenv.syncOnOpen |
true |
Run agentenv sync automatically when a workspace with .agentrc.yaml is opened. |
agentenv.syncOnConfigChange |
true |
Run agentenv sync automatically when .agentrc.yaml is modified or created. |
agentenv.configChangeDebounceMs |
1500 |
Debounce window (ms) before auto-syncing after a .agentrc.yaml change. Higher values batch rapid saves; lower values feel snappier. |
agentenv.refetchOnSync |
false |
Pass --refetch when invoking sync, forcing a marketplace refresh. |
Design principles
- Project-first: no global pollution
- Deterministic: same config produces same linked environment
- Non-blocking: recoverable failures warn instead of stopping work
- Composable: target adapters isolate tool-specific behavior
- Transparent: filesystem changes are explainable
- Safe by default: never delete unmanaged files
Roadmap
Shipped:
- Remote plugin sources beyond one marketplace (multiple namespaces under
marketplaces) - Windows symlink fallback strategies
- More target adapters (Claude Code, Codex, Cursor, Copilot)
- JSON Schema for
.agentrc.yaml(schemas/agentrc.schema.json) - VS Code extension
- Dry-run mode (
agentenv explain) - Import config + propagate capabilities from Claude Code (
source: claude-code) - Source-driven canonical pipelines for hooks, skills, and agents
- Propagate instruction files (
CLAUDE.md→AGENTS.md,.junie/AGENTS.md, …) viainstruction_files - Auto-gitignore managed links (
gitignore_managed_links: true)
Planned:
- Lockfile support:
agentenv.lock - Plugin version pinning
- CI integration:
agentenv check - Plugin compatibility metadata
Non-goals
For the initial version, agentenv is not:
- a plugin marketplace host
- a plugin runtime
- a cloud sync service
- a GUI app
- a replacement for each target tool’s native configuration system
License
MIT
Dependencies
~17–36MB
~452K SLoC