equipr: skills and MCP servers for coding agents
CLI that installs skills, commands, and MCP servers from marketplaces and Agent Plugins sources into Claude Code, Codex CLI, OpenCode, and Pi.
Install skills, commands, and MCP servers from marketplaces and Agent Plugins (AP) sources into your coding agents: Claude Code, Codex CLI, OpenCode, and Pi.
Current version: 0.4.0, which is what this page documents. equipr is pre-1.0, so the surface is still settling.
What Is This?
A single Go binary that:
- Fetches a source once, whether that is a git repository, an npm package, an archive URL, or a local path
- Resolves what is inside it: the plugins, and the skills, commands, and MCP servers each plugin holds
- Places every component where its target agent expects to find it, by copy or by symlink
- Records what it did, so
update,status,doctor, anduninstallwork from facts instead of guesses
The more interesting part is what equipr refuses to do.
It never registers itself as a native plugin in any agent. No /plugin, no global npm install, nothing that turns up in an agent’s own plugin list; it writes to personal and global config surfaces and stops there. It also never touches a config key it does not own. Every write into a shared agent config is a targeted single-key merge, which is why your hand-edits, your comments, and your key ordering all survive it.
Why It Exists
Skills and MCP servers are distributed as git repositories, npm packages, and archives, but every coding agent expects them in a different place, in a different shape. Claude Code reads ~/.claude/skills/. OpenCode reads ~/.config/opencode/skills/ and uses a different MCP config schema. The manual answer is copying directories around and hand-editing shared JSON and TOML config files that also hold your credentials.
Doing that by hand is fine once. It stops being fine at four agents, a dozen skills, and a config file you have already customized.
Quick Start
1. Install
brew install maquina-app/tap/equipr
2. Add a source
$ equipr add https://github.com/coreyhaines31/marketingskills
Fetching https://github.com/coreyhaines31/marketingskills
Added marketingskills (marketplace, fetched via git) with 1 plugin(s)
- marketing-skills 2.10.0
3. See what it holds
$ equipr list
marketingskills marketplace
marketing-skills 2.10.0 49 skills
4. Install a component into your agents
$ equipr install marketingskills/marketing-skills:seo-audit --yes
Installed 1 component(s) into 3 agent(s):
[claude-code] seo-audit (copy -> ~/.claude/skills/seo-audit)
[opencode] seo-audit (symlink -> ~/.config/opencode/skills/seo-audit)
[pi] seo-audit (symlink -> ~/.pi/agent/skills/seo-audit)
Three agents rather than four, because Codex was not installed on the machine this run was captured on.
Without --yes, install is interactive: pick agents, then pick components. Components all start checked, and so do the agents whose binary is on $PATH, so pressing enter through both installs everything into every agent you actually have.
5. Check your installs
$ equipr status
STATE COMPONENT SOURCE/PLUGIN AGENT MECH TARGET
ok seo-audit(s) marketingskills/marketing-skills claude-code copy ~/.claude/skills/seo-audit
ok seo-audit(s) marketingskills/marketing-skills opencode symlink ~/.config/opencode/skills/seo-audit
ok seo-audit(s) marketingskills/marketing-skills pi symlink ~/.pi/agent/skills/seo-audit
$ equipr doctor
No issues found.
Installation
Every release publishes binaries for darwin, linux, and windows on amd64 and arm64. macOS binaries are ad-hoc codesigned, because an unsigned Mach-O binary is killed on Apple Silicon.
# Homebrew (macOS and Linux)
brew install maquina-app/tap/equipr
# Install script: /usr/local/bin when that is writable, otherwise ~/.local/bin
curl -fsSL https://github.com/maquina-app/equipr/releases/latest/download/install.sh | sh
# From source (Go 1.24+), into any directory on your PATH
go build -o ~/.local/bin/equipr ./cmd/equipr
go install ./cmd/equipr works too, but it puts the binary in $(go env GOPATH)/bin, which is not on everyone’s PATH.
Debian and RPM packages and raw archives are on the releases page.
Verify the install:
$ equipr --version
equipr version 0.3.3
equipr doctor reports on the health of your installs. It says nothing about the binary itself, and with nothing installed yet it prints No issues found. and exits 0.
Concepts
| Term | What it means |
|---|---|
| origin | Where content comes from, as you type it into add. Auto-detected as git, npm, archive, or local path. |
| source | One added origin, fetched into the cache and recorded in the registry. Either a marketplace or a single AP package. |
| source-id | The short handle a source is addressed by, derived from the origin, usually the repository or directory name. |
| plugin | One installable unit inside a source. An AP source has exactly one; a marketplace can have many. |
| component | One installable thing inside a plugin: a skill, a command, or an MCP server. |
| agent | An install target: claude-code, codex, opencode, or pi. |
| mechanism | How a component is placed: copy or symlink. |
Addressing Grammar
Everything nests, and every command addresses one of these three levels:
<source-id> marketingskills
<source-id>/<plugin> marketingskills/marketing-skills
<source-id>/<plugin>:<component> marketingskills/marketing-skills:seo-audit
Source Types
| Type | Detected by | Contains |
|---|---|---|
| Marketplace | .claude-plugin/marketplace.json at the root |
Many plugins, in subdirectories |
| Agent Plugins package | plugin.json at the root |
Exactly one plugin |
Origin Kinds
There are three kinds: git, npm, and archive. They are resolved in a fixed order, and the order does more work than the individual rules:
- An explicit
npm:prefix wins immediately: npm - Anything ending in
.zip,.tar.gz, or.tgz: archive. The test is on the string suffix alone, so a local./pkg.zipclassifies here too. It is checked before git, which is whyhttps://host/pkg.zipresolves as an archive - Contains
://, starts withgit@, or ends in.git: git - Exists on disk: npm if it is a directory holding
package.jsonand no.git, otherwise git. Any existing path qualifies, including a plain file - Nothing above matched and nothing exists at that path: npm. This is the fallback, and it is how a bare package name like
expressresolves
A local path is not a fourth kind. Steps 2 and 4 sort it into one of the three, and step 4 holds a trap: if a directory named express happens to exist in your working directory, equipr add express takes it as that directory rather than the npm package. Write npm:express to force the package.
An origin can also name a subdirectory, either as a fragment or as a forge tree URL:
equipr add https://github.com/owner/repo#plugins/foo
equipr add https://github.com/owner/repo/tree/main/plugins/foo
The /tree/<ref>/<path> form is http(s) only, and equipr clones the default branch shallowly. A tree URL naming some other branch is reported back to you with the # form as the way forward, rather than being quietly resolved against the default branch.
Component Types
| Type | Where it lives in a plugin |
|---|---|
| skill | A directory under skills/ holding a SKILL.md |
| command | A .md file under commands/ |
| MCP server | An entry in the plugin’s mcp.json |
Commands are normalized to <name>/SKILL.md for every agent, so a plugin’s commands and skills land in the same place and are discovered the same way.
Commands
| Command | Purpose |
|---|---|
equipr add <origin> |
Fetch an origin, resolve its contents, register it |
equipr list [source-id \| source-id/plugin] |
List the registry: sources, their plugins, and what each holds |
equipr show <source-id>[/plugin] |
Details for a source or one plugin |
equipr install <source>/<plugin>[:<component>] |
Place a plugin’s components into agents |
equipr update |
Re-fetch every source that has installs and re-apply each recorded component |
equipr remove <source-id> |
Drop a source from the registry |
equipr uninstall <source>/<plugin>[:<component>] |
Remove installed files and records |
equipr doctor |
Read-only health check |
equipr status |
Read-only report of every recorded install |
add
Re-running add on a source that already exists refreshes it. Git sources pull; npm and archive sources are re-fetched.
equipr add https://github.com/coreyhaines31/marketingskills
list
-c / --components expands each plugin to its component names. Passing <source>/<plugin> prints what show prints.
equipr list marketingskills --components
install
Interactive by default: pick agents, then pick components. Components start checked; agents start checked when their binary is on $PATH (see Detection). Use -a to pin targets and --yes to skip the prompts.
equipr install marketingskills/marketing-skills:seo-audit --yes -a claude-code
update
Takes no arguments. It operates on every source that has installs.
equipr update --dry-run
--dry-run still re-fetches, so it can report “would update v1 to v2”, but it writes nothing and never prompts.
remove and uninstall
The two are deliberately separate:
removederegisters the source but leaves the installed files and their records in place, souninstallcan still clean them up.doctorreports those records asorphaned-record.remove --purgedoes it all at once: drops the records, deletes the installed files, and removes the cache clone.uninstallremoves files and records for a plugin or a single component, and works on orphaned records after aremove.
equipr uninstall marketingskills/marketing-skills:seo-audit
equipr remove marketingskills --purge
doctor
Read-only. Reports four conditions: orphaned-record, vanished-config-dir, broken-symlink, and modified-cache. It exits 5 when it finds issues and 0 when clean, so scripts checking $? should expect that.
status
A report of every recorded install: state (ok, modified, or missing), component, source and plugin, agent, mechanism, and target path. Writes nothing.
Flags
Persistent
One flag is registered on the root command and applies everywhere.
| Flag | Effect |
|---|---|
-y, --yes |
Assume yes to all prompts: install into every present agent, install all components, resolve conflicts as overwrite |
--json is not persistent, despite looking like it should be. It is registered on each of the nine commands that produce results, and completion and help never register it. So equipr --json on its own is an error, and so is equipr completion zsh --json.
Per Command
| Command | Flag | Values | Default |
|---|---|---|---|
install |
-a, --agent (repeatable) |
claude-code, codex, opencode, pi |
Prompt |
install |
-c, --component |
A component name, the same thing as the :name suffix |
Prompt (all with --yes) |
install |
--mechanism |
copy, symlink |
The per-agent default |
install |
--on-conflict |
prompt, overwrite, skip, fail |
prompt |
update |
--on-conflict |
prompt, overwrite, skip, fail |
prompt |
update |
--dry-run |
Report only, write nothing | Off |
remove |
--purge |
Also delete records, files, and cache | Off |
list |
-c, --components |
Expand plugins to component names | Off |
Choosing a Mechanism
| Mechanism | Pick it when |
|---|---|
symlink |
You want the agent to track the cache, so update is instant and the source stays the single copy on disk |
copy |
You want the installed version pinned and independent of the cache, or the agent does not reliably follow links |
The per-agent defaults exist for a reason: whole-directory symlinks proved unreliable for Claude Code and Codex, while OpenCode and Pi dereference links at any depth. Symlinks are always created per component, never for a whole directory.
Choosing a Conflict Policy
--on-conflict applies when a target has been locally modified since it was installed.
| Value | Behavior | Use it for |
|---|---|---|
prompt |
Ask per file | Interactive use; the default |
overwrite |
Discard local edits | When the source is the truth |
skip |
Keep local edits and move on | When you have deliberately customized a skill |
fail |
Abort the whole run on the first modification | Scripts and CI, where a surprise should stop the pipeline |
Exit Codes
Stable, and safe to script against.
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Generic or usage error |
2 |
Not found |
3 |
Conflict |
4 |
Environment error |
5 |
doctor found issues |
Agents
| Agent | Detected by | Components land in | MCP config |
|---|---|---|---|
claude-code |
~/.claude/ or claude on $PATH |
~/.claude/skills/<name>/ |
~/.claude.json |
codex |
~/.codex/ or codex on $PATH |
~/.codex/skills/<name>/ |
~/.codex/config.toml |
opencode |
~/.config/opencode/ or opencode on $PATH |
~/.config/opencode/skills/<name>/ |
~/.config/opencode/opencode.json |
pi |
~/.pi/agent/ or pi on $PATH |
~/.pi/agent/skills/<name>/ |
~/.config/mcp/mcp.json |
Default mechanism: copy for claude-code and codex, symlink for opencode and pi.
Detection
An agent is offered when either signal holds: a config directory, or a binary on $PATH. Both are needed, because a freshly installed agent has no config directory until its first run, and a config directory outlives an uninstalled agent. The picker labels each agent with the evidence it found (configured, on PATH, or both), so a surprising entry explains itself.
Being offered and being selected are different things. Since 0.4.0 the picker pre-checks only the agents whose binary is on $PATH. A config-only agent is listed but starts unchecked, because a leftover config directory outlives an uninstalled agent and enter should not push skills into a directory nothing reads. The exception: when nothing at all is on $PATH, every row starts checked, so that enter is never a silent no-op. --yes is unaffected and still installs into every agent present, config-only ones included.
MCP Server Writes
MCP servers are never written as a whole file. equipr merges a single key into the agent’s existing config, atomically. It writes to a temporary file, then renames it, preserving the file mode. JSON goes through sjson and gjson, TOML through a dedicated writer, specifically so key order, formatting, and comments in a file you hand-edited survive the write.
Agent schemas differ, and equipr writes each in its native shape. OpenCode uses an mcp key with an explicit type and command as an array, where Claude Code uses mcpServers with string inference.
Files and State
| Path | Holds |
|---|---|
$XDG_CONFIG_HOME/equipr/config.toml |
Reserved; nothing reads it yet |
$XDG_STATE_HOME/equipr/sources.json |
The registry: every added source and what is in it |
$XDG_STATE_HOME/equipr/installs.json |
Install records: component, agent, target path, content hash |
$XDG_STATE_HOME/equipr/equipr.lock |
Lock file, held across mutating operations |
$XDG_CACHE_HOME/equipr/sources/<id>/ |
The fetched tree, per source |
On macOS those XDG defaults resolve under ~/Library/Application Support/equipr and ~/Library/Caches/equipr. On Linux, ~/.local/state/equipr and ~/.cache/equipr.
Hand-Editing
Use the commands, not an editor. sources.json and installs.json are rewritten wholesale under the lock, and installs.json records a content hash per target that the conflict check compares against, so editing it by hand makes status and doctor report the wrong thing.
Deleting the cache directory is the one safe destructive act: add or update re-fetches it.
JSON Output
Nine commands accept --json: add, list, show, install, update, remove, uninstall, doctor, and status. Results go to stdout and progress to stderr, so stdout stays parseable.
Script against --json, not the plain-text output. The text layout is still moving: list was restructured in 0.3.2 and status in 0.3.3, both on the same day.
equipr list --json, trimmed. The real skills array holds 49 entries:
{
"sources": [
{
"id": "marketingskills",
"type": "marketplace",
"kind": "git",
"origin": "https://github.com/coreyhaines31/marketingskills",
"plugin_count": 1,
"plugins": [
{
"name": "marketing-skills",
"version": "2.10.0",
"description": "Marketing skills for AI agents — conversion optimization, copywriting, SEO, paid ads, ad creative, and growth",
"author": "Corey Haines",
"skills": ["ab-testing", "ad-creative", "ads", "ai-seo", "..."]
}
]
}
]
}
equipr status --json, trimmed to one of three entries:
{
"entries": [
{
"source_id": "marketingskills",
"plugin": "marketing-skills",
"component": "seo-audit",
"component_type": "skill",
"agent": "claude-code",
"mechanism": "copy",
"target": "~/.claude/skills/seo-audit",
"version": "2.10.0",
"state": "ok"
}
]
}
Shell Completion
Cobra-generated completion is available for four shells: bash, zsh, fish, and powershell.
equipr completion zsh > "${fpath[1]}/_equipr" # then restart your shell
equipr completion bash > /etc/bash_completion.d/equipr
equipr completion fish > ~/.config/fish/completions/equipr.fish
equipr completion <shell> --help prints the install instructions for that shell.
Completion covers commands and flags. Source, plugin, and component names are resolved at runtime, so they are not completed.
Recent Changes
| Version | Change |
|---|---|
| 0.4.0 | Local paths that are neither a git repository nor an npm package are rejected up front with a message naming the problem, instead of failing later as a git error. The install picker pre-checks only agents found on $PATH |
| 0.3.4 | MIT license added. No change to commands, flags, output, or JSON |
| 0.3.3 | status columns sized from the rows |
| 0.3.2 | list nests plugins under sources with component counts, gained --components, and accepts <source>/<plugin> |
| 0.3.1 | Install prompts pre-check every option; agents detected by config directory or binary on $PATH; skills/ and commands/ walked recursively; marketplace entries rooted at "./" resolve |
Before 0.3.1, install prompts started with nothing selected, so pressing enter installed nothing.
Troubleshooting
Nothing was installed
The picker toggles with space and submits with enter. Components start checked, so enter installs all of them. Deselect everything and press enter, though, and nothing installs; equipr says as much. Two other ways to end up with nothing: on 0.3.0 and earlier every row started unselected and the error wrongly claimed the plugin had no components, so upgrade or pass --yes; and since 0.4.0 a config-only agent starts unchecked, so an install can succeed while skipping an agent you expected it to reach.
An agent you do not use is offered
A leftover config directory counts as configured, even with no binary installed. Check the label in the picker: configured on its own means no binary was found on $PATH, and since 0.4.0 that row starts unchecked. Delete the stale directory, or pin your targets with -a. Note that --yes ignores the distinction and installs into every agent present.
A plugin shows no components
Most often it ships only an agents/ directory, holding Claude Code subagents, which equipr does not model yet. equipr handles skills, commands, and MCP servers. That is a known gap in equipr; the source itself is fine.
Adding a local path is rejected
Origin classification resolves any existing path it does not recognize to git, so a directory that is not a git repository, a plain file, and a local archive are each rejected before anything is fetched, with a message naming the actual problem:
$ equipr add ./plaindir
equipr: ./plaindir is neither a git repository nor an npm package directory; a local
source must be one or the other (run `git init` there, or point equipr at a remote origin)
$ equipr add ./notes.txt
equipr: ./notes.txt is a file, not a source; add the directory that holds plugin.json
or .claude-plugin/marketplace.json
$ equipr add ./pkg.zip
equipr: ./pkg.zip is a local archive; equipr fetches archives over http(s) only, so
extract it and add the directory instead
Exit code 1 in all three cases. Before 0.4.0 the first of these surfaced as a raw git clone: repository does not exist, which read like a bug in equipr and was not one.
A marketplace reports fewer plugins than it has
An entry whose source is a remote reference equipr cannot resolve in-tree (a github, npm, or archive object form) is skipped and named in the output, rather than failing the whole marketplace. The fix belongs upstream in the marketplace.
doctor reports modified-cache
The cached tree no longer matches the digest recorded when it was added, which usually means something outside equipr touched the cache. Run equipr add <origin> to re-fetch and clear it.
doctor exits 5
Exit code 5 is documented behavior: doctor found something. Nothing crashed.
Records survive remove
By design. remove deregisters the source but leaves files and records so uninstall can still clean them, and doctor reports them as orphaned-record. Use remove --purge for the one-shot version.
Two components with the same name fail the whole source
Components are named by their own directory or file, which is not unique across a nested tree, and skills and commands share one flat install namespace. Rather than silently overwriting one with the other, add fails and names both paths. The fix belongs upstream in the source.
License
MIT. Copyright (c) 2026 Mario Alberto Chávez.
Next Steps
GitHub Repository
Source code, releases, issues, and contribution guidelines.
Announcement
Why equipr exists, and what it refuses to do.
AI Tools
MCP servers and Claude Code plugins, the kind of thing equipr installs.
Agent Plugins Specification
The plugin format equipr resolves from a source.
Agent Skills Specification
The skill format behind every SKILL.md equipr installs.